import { Injectable } from "@nestjs/common";
import type { DistributedStep } from "@bff/infra/database/services/distributed-transaction.service.js";
import type { WhmcsOrderResult } from "@bff/integrations/whmcs/services/whmcs-order.service.js";
import type { SimFulfillmentResult } from "./sim-fulfillment.service.js";
import { FulfillmentStepExecutors } from "./fulfillment-step-executors.service.js";
import type {
  OrderFulfillmentContext,
  OrderFulfillmentStep,
} from "./order-fulfillment-orchestrator.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { mapOrderToWhmcsItems } from "@customer-portal/domain/orders/providers";
import { FulfillmentException } from "@bff/core/exceptions/domain-exceptions.js";
import type { FulfillmentPayload } from "./fulfillment-context-mapper.service.js";

type WhmcsOrderItemMappingResult = ReturnType<typeof mapOrderToWhmcsItems>;

/**
 * Mutable state container for passing results between steps
 */
interface StepState {
  simFulfillmentResult?: SimFulfillmentResult;
  mappingResult?: WhmcsOrderItemMappingResult;
  whmcsCreateResult?: WhmcsOrderResult;
}

/**
 * Fulfillment Step Factory Service
 *
 * Builds the array of DistributedStep for the fulfillment workflow.
 * Conditionally includes SIM steps based on order type.
 */
@Injectable()
export class FulfillmentStepFactory {
  constructor(private readonly executors: FulfillmentStepExecutors) {}

  /**
   * Build the ordered list of fulfillment steps
   *
   * Step order:
   * 1. sf_status_update (Activating)
   * 2. order_details (retain in context)
   * 3. sim_fulfillment (SIM orders only - PA05-19/PA02-01 + PA05-05)
   * 4. sf_activated_update (SIM orders only)
   * 5. mapping (with SIM data for WHMCS)
   * 6. whmcs_create
   * 7. whmcs_accept
   * 8. sf_registration_complete
   * 9. opportunity_update
   */
  buildSteps(context: OrderFulfillmentContext, payload: FulfillmentPayload): DistributedStep[] {
    // Mutable state container for cross-step data
    const state: StepState = {};

    const steps: DistributedStep[] = [];

    // Step 1: SF Status Update
    steps.push(this.createSfStatusUpdateStep(context));

    // Step 2: Order Details (retain in context)
    steps.push(this.createOrderDetailsStep(context));

    // Steps 3-4: SIM fulfillment (only for SIM orders)
    // Note: orderDetails may not be available yet, so we check orderType from validation
    const orderType = context.orderDetails?.orderType ?? context.validation?.sfOrder?.Type;
    if (orderType === "SIM") {
      steps.push(this.createSimFulfillmentStep(context, payload, state));
      steps.push(this.createSfActivatedUpdateStep(context, state));
    }

    // Steps 5-10: WHMCS and completion
    steps.push(this.createMappingStep(context, state));
    steps.push(this.createWhmcsCreateStep(context, state));
    steps.push(this.createWhmcsAcceptStep(context, state));
    steps.push(this.createWhmcsCustomFieldsUpdateStep(context, state));
    steps.push(this.createSfRegistrationCompleteStep(context, state));
    steps.push(this.createOpportunityUpdateStep(context, state));

    return steps;
  }

  private createSfStatusUpdateStep(ctx: OrderFulfillmentContext): DistributedStep {
    return {
      id: "sf_status_update",
      description: "Update Salesforce order status to Activating",
      execute: this.createTrackedStep(ctx, "sf_status_update", async () => {
        return this.executors.executeSfStatusUpdate(ctx);
      }),
      rollback: async () => {
        await this.executors.rollbackSfStatus(ctx);
      },
      critical: true,
    };
  }

  private createOrderDetailsStep(ctx: OrderFulfillmentContext): DistributedStep {
    return {
      id: "order_details",
      description: "Retain order details in context",
      execute: this.createTrackedStep(ctx, "order_details", async () =>
        Promise.resolve(ctx.orderDetails)
      ),
      critical: false,
    };
  }

  private createSimFulfillmentStep(
    ctx: OrderFulfillmentContext,
    payload: FulfillmentPayload,
    state: StepState
  ): DistributedStep {
    return {
      id: "sim_fulfillment",
      description: "SIM activation via Freebit (PA05-19/PA02-01 + PA05-05)",
      execute: this.createTrackedStep(ctx, "sim_fulfillment", async () => {
        const result = await this.executors.executeSimFulfillment(ctx, payload);
        state.simFulfillmentResult = result;
        // eslint-disable-next-line require-atomic-updates -- Sequential step execution, context is scoped to this transaction
        ctx.simFulfillmentResult = result;
        return result;
      }),
      // eslint-disable-next-line @typescript-eslint/promise-function-async -- Returns Promise from executor, no async work needed
      rollback: () => this.executors.rollbackSimFulfillment(ctx, state.simFulfillmentResult),
      critical: ctx.validation?.sfOrder?.SIM_Type__c === "Physical SIM",
    };
  }

