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 { SimFulfillmentService, type SimFulfillmentResult } from "./sim-fulfillment.service.js";
import { FulfillmentSideEffectsService } from "./fulfillment-side-effects.service.js";
import {
  FulfillmentContextMapper,
  type FulfillmentPayload,
} from "./fulfillment-context-mapper.service.js";
import type { OrderFulfillmentContext } from "./order-fulfillment-orchestrator.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
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>;

/**
 * Execute result containers for step data passing
 */
export interface StepExecutionState {
  simFulfillmentResult?: SimFulfillmentResult;
  mappingResult?: WhmcsOrderItemMappingResult;
  whmcsCreateResult?: WhmcsOrderResult;
}

/**
 * Fulfillment Step Executors Service
 *
 * Contains the actual execution logic for each fulfillment step.
 * Extracted from OrderFulfillmentOrchestrator to keep it thin.
 */
@Injectable()
export class FulfillmentStepExecutors {
  constructor(
    @Inject(Logger) private readonly logger: Logger,
    private readonly salesforceFacade: SalesforceFacade,
    private readonly opportunityService: SalesforceOpportunityService,
    private readonly whmcsOrderService: WhmcsOrderService,
    private readonly simFulfillmentService: SimFulfillmentService,
    private readonly sideEffects: FulfillmentSideEffectsService,
    private readonly contextMapper: FulfillmentContextMapper
  ) {}

  // ============================================
  // Execute Methods
  // ============================================

  /**
   * Update Salesforce order status to "Activating"
   */
  async executeSfStatusUpdate(ctx: OrderFulfillmentContext): Promise<void> {
    const result = await this.salesforceFacade.updateOrder({
      Id: ctx.sfOrderId,
      Activation_Status__c: "Activating",
    });
    this.sideEffects.publishActivating(ctx.sfOrderId);
    await this.sideEffects.notifyOrderApproved(ctx.sfOrderId, ctx.validation?.sfOrder?.AccountId);
    return result;
  }

  /**
   * SIM fulfillment via Freebit (PA05-19/PA02-01 + PA05-05)
   */
  async executeSimFulfillment(
    ctx: OrderFulfillmentContext,
    payload: FulfillmentPayload
  ): Promise<SimFulfillmentResult> {
    if (ctx.orderDetails?.orderType !== "SIM") {
      return { activated: false, simType: "eSIM" as const };
    }

    const sfOrder = ctx.validation?.sfOrder;
    const configurations = this.contextMapper.extractConfigurations(
      payload["configurations"],
      sfOrder
    );
    const assignedPhysicalSimId =
      typeof sfOrder?.Assign_Physical_SIM__c === "string"
        ? sfOrder.Assign_Physical_SIM__c
        : undefined;

    // Extract voice options from SF order
    const voiceMailEnabled = sfOrder?.SIM_Voice_Mail__c === true;
    const callWaitingEnabled = sfOrder?.SIM_Call_Waiting__c === true;

    // Extract contact identity from porting fields (for PA05-05)
    const contactIdentity = this.contextMapper.extractContactIdentity(sfOrder);

    this.logger.log("Starting SIM fulfillment (before WHMCS)", {
      orderId: ctx.sfOrderId,
      simType: sfOrder?.SIM_Type__c,
      assignedPhysicalSimId,
      voiceMailEnabled,
      callWaitingEnabled,
      hasContactIdentity: !!contactIdentity,
      // MNP debug: log what made it into the configurations
      mnpInConfig: {
        isMnp: configurations["isMnp"] ?? "not-set",
        mnpNumber: configurations["mnpNumber"] ? "present" : "missing",
        mnpExpiry: configurations["mnpExpiry"] ? "present" : "missing",
        mnpPhone: configurations["mnpPhone"] ? "present" : "missing",
        portingFirstName: configurations["portingFirstName"] ? "present" : "missing",
        portingLastName: configurations["portingLastName"] ? "present" : "missing",
        portingGender: configurations["portingGender"] ?? "not-set",
        portingDateOfBirth: configurations["portingDateOfBirth"] ? "present" : "missing",
      },
    });

    // Build assignment details for SIM Inventory record (only include defined properties)
    const assignmentDetails: {
      accountId?: string;
      orderId?: string;
      simType?: string;
    } = {
      orderId: ctx.sfOrderId,
    };
    if (ctx.validation?.sfOrder?.AccountId) {
      assignmentDetails.accountId = ctx.validation.sfOrder.AccountId;
    }
    if (ctx.validation?.sfOrder?.SIM_Type__c) {
      assignmentDetails.simType = ctx.validation.sfOrder.SIM_Type__c;
    }

    // Build request with only defined optional properties
    const request: Parameters<typeof this.simFulfillmentService.fulfillSimOrder>[0] = {
      orderDetails: ctx.orderDetails,
      configurations,
      voiceMailEnabled,
      callWaitingEnabled,
      assignmentDetails,
    };
    if (assignedPhysicalSimId) {
      request.assignedPhysicalSimId = assignedPhysicalSimId;
    }
    if (contactIdentity) {
      request.contactIdentity = contactIdentity;
    }

    const result = await this.simFulfillmentService.fulfillSimOrder(request);

    return result;
  }

