import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.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 { FulfillmentStepFactory } from "./fulfillment-step-factory.service.js";
import { FulfillmentSideEffectsService } from "./fulfillment-side-effects.service.js";
import type { SimFulfillmentResult } from "./sim-fulfillment.service.js";
import type { FulfillmentPayload } from "./fulfillment-context-mapper.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 { mapOrderToWhmcsItems } from "@customer-portal/domain/orders/providers";
import {
  OrderValidationException,
  FulfillmentException,
} 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;
  completedAt?: Date;
  error?: string;
}

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

/**
 * Orchestrates the complete order fulfillment workflow
 *
 * This is a thin coordinator that delegates to specialized services:
 * - FulfillmentStepFactory: Builds the step array
 * - FulfillmentStepExecutors: Contains step execution logic
 * - FulfillmentSideEffectsService: Handles events, notifications, cache
 */
@Injectable()
export class OrderFulfillmentOrchestrator {
  constructor(
    @Inject(Logger) private readonly logger: Logger,
    private readonly salesforceFacade: SalesforceFacade,
    private readonly orderOrchestrator: OrderOrchestrator,
    private readonly orderFulfillmentValidator: OrderFulfillmentValidator,
    private readonly orderFulfillmentErrorService: OrderFulfillmentErrorService,
    private readonly stepFactory: FulfillmentStepFactory,
    private readonly sideEffects: FulfillmentSideEffectsService,
    private readonly distributedTransactionService: DistributedTransactionService
  ) {}

  /**
   * Execute complete fulfillment workflow with distributed transaction support
   */
  async executeFulfillment(
    sfOrderId: string,
    payload: FulfillmentPayload,
    idempotencyKey: string
  ): Promise<OrderFulfillmentContext> {
    const context = this.initializeContext(sfOrderId, idempotencyKey, payload);

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

    try {
      // Step 1: Validation (no rollback needed)
      await this.executeValidation(context, sfOrderId, idempotencyKey);
      if (context.validation?.isAlreadyProvisioned) {
        return this.handleAlreadyProvisioned(context, sfOrderId);
      }

      // Step 2: Get order details (no rollback needed)
      await this.fetchOrderDetails(context, sfOrderId, idempotencyKey);

      // Step 3: Build and execute the fulfillment steps
      const steps = this.stepFactory.buildSteps(context, payload);
      const fulfillmentResult =
        await this.distributedTransactionService.executeDistributedTransaction(steps, {
          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,
          }
        );
      }

      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, sfOrderId, error as Error);
      throw error;
    }
  }

  /**
   * 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 => s.status === "completed");
    const failedStep = context.steps.find(s => s.status === "failed");

    if (context.validation?.isAlreadyProvisioned) {
      const result: {
        success: boolean;
        status: "Already Fulfilled" | "Fulfilled" | "Failed";
        whmcsOrderId?: string;
        whmcsServiceIds?: number[];
        message: string;
        steps: OrderFulfillmentStep[];
      } = {
        success: true,
        status: "Already Fulfilled",
        message: "Order was already fulfilled in WHMCS",
        steps: context.steps,
      };
      if (context.validation.whmcsOrderId) {
        result.whmcsOrderId = context.validation.whmcsOrderId;
      }
      return result;
    }

    if (isSuccess) {
      const result: {
        success: boolean;
        status: "Already Fulfilled" | "Fulfilled" | "Failed";
        whmcsOrderId?: string;
        whmcsServiceIds?: number[];
        message: string;
        steps: OrderFulfillmentStep[];
      } = {
        success: true,
        status: "Fulfilled",
        message: "Order fulfilled successfully in WHMCS",
        steps: context.steps,
      };
      if (context.whmcsResult?.orderId !== undefined) {
        result.whmcsOrderId = context.whmcsResult.orderId.toString();
      }
      if (context.whmcsResult?.serviceIds) {
        result.whmcsServiceIds = context.whmcsResult.serviceIds;
      }
      return result;
    }

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

  // ===========================================================================
  // Private Helpers
  // ===========================================================================

  private initializeContext(
    sfOrderId: string,
    idempotencyKey: string,
    payload: FulfillmentPayload
  ): OrderFulfillmentContext {
    const orderType = typeof payload["orderType"] === "string" ? payload["orderType"] : "Unknown";
    return {
      sfOrderId,
      idempotencyKey,
      validation: null,
      steps: this.initializeSteps(orderType),
    };
  }

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

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

    // WHMCS steps come after SIM activation
    steps.push(
      { step: "mapping", status: "pending" },
      { step: "whmcs_create", status: "pending" },
      { step: "whmcs_accept", status: "pending" },
      { step: "whmcs_custom_fields", status: "pending" },
      { step: "sf_registration_complete", status: "pending" },
      { step: "opportunity_update", status: "pending" }
    );

    return steps;
  }

  private async executeValidation(
    context: OrderFulfillmentContext,
    sfOrderId: string,
    idempotencyKey: string
  ): Promise<void> {
    this.updateStepStatus(context, "validation", "in_progress");
    try {
      const validationResult = await this.orderFulfillmentValidator.validateFulfillmentRequest(
        sfOrderId,
        idempotencyKey
      );
      // eslint-disable-next-line require-atomic-updates -- Sequential execution, context is local to this call
      context.validation = validationResult;
      this.updateStepStatus(context, "validation", "completed");
    } catch (error) {
      this.updateStepStatus(context, "validation", "failed", extractErrorMessage(error));
      this.logger.error("Fulfillment validation failed", {
        sfOrderId,
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  private handleAlreadyProvisioned(
    context: OrderFulfillmentContext,
    sfOrderId: string
  ): OrderFulfillmentContext {
    this.logger.log("Order already provisioned, skipping fulfillment", { sfOrderId });
    this.sideEffects.publishAlreadyProvisioned(sfOrderId, context.validation?.whmcsOrderId);
    void this.sideEffects.invalidateCaches(sfOrderId, context.validation?.sfOrder?.AccountId);
    return context;
  }

  private async fetchOrderDetails(
    context: OrderFulfillmentContext,
    sfOrderId: string,
    idempotencyKey: string
  ): Promise<void> {
    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: extractErrorMessage(error),
      });
      throw error;
    }
  }

  private async handleFulfillmentError(
    context: OrderFulfillmentContext,
    sfOrderId: string,
    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 => s.status === "failed")?.step,
    });

    // Update Salesforce with failure status
    try {
      await this.salesforceFacade.updateOrder({
        Id: sfOrderId,
        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),
      });
      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),
      });
    }

    // Publish failure events and notifications
    await this.sideEffects.onFulfillmentFailed(
      sfOrderId,
      context.validation?.sfOrder?.AccountId,
      error.message
    );
  }

  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) {
      step.error = error;
    } else {
      delete step.error;
    }
  }
}
