import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js";
import { SalesforceOpportunityService } from "@bff/integrations/salesforce/services/salesforce-opportunity.service.js";
import { WhmcsOrderService } from "@bff/integrations/whmcs/services/whmcs-order.service.js";
import type { WhmcsOrderResult } from "@bff/integrations/whmcs/services/whmcs-order.service.js";
import { OrderOrchestrator } from "./order-orchestrator.service.js";
import { OrderFulfillmentValidator } from "./order-fulfillment-validator.service.js";
import { OrderFulfillmentErrorService } from "./order-fulfillment-error.service.js";
import { SimFulfillmentService } from "./sim-fulfillment.service.js";
import { FulfillmentSideEffectsService } from "./fulfillment-side-effects.service.js";
import { DistributedTransactionService } from "@bff/infra/database/services/distributed-transaction.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import type { OrderDetails } from "@customer-portal/domain/orders";
import type { OrderFulfillmentValidationResult } from "@customer-portal/domain/orders/providers";
import { createOrderNotes, mapOrderToWhmcsItems } from "@customer-portal/domain/orders/providers";
import { OPPORTUNITY_STAGE } from "@customer-portal/domain/opportunity";
import {
  OrderValidationException,
  FulfillmentException,
  WhmcsOperationException,
} from "@bff/core/exceptions/domain-exceptions.js";

type WhmcsOrderItemMappingResult = ReturnType<typeof mapOrderToWhmcsItems>;

export interface OrderFulfillmentStep {
  step: string;
  status: "pending" | "in_progress" | "completed" | "failed";
  startedAt?: Date | undefined;
  completedAt?: Date | undefined;
  error?: string | undefined;
}

export interface OrderFulfillmentContext {
  sfOrderId: string;
  idempotencyKey: string;
  validation: OrderFulfillmentValidationResult | null;
  orderDetails?: OrderDetails | undefined;
  mappingResult?: WhmcsOrderItemMappingResult | undefined;
  whmcsResult?: WhmcsOrderResult | undefined;
  steps: OrderFulfillmentStep[];
}

/**
 * Orchestrates the complete order fulfillment workflow
 * Similar to OrderOrchestrator but for fulfillment operations
 */
@Injectable()
export class OrderFulfillmentOrchestrator {
  constructor(
    @Inject(Logger) private readonly logger: Logger,
    private readonly salesforceService: SalesforceFacade,
    private readonly opportunityService: SalesforceOpportunityService,
    private readonly whmcsOrderService: WhmcsOrderService,
    private readonly orderOrchestrator: OrderOrchestrator,
    private readonly orderFulfillmentValidator: OrderFulfillmentValidator,
    private readonly orderFulfillmentErrorService: OrderFulfillmentErrorService,
    private readonly simFulfillmentService: SimFulfillmentService,
    private readonly distributedTransactionService: DistributedTransactionService,
    private readonly sideEffects: FulfillmentSideEffectsService
  ) {}

  /**
   * Execute complete fulfillment workflow with distributed transaction support
   */
  async executeFulfillment(
    sfOrderId: string,
    payload: Record<string, unknown>,
    idempotencyKey: string
  ): Promise<OrderFulfillmentContext> {
    return this.executeFulfillmentWithTransactions(sfOrderId, payload, idempotencyKey);
  }

