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 OffboardCustomerProcess {



  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 OffboardCustomerProcess(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) {

    ///


    log.info("Offboard customer {} completed with failed steps.");
    log.error("Error on customer off-boarding {}");
    context.stepExecutionMap = new HashMap<>();
    context.stepExecutionMap.put("Network", null);
    StringBuffer stepExecutionDetails = new StringBuffer();
    context.stepExecutionMap.forEach((key, value) -> {
      stepExecutionDetails.append(key);
      stepExecutionDetails.append(":");
      stepExecutionDetails.append(value);
      stepExecutionDetails.append(",");
    });
    stepExecutionDetails.deleteCharAt(stepExecutionDetails.length() - 1);
    try {
      emailNotificationService.notifyExceptionToSupport("organization.getLabel()", "Deleting network resources",
          stepExecutionDetails.toString());
    } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }



    ///
    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);
    try {
      networkGroupClient.deleteNetworkGroup(context.networkGroupId);
      log.info("Network group : {} deleted", context.networkGroupId);
    } catch (Exception exception) {
      log.error("failed to delete the network group {}", context.networkGroupId);
      context.stepExecutionMap.put("NetworkGroup", 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
    try {
      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));

    } catch (Exception exception) {
      log.error("failed to update HubSpot Deal Stage {}", context.billingAccountId);
      context.stepExecutionMap.put("HubSpotDealStageUpdateByBillingAccountId", 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);
    try {
      identityClient.deleteOrganization(context.organizationId);
      log.info("Organization deleted: {}", context.organizationId);
    } catch (Exception exception) {
      log.error("failed to delete the Organization {}", context.organizationId);
      context.stepExecutionMap.put("Organization", 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) {
    /// test
    try {
      hubspotDealService.findDealByBillingAccountId(context.billingAccountId)
          .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()));

    } catch (Exception exception) {
      log.error("failed to update HubSpot DealNetwork Status {}", context.billingAccountId);
      context.stepExecutionMap.put("HubSpotNetworkStatusUpdateByBillingAccountId", context.billingAccountId);
    }
  }

  @Step("delete Customer")
  @Next("finish")
  public void deleteCustomer(final OffboardContext context) {
    log.info("Deleting Customer :  {}", context.customerId);
    try {
      customerService.delete(context.customer, context.getIdentityId());
      log.info("Customer : {} deleted", context.customerId);
    } catch (Exception exception) {
      log.error("failed to delete Customer {}", context.customerId);
      context.stepExecutionMap.put("Customer", context.customerId);
    }


  }

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

    if (!context.stepExecutionMap.isEmpty()) {
      sendNotificationToSupport(context);
    }

  }

  @ExceptionHandler
  private void handleException(Exception exception, OffboardContext context) throws Exception {
    log.error("Error on customer off-boarding {}", exception);
    sendNotificationToSupport(context);

  }

  private void sendNotificationToSupport(OffboardContext context) {
    StringBuffer stepExecutionDetails = new StringBuffer();
    context.stepExecutionMap.forEach((key, value) -> {
      stepExecutionDetails.append(key);
      stepExecutionDetails.append(":");
      stepExecutionDetails.append(value);
      stepExecutionDetails.append(",");
    });
    stepExecutionDetails.deleteCharAt(stepExecutionDetails.length() - 1);
    try {
      emailNotificationService.notifyExceptionToSupport("organization.getLabel()", "Deleting resources",
          stepExecutionDetails.toString());
    } catch (Exception exception) {
      log.error("Error on sending email noification {}", exception);
    }

  }

  @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;
    }

  }

}