  /**
   * Update Salesforce Activation_Status__c after SIM fulfillment
   * Note: Status is NOT changed here to avoid locking the Order before WHMCS setup
   */
  async executeSfActivatedUpdate(
    ctx: OrderFulfillmentContext,
    simFulfillmentResult?: SimFulfillmentResult
  ): Promise<{ skipped?: true } | void> {
    if (ctx.orderDetails?.orderType !== "SIM" || !simFulfillmentResult?.activated) {
      return { skipped: true };
    }

    // Only update Activation_Status__c - Status will be set in sf_registration_complete
    // to avoid locking the Order before WHMCS info can be written
    const result = await this.salesforceFacade.updateOrder({
      Id: ctx.sfOrderId,
      Activation_Status__c: "Activated",
    });
    this.sideEffects.publishStatusUpdate(ctx.sfOrderId, {
      status: ctx.validation?.sfOrder?.Status ?? "Pending",
      activationStatus: "Activated",
      stage: "in_progress",
      source: "fulfillment",
      message: "SIM activated, proceeding to billing setup",
      timestamp: new Date().toISOString(),
    });
    return result;
  }

  /**
   * Map OrderItems to WHMCS format with SIM data
   */
  executeMapping(
    ctx: OrderFulfillmentContext,
    simFulfillmentResult?: SimFulfillmentResult
  ): WhmcsOrderItemMappingResult {
    if (!ctx.orderDetails) {
      throw new FulfillmentException("Order details are required for mapping");
    }

    // Use domain mapper directly - single transformation!
    const result = mapOrderToWhmcsItems(ctx.orderDetails);

    // Add SIM data if we have it (phone number goes to domain field and custom fields)
    if (simFulfillmentResult?.activated && simFulfillmentResult.phoneNumber) {
      for (const item of result.whmcsItems) {
        // Set phone number as domain (shows in WHMCS Domain field)
        item.domain = simFulfillmentResult.phoneNumber!;
        // Also add to custom fields for SIM Number field
        // Note: Field names must match exactly as configured in WHMCS (with spaces)
        item.customFields = {
          ...item.customFields,
          "SIM Number": simFulfillmentResult.phoneNumber,
          ...(simFulfillmentResult.serialNumber && {
            "Serial Number": simFulfillmentResult.serialNumber,
          }),
          ...(simFulfillmentResult.eid && {
            EID: simFulfillmentResult.eid,
          }),
        };
      }
    }

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

    return result;
  }

  /**
   * Create order in WHMCS
   */
  async executeWhmcsCreate(
    ctx: OrderFulfillmentContext,
    mappingResult: WhmcsOrderItemMappingResult
  ): Promise<WhmcsOrderResult> {
    if (!ctx.validation) {
      throw new OrderValidationException("Validation context is missing", {
        sfOrderId: ctx.sfOrderId,
        step: "whmcs_create_order",
      });
    }
    if (!mappingResult) {
      throw new FulfillmentException("Mapping result is not available", {
        sfOrderId: ctx.sfOrderId,
        step: "whmcs_create_order",
      });
    }

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

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

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

    return result;
  }