  /**
   * Execute fulfillment workflow using distributed transactions for atomicity
   */
  private async executeFulfillmentWithTransactions(
    sfOrderId: string,
    payload: Record<string, unknown>,
    idempotencyKey: string
  ): Promise<OrderFulfillmentContext> {
    const context: OrderFulfillmentContext = {
      sfOrderId,
      idempotencyKey,
      validation: null,
      steps: this.initializeSteps(
        typeof payload["orderType"] === "string" ? payload["orderType"] : "Unknown"
      ),
    };

    this.logger.log("Starting transactional fulfillment orchestration", {
      sfOrderId,
      idempotencyKey,
    });

    try {
      // Step 1: Validation (no rollback needed)
      this.updateStepStatus(context, "validation", "in_progress");
      try {
        // eslint-disable-next-line require-atomic-updates -- context is not shared across concurrent executions
        context.validation = await this.orderFulfillmentValidator.validateFulfillmentRequest(
          sfOrderId,
          idempotencyKey
        );
        this.updateStepStatus(context, "validation", "completed");

        if (context.validation.isAlreadyProvisioned) {
          this.logger.log("Order already provisioned, skipping fulfillment", { sfOrderId });
          this.sideEffects.publishAlreadyProvisioned(sfOrderId, context.validation.whmcsOrderId);
          await this.sideEffects.invalidateCaches(
            sfOrderId,
            context.validation?.sfOrder?.AccountId
          );
          return context;
        }
      } catch (error) {
        this.updateStepStatus(context, "validation", "failed", extractErrorMessage(error));
        this.logger.error("Fulfillment validation failed", {
          sfOrderId,
          error: extractErrorMessage(error),
        });
        throw error;
      }

      // Step 2: Get order details (no rollback needed)
      try {
        const orderDetails = await this.orderOrchestrator.getOrder(sfOrderId);
        if (!orderDetails) {
          throw new OrderValidationException("Order details could not be retrieved.", {
            sfOrderId,
            idempotencyKey,
          });
        }
        // eslint-disable-next-line require-atomic-updates -- context is not shared across concurrent executions
        context.orderDetails = orderDetails;
      } catch (error) {
        this.logger.error("Failed to get order details", {
          sfOrderId,
          error: extractErrorMessage(error),
        });
        throw error;
      }

      // Step 3: Execute the main fulfillment workflow as a distributed transaction
      let mappingResult: WhmcsOrderItemMappingResult | undefined;
      let whmcsCreateResult: WhmcsOrderResult | undefined;

      const fulfillmentResult =
        await this.distributedTransactionService.executeDistributedTransaction(
          [
            {
              id: "sf_status_update",
              description: "Update Salesforce order status to Activating",
              execute: this.createTrackedStep(context, "sf_status_update", async () => {
                const result = await this.salesforceService.updateOrder({
                  Id: sfOrderId,
                  Activation_Status__c: "Activating",
                });
                this.sideEffects.publishActivating(sfOrderId);
                await this.sideEffects.notifyOrderApproved(
                  sfOrderId,
                  context.validation?.sfOrder?.AccountId
                );
                return result;
              }),
              rollback: async () => {
                await this.salesforceService.updateOrder({
                  Id: sfOrderId,
                  Activation_Status__c: "Failed",
                });
              },
              critical: true,
            },
            {
              id: "order_details",
              description: "Retain order details in context",
              execute: this.createTrackedStep(context, "order_details", async () =>
                Promise.resolve(context.orderDetails)
              ),
              critical: false,
            },
            {
              id: "mapping",
              description: "Map OrderItems to WHMCS format",
              execute: this.createTrackedStep(context, "mapping", async () => {
                if (!context.orderDetails) {
                  return Promise.reject(new Error("Order details are required for mapping"));
                }
                // Use domain mapper directly - single transformation!
                const result = mapOrderToWhmcsItems(context.orderDetails);
                mappingResult = result;

                this.logger.log("OrderItems mapped to WHMCS", {
                  totalItems: result.summary.totalItems,
                  serviceItems: result.summary.serviceItems,
                  activationItems: result.summary.activationItems,
                });

                return Promise.resolve(result);
              }),
              critical: true,
            },
            {
              id: "whmcs_create",
              description: "Create order in WHMCS",
              execute: this.createTrackedStep(context, "whmcs_create", async () => {
                if (!context.validation) {
                  throw new OrderValidationException("Validation context is missing", {
                    sfOrderId,
                    step: "whmcs_create_order",
                  });
                }
                if (!mappingResult) {
                  throw new FulfillmentException("Mapping result is not available", {
                    sfOrderId,
                    step: "whmcs_create_order",
                  });
                }

                const orderNotes = createOrderNotes(
                  sfOrderId,
                  `Provisioned from Salesforce Order ${sfOrderId}`
                );

                // Get OpportunityId from order details for WHMCS lifecycle linking
                const sfOpportunityId = context.orderDetails?.opportunityId;

                const result = await this.whmcsOrderService.addOrder({
                  clientId: context.validation.clientId,
                  items: mappingResult.whmcsItems,
                  paymentMethod: "stripe",
                  promoCode: "1st Month Free (Monthly Plan)",
                  sfOrderId,
                  sfOpportunityId, // Pass to WHMCS for bidirectional linking
                  notes: orderNotes,
                  noinvoiceemail: true,
                  noemail: true,
                });

                whmcsCreateResult = result;
                return result;
              }),
              rollback: async () => {
                if (whmcsCreateResult?.orderId) {
                  // Note: WHMCS doesn't have an automated cancel API
                  // Manual intervention required for order cleanup
                  this.logger.error(
                    "WHMCS order created but fulfillment failed - manual cleanup required",
                    {
                      orderId: whmcsCreateResult.orderId,
                      sfOrderId,
                      action: "MANUAL_CLEANUP_REQUIRED",
                    }
                  );
                }
                return Promise.resolve();
              },
              critical: true,
            },
            {
              id: "whmcs_accept",
              description: "Accept/provision order in WHMCS",
              execute: this.createTrackedStep(context, "whmcs_accept", async () => {
                if (!whmcsCreateResult?.orderId) {
                  throw new WhmcsOperationException(
                    "WHMCS order ID missing before acceptance step",
                    {
                      sfOrderId,
                      step: "whmcs_accept_order",
                    }
                  );
                }

                await this.whmcsOrderService.acceptOrder(whmcsCreateResult.orderId, sfOrderId);
                return { orderId: whmcsCreateResult.orderId };
              }),
              rollback: async () => {
                if (whmcsCreateResult?.orderId) {
                  // Note: WHMCS doesn't have an automated cancel API for accepted orders
                  // Manual intervention required for service termination
                  this.logger.error(
                    "WHMCS order accepted but fulfillment failed - manual cleanup required",
                    {
                      orderId: whmcsCreateResult.orderId,
                      serviceIds: whmcsCreateResult.serviceIds,
                      sfOrderId,
                      action: "MANUAL_SERVICE_TERMINATION_REQUIRED",
                    }
                  );
                }
                return Promise.resolve();
              },
              critical: true,
            },
            {
              id: "sim_fulfillment",
              description: "SIM-specific fulfillment (if applicable)",
              execute: this.createTrackedStep(context, "sim_fulfillment", async () => {
                if (context.orderDetails?.orderType === "SIM") {
                  const configurations = this.extractConfigurations(payload["configurations"]);
                  await this.simFulfillmentService.fulfillSimOrder({
                    orderDetails: context.orderDetails,
                    configurations,
                  });
                  return { completed: true as const };
                }
                return { skipped: true as const };
              }),
              critical: false, // SIM fulfillment failure shouldn't rollback the entire order
            },
            {
              id: "sf_success_update",
              description: "Update Salesforce with success",
              execute: this.createTrackedStep(context, "sf_success_update", async () => {
                const result = await this.salesforceService.updateOrder({
                  Id: sfOrderId,
                  Status: "Completed",
                  Activation_Status__c: "Activated",
                  WHMCS_Order_ID__c: whmcsCreateResult?.orderId?.toString(),
                });
                this.sideEffects.publishCompleted(
                  sfOrderId,
                  whmcsCreateResult?.orderId,
                  whmcsCreateResult?.serviceIds
                );
                await this.sideEffects.notifyOrderActivated(
                  sfOrderId,
                  context.validation?.sfOrder?.AccountId
                );
                return result;
              }),
              rollback: async () => {
                await this.salesforceService.updateOrder({
                  Id: sfOrderId,
                  Activation_Status__c: "Failed",
                });
              },
              critical: true,
            },
            {
              id: "opportunity_update",
              description: "Update Opportunity with WHMCS Service ID and Active stage",
              execute: this.createTrackedStep(context, "opportunity_update", async () => {
                const opportunityId = context.orderDetails?.opportunityId;
                const serviceId = whmcsCreateResult?.serviceIds?.[0];

                if (!opportunityId) {
                  this.logger.debug("No Opportunity linked to order, skipping update", {
                    sfOrderId,
                  });
                  return { skipped: true as const };
                }

                try {
                  // Update Opportunity stage to Active and set WHMCS Service ID
                  await this.opportunityService.updateStage(
                    opportunityId,
                    OPPORTUNITY_STAGE.ACTIVE,
                    "Service activated via fulfillment"
                  );

                  if (serviceId) {
                    await this.opportunityService.linkWhmcsServiceToOpportunity(
                      opportunityId,
                      serviceId
                    );
                  }

                  this.logger.log("Opportunity updated with Active stage and WHMCS link", {
                    opportunityIdTail: opportunityId.slice(-4),
                    whmcsServiceId: serviceId,
                    sfOrderId,
                  });

                  return { opportunityId, whmcsServiceId: serviceId };
                } catch (error) {
                  // Log but don't fail - Opportunity update is non-critical
                  this.logger.warn("Failed to update Opportunity after fulfillment", {
                    error: extractErrorMessage(error),
                    opportunityId,
                    sfOrderId,
                  });
                  return { failed: true as const, error: extractErrorMessage(error) };
                }
              }),
              critical: false, // Opportunity update failure shouldn't rollback fulfillment
            },
          ],
          {
            description: `Order fulfillment for ${sfOrderId}`,
            timeout: 300000, // 5 minutes
            continueOnNonCriticalFailure: true,
          }
        );

      if (!fulfillmentResult.success) {
        this.logger.error("Fulfillment transaction failed", {
          sfOrderId,
          error: fulfillmentResult.error,
          stepsExecuted: fulfillmentResult.stepsExecuted,
          stepsRolledBack: fulfillmentResult.stepsRolledBack,
        });
        throw new FulfillmentException(
          fulfillmentResult.error || "Fulfillment transaction failed",
          {
            sfOrderId,
            idempotencyKey,
            stepsExecuted: fulfillmentResult.stepsExecuted,
            stepsRolledBack: fulfillmentResult.stepsRolledBack,
          }
        );
      }

      // Update context with results
      // eslint-disable-next-line require-atomic-updates -- context is not shared across concurrent executions
      context.mappingResult = mappingResult;
      // eslint-disable-next-line require-atomic-updates -- context is not shared across concurrent executions
      context.whmcsResult = whmcsCreateResult;

      this.logger.log("Transactional fulfillment completed successfully", {
        sfOrderId,
        stepsExecuted: fulfillmentResult.stepsExecuted,
        duration: fulfillmentResult.duration,
      });

      await this.sideEffects.invalidateCaches(sfOrderId, context.validation?.sfOrder?.AccountId);
      return context;
    } catch (error) {
      await this.handleFulfillmentError(context, error as Error);
      await this.sideEffects.onFulfillmentFailed(
        sfOrderId,
        context.validation?.sfOrder?.AccountId,
        error instanceof Error ? error.message : String(error)
      );
      throw error;
    }
  }

