package io.netfoundry.customer.process;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import io.netfoundry.customer.domain.assignment.CustomerResourceAssignment;
import io.netfoundry.customer.domain.assignment.CustomerResourceAssignmentService;
import io.netfoundry.customer.domain.assignment.FindCustomerResourceAssignment;
import io.netfoundry.customer.domain.assignment.ResourceType;
import io.netfoundry.customer.domain.customer.Customer;
import io.netfoundry.customer.domain.customer.CustomerService;
import io.netfoundry.customer.email.EmailNotificationService;
import io.netfoundry.customer.external.billing.BillingServiceClient;
import io.netfoundry.customer.external.core.NetworkClient;
import io.netfoundry.customer.external.core.NetworkGroupClient;
import io.netfoundry.customer.external.core.domain.Network;
import io.netfoundry.customer.external.core.domain.Status;
import io.netfoundry.customer.external.hubspot.DealConstants;
import io.netfoundry.customer.external.hubspot.HubspotContactService;
import io.netfoundry.customer.external.hubspot.HubspotDealService;
import io.netfoundry.customer.external.identity.IdentityClient;
import io.netfoundry.processing.ProcessResult;
import io.netfoundry.processing.stereotypes.ExceptionHandler;
import io.netfoundry.processing.stereotypes.Next;
import io.netfoundry.processing.stereotypes.ParallelArg;
import io.netfoundry.processing.stereotypes.Process;
import io.netfoundry.processing.stereotypes.Step;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Process("Offboard Teams Customer")
public class OffboardCustomerProcess2 {

  private final CustomerService customerService;
  private final CustomerResourceAssignmentService assignmentService;
  private final NetworkClient networkClient;
  private final HubspotDealService hubspotDealService;
  private final NetworkGroupClient networkGroupClient;
  private final IdentityClient identityClient;
  private final BillingServiceClient billingServiceClient;
  private final EmailNotificationService emailNotificationService;
  private static final String DEAL_NETWORK_STATUS_ACTIVE = "Active";
  private static final String DEAL_NETWORK_STATUS_DELETED = "Deleted";
  private static final String DEAL_STAGE_PROPERTY_NAME = "dealstage";
  private static final String DEAL_STAGE_PROPERTY_VALUE = "closedlost";
  private static final String PAYMENT_METHOD_INVALID_STATUS = "INVALID";


  @Autowired
  public OffboardCustomerProcess2(final CustomerService customerService,
      final CustomerResourceAssignmentService assignmentService,
      final NetworkClient networkClient,
      final HubspotDealService hubspotDealService, final NetworkGroupClient networkGroupClient, final IdentityClient identityClient,
      final BillingServiceClient billingServiceClient,
      final HubspotContactService hubspotContactService, EmailNotificationService emailNotificationService) {
    this.customerService = customerService;
    this.assignmentService = assignmentService;
    this.networkClient = networkClient;
    this.hubspotDealService = hubspotDealService;
    this.networkGroupClient = networkGroupClient;
    this.identityClient = identityClient;
    this.billingServiceClient = billingServiceClient;
    this.emailNotificationService = emailNotificationService;
  }

  @Step("Offboard Customer")
  @Next("deleteNetworks")
  public ProcessResult<Customer> offboardCustomer(final OffboardContext context) {
    context.customerResourceMap =
        assignmentService.find(FindCustomerResourceAssignment.by().customerId(context.customerId)).stream()
            .collect(Collectors.toMap(CustomerResourceAssignment::getResourceType, Function.identity()));
    context.stepExecutionMap = new HashMap<>();
    return ProcessResult.value(context.customer).success();
  }

  @Step("delete Networks")
  @Next("deleteNetwork")
  public ProcessResult<UUID> deleteNetworks(final OffboardContext context) {
    context.networkGroupId = context.customerResourceMap.get(ResourceType.NetworkGroup).getResourceId();
    final Set<Network> networks = networkClient.findNetworksByNetworkGroupId(context.networkGroupId);
    final List<UUID> networkIds = networks.stream()
        .filter(network -> network.getStatus() != Status.DELETED)// get all non-deleted networks
        .map(Network::getId)
        .collect(Collectors.toList());
    return ProcessResult.parallel(networkIds).build();

  }

  @Step("delete NetWork")
  @Next("deleteNetworkGroup")
  public ProcessResult<Customer> deleteNetwork(final OffboardContext context, @ParallelArg UUID networkId) {
    log.info("Deleting Network :  {}", networkId);
    try {
      networkClient.deleteNetwork(networkId);
      log.info("Network : {} deleted", networkId);
    } catch (Exception exception) {
      log.error("failed to delete the network {}", networkId);
      context.stepExecutionMap.put("Network", networkId);
      throw exception;
    }

    return ProcessResult.success();

  }

  @Step("delete Network Group")
  @Next("deactivateBillingAccount")
  public void deleteNetworkGroup(final OffboardContext context) {
    log.info("Deleting network group :  {}", context.networkGroupId);
    networkGroupClient.deleteNetworkGroup(context.networkGroupId);
    log.info("Network group : {} deleted", context.networkGroupId);
    log.info("Deleting CRA NetworkGroup: {}", context.networkGroupId);
    assignmentService.delete(context.customerResourceMap.get(ResourceType.NetworkGroup), context.getIdentityId());
    log.info("CRA NetworkGroup : {} deleted", context.networkGroupId);

  }

