import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { SalesforceService } from "@bff/integrations/salesforce/salesforce.service";
import {
  WhmcsOrderService,
  WhmcsOrderResult,
} from "@bff/integrations/whmcs/services/whmcs-order.service";
import { OrderOrchestrator } from "./order-orchestrator.service";
import { OrderFulfillmentValidator } from "./order-fulfillment-validator.service";
import { OrderFulfillmentErrorService } from "./order-fulfillment-error.service";
import { SimFulfillmentService } from "./sim-fulfillment.service";
import { DistributedTransactionService } from "@bff/core/database/services/distributed-transaction.service";
import { getErrorMessage } from "@bff/core/utils/error.util";
import { OrderEventsService } from "./order-events.service";
import { OrdersCacheService } from "./orders-cache.service";
import {
  type OrderDetails,
  type OrderFulfillmentValidationResult,
  Providers as OrderProviders,
} from "@customer-portal/domain/orders";
import {
  OrderValidationException,
  FulfillmentException,
  WhmcsOperationException,
} from "@bff/core/exceptions/domain-exceptions";

type WhmcsOrderItemMappingResult = ReturnType<typeof OrderProviders.Whmcs.mapOrderToWhmcsItems>;

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

export interface OrderFulfillmentContext {
  sfOrderId: string;
  idempotencyKey: string;
  validation: OrderFulfillmentValidationResult | null;
  orderDetails?: OrderDetails;
  mappingResult?: WhmcsOrderItemMappingResult;
  whmcsResult?: WhmcsOrderResult;
  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: SalesforceService,
    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 orderEvents: OrderEventsService,
    private readonly ordersCache: OrdersCacheService
  ) {}

  /**
   * 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 {
        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.orderEvents.publish(sfOrderId, {
            orderId: sfOrderId,
            status: "Completed",
            activationStatus: "Activated",
            stage: "completed",
            source: "fulfillment",
            message: "Order already provisioned",
            timestamp: new Date().toISOString(),
            payload: {
              whmcsOrderId: context.validation.whmcsOrderId,
            },
          });
          await this.invalidateOrderCaches(sfOrderId, context.validation?.sfOrder?.AccountId);
          return context;
        }
      } catch (error) {
        this.updateStepStatus(context, "validation", "failed", getErrorMessage(error));
        this.logger.error("Fulfillment validation failed", {
          sfOrderId,
          error: getErrorMessage(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,
          });
        }
        context.orderDetails = orderDetails;
      } catch (error) {
        this.logger.error("Failed to get order details", {
          sfOrderId,
          error: getErrorMessage(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.orderEvents.publish(sfOrderId, {
                  orderId: sfOrderId,
                  status: "Processing",
                  activationStatus: "Activating",
                  stage: "in_progress",
                  source: "fulfillment",
                  timestamp: new Date().toISOString(),
                });
                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", () =>
                Promise.resolve(context.orderDetails)
              ),
              critical: false,
            },
            {
              id: "mapping",
              description: "Map OrderItems to WHMCS format",
              execute: this.createTrackedStep(context, "mapping", () => {
                if (!context.orderDetails) {
                  return Promise.reject(new Error("Order details are required for mapping"));
                }
                // Use domain mapper directly - single transformation!
                const result = OrderProviders.Whmcs.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 = OrderProviders.Whmcs.createOrderNotes(
                  sfOrderId,
                  `Provisioned from Salesforce Order ${sfOrderId}`
                );

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

                whmcsCreateResult = result;
                return result;
              }),
              rollback: () => {
                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: () => {
                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.orderEvents.publish(sfOrderId, {
                  orderId: sfOrderId,
                  status: "Completed",
                  activationStatus: "Activated",
                  stage: "completed",
                  source: "fulfillment",
                  timestamp: new Date().toISOString(),
                  payload: {
                    whmcsOrderId: whmcsCreateResult?.orderId,
                    whmcsServiceIds: whmcsCreateResult?.serviceIds,
                  },
                });
                return result;
              }),
              rollback: async () => {
                await this.salesforceService.updateOrder({
                  Id: sfOrderId,
                  Activation_Status__c: "Failed",
                });
              },
              critical: true,
            },
          ],
          {
            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
      context.mappingResult = mappingResult;
      context.whmcsResult = whmcsCreateResult;

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

      await this.invalidateOrderCaches(sfOrderId, context.validation?.sfOrder?.AccountId);
      return context;
    } catch (error) {
      await this.invalidateOrderCaches(sfOrderId, context.validation?.sfOrder?.AccountId);
      await this.handleFulfillmentError(context, error as Error);
      this.orderEvents.publish(sfOrderId, {
        orderId: sfOrderId,
        status: "Pending Review",
        activationStatus: "Failed",
        stage: "failed",
        source: "fulfillment",
        timestamp: new Date().toISOString(),
        reason: 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 {};
  }

  private async invalidateOrderCaches(orderId: string, accountId?: string | null): Promise<void> {
    const tasks: Array<Promise<unknown>> = [this.ordersCache.invalidateOrder(orderId)];
    if (accountId) {
      tasks.push(this.ordersCache.invalidateAccountOrders(accountId));
    }

    try {
      await Promise.all(tasks);
    } catch (error) {
      this.logger.warn("Failed to invalidate order caches", {
        orderId,
        accountId: accountId ?? undefined,
        error: getErrorMessage(error),
      });
    }
  }

  /**
   * 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()
          .substring(0, 60),
        Activation_Error_Message__c: userMessage?.substring(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;
    whmcsServiceIds?: number[];
    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) {
      return {
        success: true,
        status: "Already Fulfilled",
        whmcsOrderId: context.validation.whmcsOrderId,
        message: "Order was already fulfilled in WHMCS",
        steps: context.steps,
      };
    }

    if (isSuccess) {
      return {
        success: true,
        status: "Fulfilled",
        whmcsOrderId: context.whmcsResult?.orderId.toString(),
        whmcsServiceIds: context.whmcsResult?.serviceIds,
        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;
      step.error = undefined;
      return;
    }

    step.status = status;
    step.completedAt = timestamp;
    step.error = status === "failed" ? error : undefined;
  }

  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", getErrorMessage(error));
        throw error;
      }
    };
  }
}
