package io.netfoundry.billing.process;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
import org.springframework.util.CollectionUtils;
import io.netfoundry.billing.config.UsageMeteredItemConfig;
import io.netfoundry.billing.domain.contract.Contract;
import io.netfoundry.billing.domain.contract.ContractService;
import io.netfoundry.billing.domain.subscription.SubscriptionCacheRepository;
import io.netfoundry.billing.netsuite.auth.client.NetSuiteApiClient;
import io.netfoundry.billing.netsuite.auth.client.NetSuiteAuthService;
import io.netfoundry.billing.netsuite.domain.subscription.DailyUsage;
import io.netfoundry.billing.netsuite.domain.subscription.DailyUsageService;
import io.netfoundry.billing.netsuite.domain.subscription.ItemsItem;
import io.netfoundry.billing.netsuite.domain.subscription.PostUsagePayload;
import io.netfoundry.billing.netsuite.domain.subscription.Subscription;
import io.netfoundry.billing.netsuite.domain.subscription.UsageSubscription;
import io.netfoundry.billing.netsuite.domain.subscription.UsageSubscriptionLine;
import io.netfoundry.billing.processor.usage.EnterpriseOverageInvoicingService;
import io.netfoundry.processing.ProcessResult;
import io.netfoundry.processing.stereotypes.CollectionArg;
import io.netfoundry.processing.stereotypes.ExceptionHandler;
import io.netfoundry.processing.stereotypes.Next;
import io.netfoundry.processing.stereotypes.Process;
import io.netfoundry.processing.stereotypes.Step;
import lombok.Builder;
import lombok.extern.jackson.Jacksonized;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Process("Enterprise Monthly Overage Invoice Process")
public class EnterpriseMonthlyOverageInvoiceProcess {

  protected final ContractService contractService;
  protected final EnterpriseOverageInvoicingService enterpriseOverageInvoicingService;
  private final DailyUsageService dailyUsageService;
  private final NetSuiteApiClient apiClient;
  private final NetSuiteAuthService authService;
  private final UsageMeteredItemConfig usageMeteredItemConfig;

  private final SubscriptionCacheRepository subscriptionCacheRepository;

  public EnterpriseMonthlyOverageInvoiceProcess(ContractService contractService, EnterpriseOverageInvoicingService enterpriseOverageInvoicingService,
      DailyUsageService dailyUsageService, SubscriptionCacheRepository subscriptionCacheRepository, NetSuiteApiClient apiClient, NetSuiteAuthService authService,
      UsageMeteredItemConfig usageMeteredItemConfig) {
    this.contractService = contractService;
    this.enterpriseOverageInvoicingService = enterpriseOverageInvoicingService;
    this.dailyUsageService = dailyUsageService;
    this.subscriptionCacheRepository = subscriptionCacheRepository;
    this.apiClient = apiClient;
    this.authService = authService;
    this.usageMeteredItemConfig = usageMeteredItemConfig;
  }

  @Step("Get All Active Contracts for Invoicing")
  @Next("createInvoiceForContracts")
  public ProcessResult<Void> createOverageInvoice(EnterpriseMonthlyOverageInvoiceContext enterpriseMonthlyOverageInvoiceContext) {
    // get all active contracts for suite billing subscription
    List<DailyUsage> dailyUsageList = new ArrayList<>();
    List<Contract> contractList = enterpriseMonthlyOverageInvoiceContext.getContracts().stream().filter(contract -> {
      Set<DailyUsage> dailUsageList = dailyUsageService.find(contract.getId());
      return !CollectionUtils.isEmpty(dailUsageList);
    }).collect(Collectors.toList());

    if (CollectionUtils.isEmpty(contractList)) {
      log.warn("No daily usage found for the contracts");
      return ProcessResult.fail();
    }
    enterpriseMonthlyOverageInvoiceContext.setContracts(contractList);
    // enterpriseMonthlyOverageInvoiceContext.setDailyUsages(dailyUsageList);
    return ProcessResult.success();
  }


  @Step("Generate Invoice for All Contracts")
  @Next("createInvoiceForContract")
  private ProcessResult<Contract> createInvoiceForContracts(EnterpriseMonthlyOverageInvoiceContext enterpriseMonthlyOverageInvoiceContext) {
    return ProcessResult.parallel(enterpriseMonthlyOverageInvoiceContext.getContracts()).build();

  }