  /**
   * Initialize fulfillment steps
   */
  private initializeSteps(orderType?: string): OrderFulfillmentStep[] {
    const steps: OrderFulfillmentStep[] = [
      { step: "validation", status: "pending" },
      { step: "sf_status_update", status: "pending" },
      { step: "order_details", status: "pending" },
      { step: "mapping", status: "pending" },
      { step: "whmcs_create", status: "pending" },
      { step: "whmcs_accept", status: "pending" },
    ];

    // Add SIM fulfillment step for SIM orders
    if (orderType === "SIM") {
      steps.push({ step: "sim_fulfillment", status: "pending" });
    }

    steps.push({ step: "sf_success_update", status: "pending" });
    return steps;
  }

  private extractConfigurations(rawConfigurations: unknown): Record<string, unknown> {
    if (rawConfigurations && typeof rawConfigurations === "object") {
      return { ...(rawConfigurations as Record<string, unknown>) };
    }
    return {};
  }

  /**
   * Handle fulfillment errors and update Salesforce
   */
  private async handleFulfillmentError(
    context: OrderFulfillmentContext,
    error: Error
  ): Promise<void> {
    const errorCode = this.orderFulfillmentErrorService.determineErrorCode(error);
    const userMessage = error.message;

    this.logger.error("Fulfillment orchestration failed", {
      sfOrderId: context.sfOrderId,
      idempotencyKey: context.idempotencyKey,
      error: error.message,
      errorCode,
      failedStep: context.steps.find((s: OrderFulfillmentStep) => s.status === "failed")?.step,
    });

    // Try to update Salesforce with failure status
    try {
      const updates: Record<string, unknown> = {
        Id: context.sfOrderId,
        // Set overall Order.Status to Pending Review for manual attention
        Status: "Pending Review",
        Activation_Status__c: "Failed",
        Activation_Error_Code__c: (
          this.orderFulfillmentErrorService.getShortCode(error) || String(errorCode)
        )
          .toString()
          .slice(0, 60),
        Activation_Error_Message__c: userMessage?.slice(0, 255),
      };

      await this.salesforceService.updateOrder(updates as { Id: string; [key: string]: unknown });

      this.logger.log("Salesforce updated with failure status", {
        sfOrderId: context.sfOrderId,
        errorCode,
      });
    } catch (updateError) {
      this.logger.error("Failed to update Salesforce with error status", {
        sfOrderId: context.sfOrderId,
        updateError: updateError instanceof Error ? updateError.message : String(updateError),
      });
    }
  }