  /**
   * Accept/provision order in WHMCS
   */
  async executeWhmcsAccept(
    ctx: OrderFulfillmentContext,
    whmcsCreateResult: WhmcsOrderResult
  ): Promise<{ orderId: number; serviceIds: number[] }> {
    if (!whmcsCreateResult?.orderId) {
      throw new WhmcsOperationException("WHMCS order ID missing before acceptance step", {
        sfOrderId: ctx.sfOrderId,
        step: "whmcs_accept_order",
      });
    }

    const acceptResult = await this.whmcsOrderService.acceptOrder(
      whmcsCreateResult.orderId,
      ctx.sfOrderId
    );

    // Return both orderId and serviceIds from AcceptOrder
    // Note: Services are created on accept, not on add
    const orderId = whmcsCreateResult.orderId;
    const serviceIds = acceptResult.serviceIds.length > 0 ? acceptResult.serviceIds : [];

    return { orderId, serviceIds };
  }

  /**
   * Update WHMCS service custom fields after order is accepted
   * This is necessary because AddOrder expects field IDs, but we use field names
   */
  async executeWhmcsCustomFieldsUpdate(
    ctx: OrderFulfillmentContext,
    whmcsCreateResult?: WhmcsOrderResult,
    simFulfillmentResult?: SimFulfillmentResult
  ): Promise<{ updated: boolean; serviceId?: number }> {
    const serviceId = whmcsCreateResult?.serviceIds?.[0];

    if (!serviceId) {
      this.logger.debug("No WHMCS service ID available for custom fields update", {
        sfOrderId: ctx.sfOrderId,
      });
      return { updated: false };
    }

    // Build custom fields from SIM fulfillment result
    const customFields: Record<string, string> = {};

    if (simFulfillmentResult?.phoneNumber) {
      customFields["SIM Number"] = simFulfillmentResult.phoneNumber;
    }
    if (simFulfillmentResult?.serialNumber) {
      customFields["Serial Number"] = simFulfillmentResult.serialNumber;
    }
    if (simFulfillmentResult?.eid) {
      customFields["EID"] = simFulfillmentResult.eid;
    }

    if (Object.keys(customFields).length === 0) {
      this.logger.debug("No custom fields to update for WHMCS service", {
        sfOrderId: ctx.sfOrderId,
        serviceId,
      });
      return { updated: false, serviceId };
    }

    this.logger.log("Updating WHMCS service custom fields", {
      sfOrderId: ctx.sfOrderId,
      serviceId,
      fieldNames: Object.keys(customFields),
    });

    await this.whmcsOrderService.updateServiceCustomFields(serviceId, customFields);

    return { updated: true, serviceId };
  }

  /**
   * Update Salesforce with WHMCS registration info
   * This is the final step that sets Status to "Processed"
   *
   * If the order is already in an activated status (e.g. auto-approved by SF Flow),
   * the integration user may lack "Edit Activated Orders" permission. In that case,
   * we retry with only custom field updates (no Status change).
   */
  async executeSfRegistrationComplete(
    ctx: OrderFulfillmentContext,
    whmcsCreateResult?: WhmcsOrderResult,
    // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Parameter kept for interface consistency
    _simFulfillmentResult?: SimFulfillmentResult
  ): Promise<void> {
    // Set Status to "Processed" and update WHMCS info
    // Status is set here (not earlier) to avoid locking the Order before WHMCS data is written
    const updatePayload: { Id: string; [key: string]: unknown } = {
      Id: ctx.sfOrderId,
      Status: "Processed",
      Activation_Status__c: "Activated",
      WHMCS_Order_ID__c: whmcsCreateResult?.orderId?.toString(),
    };

    try {
      await this.salesforceFacade.updateOrder(updatePayload);
    } catch (error) {
      // SF returns permission error when order is already in an activated status
      // (e.g. auto-approved by a Flow). Retry with only custom fields, no Status change.
      const msg = error instanceof Error ? error.message : String(error);
      if (
        msg.includes("有効注文") ||
        msg.includes("Edit Activated Orders") ||
        msg.includes("FIELD_INTEGRITY_EXCEPTION")
      ) {
        this.logger.warn("SF order already activated, retrying without Status change", {
          sfOrderId: ctx.sfOrderId,
          originalError: msg,
        });
        await this.salesforceFacade.updateOrder({
          Id: ctx.sfOrderId,
          Activation_Status__c: "Activated",
          WHMCS_Order_ID__c: whmcsCreateResult?.orderId?.toString(),
        });
      } else {
        throw error;
      }
    }

    this.sideEffects.publishStatusUpdate(ctx.sfOrderId, {
      status: "Processed",
      activationStatus: "Activated",
      stage: "completed",
      source: "fulfillment",
      timestamp: new Date().toISOString(),
      payload: {
        whmcsOrderId: whmcsCreateResult?.orderId,
        whmcsServiceIds: whmcsCreateResult?.serviceIds,
        simPhoneNumber: ctx.simFulfillmentResult?.phoneNumber,
      },
    });
    await this.sideEffects.notifyOrderActivated(ctx.sfOrderId, ctx.validation?.sfOrder?.AccountId);
  }

