package io.netfoundry.billing.domain.subscription;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import io.netfoundry.billing.chargebee.ChargebeeCustomerService;
import io.netfoundry.billing.chargebee.ChargebeeSubscriptionService;
import io.netfoundry.billing.controller.EnterpriseSubscriptionController;
import io.netfoundry.billing.external.netsuite.client.NetSuiteApiClient;
import io.netfoundry.billing.external.netsuite.domain.LineItemsItem;
import io.netfoundry.billing.external.netsuite.domain.NetSuiteSubscription;
import io.netfoundry.billing.external.netsuite.domain.PricePlan;
import io.netfoundry.billing.external.netsuite.domain.PricePlanMapper;
import io.netfoundry.billing.external.netsuite.domain.PricingTier;
import io.netfoundry.billing.external.netsuite.domain.PricingTierItem;
import io.netfoundry.billing.external.netsuite.domain.PricingTierResponse;
import io.netfoundry.billing.external.netsuite.domain.SubscriptionPriceIntervalResponse;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class EnterpriseSubscriptionService extends SubscriptionServiceBase
    implements SubscriptionService {

  private final ChargebeeSubscriptionService chargebeeSubscriptionService;
  private final NetSuiteApiClient netSuiteApiClient;

  @Autowired
  public EnterpriseSubscriptionService(ChargebeeCustomerService chargebeeService,
      ChargebeeSubscriptionService chargebeeSubscriptionService, NetSuiteApiClient netSuiteApiClient,
      SubscriptionCacheRepository subscriptionCacheRepository) {
    super(chargebeeService, chargebeeSubscriptionService, subscriptionCacheRepository);
    this.chargebeeSubscriptionService = chargebeeSubscriptionService;
    this.netSuiteApiClient = netSuiteApiClient;
  }

  public Optional<Subscription> createEnterpriseSubscription(String paymentProfileId,
      EnterpriseSubscriptionController.CreateSubscription createSubscription,
      SubscriptionConfig subscriptionConfig) {

    Optional<com.chargebee.models.Subscription> subscription =
        chargebeeSubscriptionService.createSubscription(paymentProfileId,
            createSubscription, subscriptionConfig);

    if (subscription.isEmpty())
      return Optional.empty();

    return getSubscription(subscription.get(), subscriptionConfig);
  }

  public Optional<Subscription> updateEnterpriseSubscription(Subscription subscription,
      EnterpriseSubscriptionController.UpdateSubscription updateSubscription,
      SubscriptionConfig subscriptionConfig) {

    Optional<com.chargebee.models.Subscription> updatedSubscription =
        chargebeeSubscriptionService.updateSubscription(subscription, updateSubscription,
            subscriptionConfig);

    if (updatedSubscription.isEmpty())
      return Optional.empty();

    return getSubscription(updatedSubscription.get(), subscriptionConfig);
  }

  public Optional<Subscription> getNetSuiteEnterpriseSubscription(String subscriptionId) {
    Optional<NetSuiteSubscription> netSuiteSubscription = getSubscription(subscriptionId);
    NetSuiteSubscription suiteSubscription = netSuiteSubscription.orElseThrow();
    Map<String, String> lineItems = suiteSubscription.getSubscription().getLineItems().stream().filter(lineItemsItem -> lineItemsItem.getStatus().equals("ACTIVE"))
        .collect(Collectors.toMap(LineItemsItem::getDisplayName, item -> String.valueOf(item.getQuantity())));
    Subscription subscription = new Subscription(suiteSubscription.getSubscription().getId() + "", suiteSubscription.getSubscription().getName(), null,
        suiteSubscription.getSubscription().getNfSubscriptionId(), null, null, null,
        suiteSubscription.getSubscription().getBillingSubscriptionStatus(),
        ObjectUtils.isEmpty(suiteSubscription.getSubscription().getSubscriptionCost()) ? 0
            : (int) Math.floor(Double.valueOf(suiteSubscription.getSubscription().getSubscriptionCost())),
        suiteSubscription.getSubscription().getCurrency(),
        suiteSubscription.getSubscription().getFrequency(), null, null, null, suiteSubscription.getSubscription().getStartDate(),
        suiteSubscription.getSubscription().getEndDate(), null, lineItems, getPricePlanBySQL(subscriptionId),
        null,
        suiteSubscription.getSubscription().getStartDate(), null, null, null, null, null);
    return Optional.ofNullable(subscription);
  }

  /**
   * Get all priceTiers via REST API call. Due to performance issue for now we can ignore this call
   * 
   * @param subscriptionId
   * @return
   */
  private List<SubscriptionPricePlan> getPricePlan(String subscriptionId) {
    List<SubscriptionPricePlan> subscriptionPricePlans = new ArrayList<>();
    SubscriptionPriceIntervalResponse subscriptionPriceIntervalResponse = netSuiteApiClient.getSubscriptionPriceInterval(subscriptionId).orElse(null);
    if (subscriptionPriceIntervalResponse != null && !CollectionUtils.isEmpty(subscriptionPriceIntervalResponse.getPriceInterval().getItems())) {
      /**
       * Get all active line items for the subscription
       */
      subscriptionPriceIntervalResponse.getPriceInterval().getItems().stream().filter(item -> "ACTIVE".equalsIgnoreCase(item.getStatus().getId()))
          .forEach(item -> {
            Optional<PricingTierResponse> pricingTierResponse = netSuiteApiClient.getSubscriptionPricingTier(item.getPricePlan().getId());
            PricingTierResponse pricingTier = pricingTierResponse.orElse(new PricingTierResponse());
            List<SubscriptionPricingTier> subscriptionPricingTiers = new ArrayList<>();
            if (pricingTier.getPriceTiers().getItems() != null) {
              pricingTier.getPriceTiers().getItems().forEach(priceTierItem -> {
                SubscriptionPricingTier subscriptionPricingTier = new SubscriptionPricingTier();
                subscriptionPricingTier.setMinQuantity(priceTierItem.getMinQuantity());
                subscriptionPricingTier.setMaxQuantity(priceTierItem.getMaxQuantity());
                subscriptionPricingTier.setPrice(priceTierItem.getPrice());
                subscriptionPricingTiers.add(subscriptionPricingTier);
              });
              SubscriptionPricePlan subscriptionPricePlan = new SubscriptionPricePlan();
              subscriptionPricePlan.setPlanName(item.getItem().getRefName());
              subscriptionPricePlan.setPlanId(item.getPricePlan().getId());
              subscriptionPricePlan.setTier(subscriptionPricingTiers);
              subscriptionPricePlans.add(subscriptionPricePlan);
            }
          });
    }
    return subscriptionPricePlans;
  }

  private List<PricePlan> getPricePlanBySQL(String subscriptionId) {
    List<SubscriptionPricePlan> subscriptionPricePlans = new ArrayList<>();
    SubscriptionPriceIntervalResponse subscriptionPriceIntervalResponse = netSuiteApiClient.getSubscriptionPriceInterval(subscriptionId).orElse(null);
    if (subscriptionPriceIntervalResponse != null && !CollectionUtils.isEmpty(subscriptionPriceIntervalResponse.getPriceInterval().getItems())) {
      // Get all active line items for the subscription
      List<String> activePricePlanIds = subscriptionPriceIntervalResponse.getPriceInterval().getItems().stream().filter(item -> "ACTIVE".equalsIgnoreCase(item.getStatus().getId()))
          .map(item -> item.getPricePlan().getId()).filter(Objects::nonNull).collect(Collectors.toList());
      //join all price line ids 
      String pricePlanId = activePricePlanIds.stream().map(String::valueOf).collect(Collectors.joining(","));
      //pass all price line ids to get price tier of each subscription line item
      PricingTier subscriptionPricePlanTier = netSuiteApiClient.findSubscriptionPricePlanTier(pricePlanId);
      subscriptionPriceIntervalResponse.getPriceInterval().getItems().stream().filter(item -> "ACTIVE".equalsIgnoreCase(item.getStatus().getId()))
          .forEach(subscriptionLineItem -> {
            List<SubscriptionPricingTier> subscriptionPricingTiers = new ArrayList<>();
            if (subscriptionPricePlanTier.getItems() != null) {
              List<PricingTierItem> pricingTierItemList =
                  subscriptionPricePlanTier.getItems().stream().filter(planTier -> planTier.getId().equals(subscriptionLineItem.getPricePlan().getId()))
                      .collect(Collectors.toList());
              pricingTierItemList.forEach(priceTierItem -> {
                if (priceTierItem.getId().equals(subscriptionLineItem.getPricePlan().getId())) {
                  SubscriptionPricingTier subscriptionPricingTier = new SubscriptionPricingTier();
                  subscriptionPricingTier.setMinQuantity(priceTierItem.getMinQuantity());
                  subscriptionPricingTier.setMaxQuantity(priceTierItem.getMaxQuantity());
                  subscriptionPricingTier.setPrice(priceTierItem.getPrice());
                  subscriptionPricingTiers.add(subscriptionPricingTier);
                }
              });
              SubscriptionPricePlan subscriptionPricePlan = new SubscriptionPricePlan();
              subscriptionPricePlan.setPlanName(subscriptionLineItem.getItem().getRefName());
              subscriptionPricePlan.setPlanId(subscriptionLineItem.getPricePlan().getId());
              subscriptionPricePlan.setTier(subscriptionPricingTiers);
              subscriptionPricePlans.add(subscriptionPricePlan);
            }
          });
    }
    // transform to Actual PricePlan
    Map<String, PlanWithTiers> planWithTiers = subscriptionPricePlans.stream()
        .collect(Collectors.toMap(SubscriptionPricePlan::getPlanName, spp -> new PlanWithTiers(spp.getPlanName(), spp.getPlanId(), toTierMap(spp.getTier())),
            (p1, p2) -> Integer.parseInt(p1.getPlanId()) >= Integer.parseInt(p2.getPlanId()) ? p1 : p2));
    List<PricePlan> planList = new ArrayList<>();
    planWithTiers.entrySet().forEach(data -> {
      PricePlan pricePlan = PricePlanMapper.getPricePlan(data.getKey(), data.getValue().getTierMap());
      planList.add(pricePlan);
    });
    return planList;
  }

  private Map<String, Double> toTierMap(List<SubscriptionPricingTier> tiers) {
    return tiers.stream().collect(Collectors.toMap(SubscriptionPricingTier::getMinQuantity, SubscriptionPricingTier::getPrice));
  }

}