  /**
   * Get fulfillment summary from context
   */
  getFulfillmentSummary(context: OrderFulfillmentContext): {
    success: boolean;
    status: "Already Fulfilled" | "Fulfilled" | "Failed";
    whmcsOrderId?: string | undefined;
    whmcsServiceIds?: number[] | undefined;
    message: string;
    steps: OrderFulfillmentStep[];
  } {
    const isSuccess = context.steps.every((s: OrderFulfillmentStep) => s.status === "completed");
    const failedStep = context.steps.find((s: OrderFulfillmentStep) => s.status === "failed");

    if (context.validation?.isAlreadyProvisioned) {
      const whmcsOrderId = context.validation.whmcsOrderId;
      return {
        success: true,
        status: "Already Fulfilled",
        ...(whmcsOrderId !== undefined && { whmcsOrderId }),
        message: "Order was already fulfilled in WHMCS",
        steps: context.steps,
      };
    }

    if (isSuccess) {
      const whmcsOrderId = context.whmcsResult?.orderId.toString();
      const whmcsServiceIds = context.whmcsResult?.serviceIds;
      return {
        success: true,
        status: "Fulfilled",
        ...(whmcsOrderId !== undefined && { whmcsOrderId }),
        ...(whmcsServiceIds !== undefined && { whmcsServiceIds }),
        message: "Order fulfilled successfully in WHMCS",
        steps: context.steps,
      };
    }

    return {
      success: false,
      status: "Failed",
      message: failedStep?.error ?? "Fulfillment failed",
      steps: context.steps,
    };
  }

  private updateStepStatus(
    context: OrderFulfillmentContext,
    stepName: string,
    status: OrderFulfillmentStep["status"],
    error?: string
  ): void {
    const step = context.steps.find(s => s.step === stepName);
    if (!step) return;

    const timestamp = new Date();
    if (status === "in_progress") {
      step.status = "in_progress";
      step.startedAt = timestamp;
      delete step.error;
      return;
    }

    step.status = status;
    step.completedAt = timestamp;
    if (status === "failed" && error !== undefined) {
      step.error = error;
    } else {
      delete step.error;
    }
  }

  private createTrackedStep<TResult>(
    context: OrderFulfillmentContext,
    stepName: string,
    executor: () => Promise<TResult>
  ): () => Promise<TResult> {
    return async () => {
      this.updateStepStatus(context, stepName, "in_progress");
      try {
        const result = await executor();
        this.updateStepStatus(context, stepName, "completed");
        return result;
      } catch (error) {
        this.updateStepStatus(context, stepName, "failed", extractErrorMessage(error));
        throw error;
      }
    };
  }
}