  /**
   * Update Opportunity with WHMCS Service ID and Active stage
   */
  async executeOpportunityUpdate(
    ctx: OrderFulfillmentContext,
    whmcsCreateResult?: WhmcsOrderResult
  ): Promise<
    | { skipped: true }
    | { opportunityId: string; whmcsServiceId?: number }
    | { failed: true; error: string }
  > {
    const opportunityId = ctx.orderDetails?.opportunityId;
    const serviceId = whmcsCreateResult?.serviceIds?.[0];

    if (!opportunityId) {
      this.logger.debug("No Opportunity linked to order, skipping update", {
        sfOrderId: ctx.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: ctx.sfOrderId,
      });

      // Build result with optional whmcsServiceId only if present
      const result: { opportunityId: string; whmcsServiceId?: number } = { opportunityId };
      if (serviceId !== undefined) {
        result.whmcsServiceId = serviceId;
      }
      return result;
    } 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: ctx.sfOrderId,
      });
      return { failed: true as const, error: extractErrorMessage(error) };
    }
  }

  // ============================================
  // Rollback Methods (safe, never throw)
  // ============================================

  /**
   * Rollback SF status update by setting to Failed
   */
  async rollbackSfStatus(ctx: OrderFulfillmentContext): Promise<void> {
    try {
      await this.salesforceFacade.updateOrder({
        Id: ctx.sfOrderId,
        Activation_Status__c: "Failed",
      });
    } catch (error) {
      this.logger.error("Rollback failed for SF status update", {
        sfOrderId: ctx.sfOrderId,
        error: extractErrorMessage(error),
      });
    }
  }

  /**
   * Rollback SIM fulfillment - logs for manual intervention
   */
  // eslint-disable-next-line @typescript-eslint/promise-function-async -- Synchronous method returning Promise to match interface
  rollbackSimFulfillment(
    ctx: OrderFulfillmentContext,
    simFulfillmentResult?: SimFulfillmentResult
  ): Promise<void> {
    // SIM activation cannot be easily rolled back
    // Log for manual intervention if needed
    this.logger.warn("SIM fulfillment step needs rollback - manual intervention may be required", {
      sfOrderId: ctx.sfOrderId,
      simFulfillmentResult,
    });
    return Promise.resolve();
  }

  /**
   * Rollback WHMCS order creation - logs for manual cleanup
   */
  // eslint-disable-next-line @typescript-eslint/promise-function-async -- Synchronous method returning Promise to match interface
  rollbackWhmcsCreate(
    ctx: OrderFulfillmentContext,
    whmcsCreateResult?: WhmcsOrderResult
  ): Promise<void> {
    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: ctx.sfOrderId,
        action: "MANUAL_CLEANUP_REQUIRED",
      });
    }
    return Promise.resolve();
  }

  /**
   * Rollback WHMCS order acceptance - logs for manual service termination
   */
  // eslint-disable-next-line @typescript-eslint/promise-function-async -- Synchronous method returning Promise to match interface
  rollbackWhmcsAccept(
    ctx: OrderFulfillmentContext,
    whmcsCreateResult?: WhmcsOrderResult
  ): Promise<void> {
    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: ctx.sfOrderId,
        action: "MANUAL_SERVICE_TERMINATION_REQUIRED",
      });
    }
    return Promise.resolve();
  }
}