  @Step("deactivate Billing Account")
  @Next("deleteOrganization")
  public void deactivateBillingAccount(final OffboardContext context) {

    context.billingAccountId = context.customerResourceMap.get(ResourceType.BillingAccount).getResourceId();
    Optional.ofNullable(billingServiceClient.findContract(context.billingAccountId)).ifPresentOrElse(contract -> {
      // get profile payment profile by billingAccountId
      billingServiceClient.getPaymentProfileByBilingAccountId(context.billingAccountId).ifPresentOrElse(profile -> {
        // remove card if card status valid
        if (!PAYMENT_METHOD_INVALID_STATUS.equalsIgnoreCase(profile.stream().findFirst().get().getPaymentMethod().getStatus()))
          billingServiceClient.removePaymentCard(profile.stream().findFirst().get(), contract.stream().findFirst().get());
        // cancel self service subscription
        billingServiceClient.cancelSelfServiceSubscription(contract.stream().findFirst().get().getProductId(),
            contract.stream().findFirst().get().getProductName());

      }, () -> {
        log.info("No Payment Profiles found for the billingAccountId:{}", context.billingAccountId);
      });
    },
        () -> {
          log.info("No Billing Contracts found for the billingAccountId:{}", context.billingAccountId);
        });
    // deactivate billing account
    billingServiceClient.deactivateBillingAccount(context.billingAccountId);
    context.organizationId = context.customerResourceMap.get(ResourceType.Organization).getResourceId();
    // findDealByBillingAccountId
    hubspotDealService.findDealByBillingAccountId(context.billingAccountId)
        .ifPresentOrElse(d -> {
          if (DEAL_NETWORK_STATUS_ACTIVE.equals(d.getPropertyValue(DealConstants.NETWORK_STATUS.getName()))) {
            log.info("Setting deal {} to closed(lost)", d.getPropertyValue(DealConstants.DEAL_NAME.getName()));
            hubspotDealService.updateDealProperty(d, DEAL_STAGE_PROPERTY_NAME, DEAL_STAGE_PROPERTY_VALUE);
          }
        }, () -> log.warn("No HubSpot Deal found for billingAccountId={} to mark dealstage as closedlost)", context.billingAccountId));

    log.info("Deleting CRA BillingAccount: {}", context.billingAccountId);
    assignmentService.delete(context.customerResourceMap.get(ResourceType.BillingAccount), context.getIdentityId());
    log.info("CRA BillingAccount : {} deleted", context.billingAccountId);

  }

  @Step("delete Organization")
  @Next("updateHubSpotDealNetworkStatus")
  public void deleteOrganization(final OffboardContext context) {
    // delete Organization
    log.info("Deleting Organization : {}", context.organizationId);
    identityClient.deleteOrganization(context.organizationId);
    log.info("Organization deleted: {}", context.organizationId);
    // delete CRA for Organization
    log.info("Deleting CRA Organization : {}", context.organizationId);
    assignmentService.delete(context.customerResourceMap.get(ResourceType.NetworkGroup), context.getIdentityId());
    log.info("Deleted CRA Organization : {}", context.organizationId);
  }



  @Step("update HubSpot DealNetwork Status")
  @Next("deleteCustomer")
  public void updateHubSpotDealNetworkStatus(final OffboardContext context) {
    hubspotDealService.findDealByBillingAccountId(context.getBillingAccountId())
        .ifPresentOrElse(d -> {
          if (DEAL_NETWORK_STATUS_ACTIVE.equals(d.getPropertyValue(DealConstants.NETWORK_STATUS.getName()))) {
            log.info("Set deal network status {} to Deleted", d.getPropertyValue(DealConstants.DEAL_NAME.getName()));
            hubspotDealService.updateDealProperty(d, DealConstants.NETWORK_STATUS.getName(), DEAL_NETWORK_STATUS_DELETED);
          }
        }, () -> log.warn("No HubSpot Deal found for billingAccountId={} to mark as network status delete", context.getNetworkGroupId()));
  }

  @Step("delete Customer")
  @Next("finish")
  public void deleteCustomer(final OffboardContext context) {
    log.info("Deleting Customer :  {}", context.customerId);
    customerService.delete(context.customer, context.getIdentityId());
    log.info("Customer : {} deleted", context.customerId);

  }

  @Step("Finish")
  private void finish(final OffboardContext context) {
    log.info("Offboard customer {} completed.", context.customerId);
  }

  @ExceptionHandler
  private void handleException(Exception exception, OffboardContext context) throws Exception {
    log.error("Error on customer offboarding");
    emailNotificationService.notifyExceptionToSupport("organization.getLabel()", "Deleting network resources", context.getStepExecutionMap().get("network").toString());
  }

  @lombok.Data
  @NoArgsConstructor
  public static class OffboardContext {
    UUID customerId;
    UUID networkGroupId;
    UUID identityId;
    UUID organizationId;
    UUID billingAccountId;
    Customer customer;
    Map<ResourceType, CustomerResourceAssignment> customerResourceMap;
    Map<String, UUID> stepExecutionMap;

    public OffboardContext(UUID customerId, UUID identityId, Customer customer) {
      this.customerId = customerId;
      this.identityId = identityId;
      this.customer = customer;
    }

  }

}