  @Step("Create Invoice For Contract")
  @Next("finish")
  private ProcessResult<Boolean> createInvoiceForContract(@CollectionArg Contract contract, EnterpriseMonthlyOverageInvoiceContext enterpriseMonthlyOverageInvoiceContext) {
    // look up subscription by using the productId
    Optional<Subscription> subscriptionforSuite = subscriptionCacheRepository.getForSuite(contract.getProductId());
    // from the subscription look up for subscriptionId and subscriptionline number
    Subscription subscription = subscriptionforSuite.get();
    // get item name from subscription using key :) magic key
   /* Optional<ItemsItem> data =
        subscription.getSubscriptionLine().getItems().stream().filter(itemsItem -> itemsItem.getItem().getRefName().equals("Entitled Data (GB)")).findFirst();
    Optional<ItemsItem> endpoint =
        subscription.getSubscriptionLine().getItems().stream().filter(itemsItem -> itemsItem.getItem().getRefName().equals("Entitled Endpoint")).findFirst();
    Optional<ItemsItem> gateWay =
        subscription.getSubscriptionLine().getItems().stream().filter(itemsItem -> itemsItem.getItem().getRefName().equals("Entitled Gateway")).findFirst();
    Optional<ItemsItem> network =
        subscription.getSubscriptionLine().getItems().stream().filter(itemsItem -> itemsItem.getItem().getRefName().equals("Entitled Network")).findFirst();
*/
    // usage data
    Set<DailyUsage> dailUsageList = dailyUsageService.find(contract.getId());

    dailUsageList.forEach(dailUsageData -> {
      Optional<DailyUsage> usageForContract = dailUsageList.stream().filter(usage -> usage.getContractId().compareTo(contract.getId()) == 0).findAny();
      // prepare payload
      PostUsagePayload postUsagePayload = new PostUsagePayload();
      Optional<ItemsItem> item = subscription.getSubscriptionLine().getItems().stream()
          .filter(itemsItem -> itemsItem.getItem().getRefName().equals(usageMeteredItemConfig.getUsageItemMap().get(dailUsageData.getMeteredItem()))).findFirst();
      postUsagePayload.setUsagedate(LocalDate.now().toString());
      postUsagePayload.setUsagequantity((int) Math.round(dailUsageData.getMeteredQuantity()));
      UsageSubscription usageSubscription = new UsageSubscription();
      usageSubscription.setId(subscription.getId());
      postUsagePayload.setUsageSubscription(usageSubscription);
      UsageSubscriptionLine usageSubscriptionLine = new UsageSubscriptionLine();
      usageSubscriptionLine.setId(item.get().getSubscriptionLine() + "");
      postUsagePayload.setUsageSubscriptionLine(usageSubscriptionLine);
      apiClient.postUsage(postUsagePayload);


     /** if (dailUsageData.getMeteredItem().equals("Entitled Data (GB)")) {

        postUsagePayload.setUsagedate(LocalDate.now().toString());
        postUsagePayload.setUsagequantity((int) Math.round(dailUsageData.getMeteredQuantity()));
        UsageSubscription usageSubscription = new UsageSubscription();
        usageSubscription.setId(subscription.getId());
        postUsagePayload.setUsageSubscription(usageSubscription);
        UsageSubscriptionLine usageSubscriptionLine = new UsageSubscriptionLine();
        usageSubscriptionLine.setId(data.get().getSubscriptionLine() + "");
        postUsagePayload.setUsageSubscriptionLine(usageSubscriptionLine);
        apiClient.postUsage(postUsagePayload);

      }
      if (dailUsageData.getMeteredItem().equals("Entitled Endpoint")) {
        postUsagePayload.setUsagedate(LocalDate.now().toString());
        postUsagePayload.setUsagequantity((int) Math.round(dailUsageData.getMeteredQuantity()));
        UsageSubscription usageSubscription = new UsageSubscription();
        usageSubscription.setId(subscription.getId());
        postUsagePayload.setUsageSubscription(usageSubscription);
        UsageSubscriptionLine usageSubscriptionLine = new UsageSubscriptionLine();
        usageSubscriptionLine.setId(endpoint.get().getSubscriptionLine() + "");
        postUsagePayload.setUsageSubscriptionLine(usageSubscriptionLine);
        apiClient.postUsage(postUsagePayload);


      }
      if (dailUsageData.getMeteredItem().equals("Entitled Gateway")) {
        postUsagePayload.setUsagedate(LocalDate.now().toString());
        postUsagePayload.setUsagequantity((int) Math.round(dailUsageData.getMeteredQuantity()));
        UsageSubscription usageSubscription = new UsageSubscription();
        usageSubscription.setId(subscription.getId());
        postUsagePayload.setUsageSubscription(usageSubscription);
        UsageSubscriptionLine usageSubscriptionLine = new UsageSubscriptionLine();
        usageSubscriptionLine.setId(gateWay.get().getSubscriptionLine() + "");
        postUsagePayload.setUsageSubscriptionLine(usageSubscriptionLine);
        apiClient.postUsage(postUsagePayload);


      }
      if (dailUsageData.getMeteredItem().equals("Entitled Network")) {

        postUsagePayload.setUsagedate(LocalDate.now().toString());
        postUsagePayload.setUsagequantity((int) Math.round(dailUsageData.getMeteredQuantity()));
        UsageSubscription usageSubscription = new UsageSubscription();
        usageSubscription.setId(subscription.getId());
        postUsagePayload.setUsageSubscription(usageSubscription);
        UsageSubscriptionLine usageSubscriptionLine = new UsageSubscriptionLine();
        usageSubscriptionLine.setId(network.get().getSubscriptionLine() + "");
        postUsagePayload.setUsageSubscriptionLine(usageSubscriptionLine);
        apiClient.postUsage(postUsagePayload);

      }
*/
    });

    return ProcessResult.success();

  }

  @Step("Finish")
  private ProcessResult<Void> finish(EnterpriseMonthlyOverageInvoiceContext enterpriseMonthlyOverageInvoiceContext) {
    log.info("Enterprise overage invoicing finished successfully for the accountId: {} and contractId: {}", enterpriseMonthlyOverageInvoiceContext.getAccountId(),
        enterpriseMonthlyOverageInvoiceContext.getContractId());
    return ProcessResult.success();
  }

  @ExceptionHandler
  private void handleException(Exception e, EnterpriseMonthlyOverageInvoiceContext enterpriseMonthlyOverageInvoiceContext) {
    log.warn("Enterprise overage invoicing failed for the accountId: {} and contractId: {}", enterpriseMonthlyOverageInvoiceContext.getAccountId(),
        enterpriseMonthlyOverageInvoiceContext.getContractId(), e);

  }

  @lombok.Data
  @Builder
  @Jacksonized
  public static class EnterpriseMonthlyOverageInvoiceContext {
    UUID accountId;
    UUID contractId;
    List<Contract> contracts;
    List<DailyUsage> dailyUsages;
    String invoiceType;
    Page<Contract> contractPage;
    Long fromTimeEpochMs;
    Long toTimeEpochMs;
  }
}