  private createSfActivatedUpdateStep(
    ctx: OrderFulfillmentContext,
    state: StepState
  ): DistributedStep {
    return {
      id: "sf_activated_update",
      description: "Update Salesforce Activation_Status to Activated",
      execute: this.createTrackedStep(ctx, "sf_activated_update", async () => {
        return this.executors.executeSfActivatedUpdate(ctx, state.simFulfillmentResult);
      }),
      rollback: async () => {
        await this.executors.rollbackSfStatus(ctx);
      },
      critical: false,
    };
  }

  private createMappingStep(ctx: OrderFulfillmentContext, state: StepState): DistributedStep {
    return {
      id: "mapping",
      description: "Map OrderItems to WHMCS format with SIM data",
      // eslint-disable-next-line @typescript-eslint/promise-function-async -- Synchronous mapping wrapped in Promise for interface
      execute: this.createTrackedStep(ctx, "mapping", () => {
        const result = this.executors.executeMapping(ctx, state.simFulfillmentResult);
        state.mappingResult = result;
        ctx.mappingResult = result;
        return Promise.resolve(result);
      }),
      critical: true,
    };
  }

  private createWhmcsCreateStep(ctx: OrderFulfillmentContext, state: StepState): DistributedStep {
    return {
      id: "whmcs_create",
      description: "Create order in WHMCS",
      execute: this.createTrackedStep(ctx, "whmcs_create", async () => {
        if (!state.mappingResult) {
          throw new FulfillmentException("Mapping result is not available");
        }
        const result = await this.executors.executeWhmcsCreate(ctx, state.mappingResult);
        // eslint-disable-next-line require-atomic-updates -- Sequential step execution, state is scoped to this transaction
        state.whmcsCreateResult = result;
        // eslint-disable-next-line require-atomic-updates -- Sequential step execution, context is scoped to this transaction
        ctx.whmcsResult = result;
        return result;
      }),
      // eslint-disable-next-line @typescript-eslint/promise-function-async -- Returns Promise from executor, no async work needed
      rollback: () => this.executors.rollbackWhmcsCreate(ctx, state.whmcsCreateResult),
      critical: true,
    };
  }

  private createWhmcsAcceptStep(ctx: OrderFulfillmentContext, state: StepState): DistributedStep {
    return {
      id: "whmcs_accept",
      description: "Accept/provision order in WHMCS",
      execute: this.createTrackedStep(ctx, "whmcs_accept", async () => {
        if (!state.whmcsCreateResult) {
          throw new FulfillmentException("WHMCS create result is not available");
        }
        const acceptResult = await this.executors.executeWhmcsAccept(ctx, state.whmcsCreateResult);
        // Update state with serviceIds from accept (services are created on accept, not on add)
        if (acceptResult.serviceIds.length > 0 && state.whmcsCreateResult) {
          state.whmcsCreateResult = {
            ...state.whmcsCreateResult,
            serviceIds: acceptResult.serviceIds,
          };
        }
        return acceptResult;
      }),
      // eslint-disable-next-line @typescript-eslint/promise-function-async -- Returns Promise from executor, no async work needed
      rollback: () => this.executors.rollbackWhmcsAccept(ctx, state.whmcsCreateResult),
      critical: true,
    };
  }

  private createWhmcsCustomFieldsUpdateStep(
    ctx: OrderFulfillmentContext,
    state: StepState
  ): DistributedStep {
    return {
      id: "whmcs_custom_fields",
      description: "Update WHMCS service custom fields (SIM Number, Serial Number, EID)",
      execute: this.createTrackedStep(ctx, "whmcs_custom_fields", async () => {
        return this.executors.executeWhmcsCustomFieldsUpdate(
          ctx,
          state.whmcsCreateResult,
          state.simFulfillmentResult
        );
      }),
      critical: false, // Custom fields update failure shouldn't rollback fulfillment
    };
  }

  private createSfRegistrationCompleteStep(
    ctx: OrderFulfillmentContext,
    state: StepState
  ): DistributedStep {
    return {
      id: "sf_registration_complete",
      description: "Update Salesforce with WHMCS registration info",
      execute: this.createTrackedStep(ctx, "sf_registration_complete", async () => {
        return this.executors.executeSfRegistrationComplete(
          ctx,
          state.whmcsCreateResult,
          state.simFulfillmentResult
        );
      }),
      rollback: async () => {
        await this.executors.rollbackSfStatus(ctx);
      },
      critical: true,
    };
  }

  private createOpportunityUpdateStep(
    ctx: OrderFulfillmentContext,
    state: StepState
  ): DistributedStep {
    return {
      id: "opportunity_update",
      description: "Update Opportunity with WHMCS Service ID and Active stage",
      execute: this.createTrackedStep(ctx, "opportunity_update", async () => {
        return this.executors.executeOpportunityUpdate(ctx, state.whmcsCreateResult);
      }),
      critical: false, // Opportunity update failure shouldn't rollback fulfillment
    };
  }

  /**
   * Wrap step executor with status tracking
   */
  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;
      }
    };
  }

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