import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { FreebitFacade } from "@bff/integrations/freebit/facades/freebit.facade.js";
import { SalesforceSIMInventoryService } from "@bff/integrations/salesforce/services/salesforce-sim-inventory.service.js";
import type { OrderDetails, OrderItemDetails } from "@customer-portal/domain/orders";
import { mapProductToFreebitPlanCode } from "@customer-portal/domain/sim";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import {
  SimActivationException,
  OrderValidationException,
} from "@bff/core/exceptions/domain-exceptions.js";
import type { FulfillmentConfigurations } from "./fulfillment-context-mapper.service.js";

/**
 * Contact identity data for PA05-05 voice option registration
 */
export interface ContactIdentityData {
  firstnameKanji: string;
  lastnameKanji: string;
  firstnameKana: string;
  lastnameKana: string;
  gender: "M" | "F";
  birthday: string; // YYYYMMDD format
}

/**
 * Assignment details for Physical SIM inventory
 */
export interface SimAssignmentDetails {
  /** Salesforce Account ID to assign the SIM to */
  accountId?: string;
  /** Salesforce Order ID that assigned the SIM */
  orderId?: string;
  /** SIM Type (eSIM or Physical SIM) */
  simType?: string;
}

export interface SimFulfillmentRequest {
  orderDetails: OrderDetails;
  configurations: FulfillmentConfigurations;
  /** Salesforce ID of the assigned Physical SIM (from Assign_Physical_SIM__c) */
  assignedPhysicalSimId?: string;
  /** Voice Mail enabled from Order.SIM_Voice_Mail__c */
  voiceMailEnabled?: boolean;
  /** Call Waiting enabled from Order.SIM_Call_Waiting__c */
  callWaitingEnabled?: boolean;
  /** Contact identity data for PA05-05 */
  contactIdentity?: ContactIdentityData;
  /** Assignment details for SIM Inventory record (Physical SIM only) */
  assignmentDetails?: SimAssignmentDetails;
}

/**
 * Result from SIM fulfillment containing inventory data for WHMCS
 */
export interface SimFulfillmentResult {
  /** Whether the SIM was successfully activated */
  activated: boolean;
  /** SIM type that was activated */
  simType: "eSIM" | "Physical SIM";
  /** Phone number from SIM inventory (for WHMCS custom fields) */
  phoneNumber?: string;
  /** PT Number / Serial number from SIM inventory (for WHMCS custom fields) */
  serialNumber?: string;
  /** Salesforce SIM Inventory ID */
  simInventoryId?: string;
  /** EID for eSIM (for WHMCS custom fields) */
  eid?: string;
}

/**
 * MNP configuration extracted from Salesforce order/porting fields
 */
interface MnpConfig {
  reserveNumber?: string;
  reserveExpireDate?: string;
  account?: string;
  firstnameKanji?: string;
  lastnameKanji?: string;
  firstnameZenKana?: string;
  lastnameZenKana?: string;
  gender?: string;
  birthday?: string;
}

/**
 * Map Salesforce gender code to Freebit gender code.
 * Salesforce: 'M' (Male), 'F' (Female)
 * Freebit:    'M' (Male), 'W' (Weiblich/Female), 'C' (Corporation)
 */
function mapGenderToFreebit(gender: string): string {
  if (gender === "F") return "W";
  return gender;
}

@Injectable()
export class SimFulfillmentService {
  constructor(
    private readonly freebitFacade: FreebitFacade,
    private readonly simInventory: SalesforceSIMInventoryService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  async fulfillSimOrder(request: SimFulfillmentRequest): Promise<SimFulfillmentResult> {
    const {
      orderDetails,
      configurations,
      assignedPhysicalSimId,
      voiceMailEnabled = false,
      callWaitingEnabled = false,
      contactIdentity,
      assignmentDetails,
    } = request;

    const simType = this.readEnum(configurations["simType"], ["eSIM", "Physical SIM"]);

    this.logger.log("Starting SIM fulfillment", {
      orderId: orderDetails.id,
      orderType: orderDetails.orderType,
      simType: simType ?? "(not set)",
      hasAssignedPhysicalSim: !!assignedPhysicalSimId,
      voiceMailEnabled,
      callWaitingEnabled,
      hasContactIdentity: !!contactIdentity,
    });

    // Validate SIM type is explicitly set - don't default to eSIM
    if (!simType) {
      throw new SimActivationException(
        "SIM Type must be explicitly set to 'eSIM' or 'Physical SIM'",
        {
          orderId: orderDetails.id,
          configuredSimType: configurations["simType"],
        }
      );
    }

    const eid = this.readString(configurations["eid"]);
    const activationType =
      this.readEnum(configurations["activationType"], ["Immediate", "Scheduled"]) ?? "Immediate";
    const scheduledAt = this.readString(configurations["scheduledAt"]);
    const phoneNumber = this.readString(configurations["mnpPhone"]);
    const mnp = this.extractMnpConfig(configurations);
    const isMnp = !!mnp?.["reserveNumber"];

    this.logger.log("MNP detection result", {
      orderId: orderDetails.id,
      isMnp,
      simType,
      mnpReserveNumber: mnp?.["reserveNumber"],
      mnpHasIdentity: !!(mnp?.["lastnameKanji"] || mnp?.["firstnameKanji"]),
      mnpGender: mnp?.["gender"],
    });

    const simPlanItem = orderDetails.items.find(
      (item: OrderItemDetails) =>
        item.product?.itemClass === "Plan" || item.product?.sku?.toLowerCase().includes("sim")
    );

    if (!simPlanItem) {
      throw new OrderValidationException("No SIM plan found in order items", {
        orderId: orderDetails.id,
      });
    }

    const planSku = simPlanItem.product?.sku;
    const planName = simPlanItem.product?.name;
    if (!planSku) {
      throw new OrderValidationException("SIM plan SKU not found", {
        orderId: orderDetails.id,
        itemId: simPlanItem.id,
      });
    }

    if (simType === "eSIM") {
      return this.fulfillEsim({
        orderDetails,
        eid,
        phoneNumber,
        planSku,
        planName,
        activationType,
        scheduledAt,
        mnp,
      });
    }

    return this.fulfillPhysicalSim({
      orderDetails,
      assignedPhysicalSimId,
      planSku,
      planName,
      voiceMailEnabled,
      callWaitingEnabled,
      contactIdentity,
      assignmentDetails,
      isMnp,
      mnp,
    });
  }

  private async fulfillEsim(params: {
    orderDetails: OrderDetails;
    eid: string | undefined;
    phoneNumber: string | undefined;
    planSku: string;
    planName: string | undefined;
    activationType: "Immediate" | "Scheduled";
    scheduledAt: string | undefined;
    mnp: MnpConfig | undefined;
  }): Promise<SimFulfillmentResult> {
    const { orderDetails, eid, phoneNumber, planSku, planName, activationType, scheduledAt, mnp } =
      params;

    if (!eid || eid.length < 15) {
      throw new SimActivationException("EID is required for eSIM and must be valid", {
        orderId: orderDetails.id,
        simType: "eSIM",
        eidLength: eid?.length,
      });
    }

    if (!phoneNumber) {
      throw new SimActivationException("Phone number is required for eSIM activation", {
        orderId: orderDetails.id,
      });
    }

    const planCode = mapProductToFreebitPlanCode(planSku, planName);
    if (!planCode) {
      throw new SimActivationException(
        `Unable to map product to Freebit plan code. SKU: ${planSku}, Name: ${planName}`,
        { orderId: orderDetails.id, planSku, planName }
      );
    }

    await this.activateEsim({
      account: phoneNumber,
      eid,
      planCode,
      activationType,
      ...(scheduledAt && { scheduledAt }),
      ...(mnp && { mnp }),
    });

    this.logger.log("eSIM fulfillment completed successfully", {
      orderId: orderDetails.id,
      account: phoneNumber,
      planSku,
    });

    return {
      activated: true,
      simType: "eSIM",
      phoneNumber,
      eid,
    };
  }

  private async fulfillPhysicalSim(params: {
    orderDetails: OrderDetails;
    assignedPhysicalSimId: string | undefined;
    planSku: string;
    planName: string | undefined;
    voiceMailEnabled: boolean;
    callWaitingEnabled: boolean;
    contactIdentity: ContactIdentityData | undefined;
    assignmentDetails: SimAssignmentDetails | undefined;
    isMnp: boolean;
    mnp: MnpConfig | undefined;
  }): Promise<SimFulfillmentResult> {
    const {
      orderDetails,
      assignedPhysicalSimId,
      planSku,
      planName,
      voiceMailEnabled,
      callWaitingEnabled,
      contactIdentity,
      assignmentDetails,
      isMnp,
      mnp,
    } = params;

    if (!assignedPhysicalSimId) {
      throw new SimActivationException(
        "Physical SIM requires an assigned SIM from inventory (Assign_Physical_SIM__c)",
        { orderId: orderDetails.id }
      );
    }

    const simData = await this.activatePhysicalSim({
      orderId: orderDetails.id,
      simInventoryId: assignedPhysicalSimId,
      planSku,
      planName,
      voiceMailEnabled,
      callWaitingEnabled,
      contactIdentity,
      assignmentDetails,
      isMnp,
      ...(mnp && { mnp }),
    });

    this.logger.log("Physical SIM fulfillment completed successfully", {
      orderId: orderDetails.id,
      simInventoryId: assignedPhysicalSimId,
      planSku,
      voiceMailEnabled,
      callWaitingEnabled,
      phoneNumber: simData.phoneNumber,
      serialNumber: simData.serialNumber,
    });

    return {
      activated: true,
      simType: "Physical SIM",
      phoneNumber: simData.phoneNumber,
      serialNumber: simData.serialNumber,
      simInventoryId: assignedPhysicalSimId,
    };
  }

  /**
   * Activate eSIM via Freebit PA05-41 API
   */
  private async activateEsim(params: {
    account: string;
    eid: string;
    planCode: string;
    activationType: "Immediate" | "Scheduled";
    scheduledAt?: string;
    mnp?: MnpConfig;
  }): Promise<void> {
    const { account, eid, planCode, activationType, scheduledAt, mnp } = params;
    const isMnp = !!mnp?.reserveNumber;

    this.logger.log("eSIM activation starting", {
      account,
      planCode,
      isMnp,
      addKind: isMnp ? "M" : "N",
      aladinOperated: isMnp ? "20" : "10",
      mnpReserveNumber: mnp?.reserveNumber,
      mnpHasIdentity: !!(mnp?.lastnameKanji || mnp?.firstnameKanji),
      mnpGender: mnp?.gender,
    });

    try {
      // Build unified MNP object with both reservation and identity data (all Level 2 per PA05-41)
      const mnpPayload = isMnp ? this.buildMnpPayload(mnp) : undefined;

      const addKind = isMnp ? ("M" as const) : ("N" as const);
      const aladinOperated = isMnp ? ("20" as const) : ("10" as const);
      const pa0541Params = {
        account,
        eid,
        planCode,
        contractLine: "5G" as const,
        simKind: "E0" as const,
        addKind,
        aladinOperated,
        ...(activationType === "Scheduled" && scheduledAt && { shipDate: scheduledAt }),
        ...(mnpPayload && { mnp: mnpPayload }),
      };

      this.logger.log("PA05-41 full request payload", {
        account: pa0541Params.account,
        eid: pa0541Params.eid,
        planCode: pa0541Params.planCode,
        contractLine: pa0541Params.contractLine,
        simKind: pa0541Params.simKind,
        addKind: pa0541Params.addKind,
        aladinOperated: pa0541Params.aladinOperated,
        shipDate: pa0541Params.shipDate ?? "not-set",
        hasMnpPayload: !!mnpPayload,
        mnpPayloadFields: mnpPayload ? Object.keys(mnpPayload) : [],
        mnpReserveNumber: mnpPayload?.reserveNumber ?? "not-set",
        mnpGenderMapped: mnpPayload?.gender ?? "not-set",
      });

      await this.freebitFacade.activateEsimAccountNew(pa0541Params);

      this.logger.log("eSIM activated successfully via PA05-41", {
        account,
        planCode,
        isMnp,
        addKind: pa0541Params.addKind,
        scheduled: activationType === "Scheduled",
      });
    } catch (error: unknown) {
      this.logger.error("eSIM activation failed via PA05-41", {
        account,
        planCode,
        isMnp,
        addKind: isMnp ? "M" : "N",
        mnpReserveNumber: mnp?.reserveNumber ?? "not-set",
        mnpFieldCount: mnp ? Object.keys(mnp).length : 0,
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Activate Physical SIM (Black SIM) via Freebit APIs
   *
   * Non-MNP flow:
   * 1. Fetch SIM Inventory details from Salesforce
   * 2. Validate SIM status is "Available"
   * 3. Map product SKU to Freebit plan code
   * 4. Call Freebit PA02-01 (Account Registration) with createType="new"
   * 5. Call Freebit PA05-05 (Voice Options) to configure voice features
   * 6. Update SIM Inventory status to "Assigned"
   *
   * MNP flow:
   * 1-3. Same as above
   * 4. Call Freebit PA05-19 (Semi-Black MNP Registration) — replaces PA02-01
   * 5. Call Freebit PA05-05 (Voice Options)
   * 6. Update SIM Inventory status to "Assigned"
   */
  private async activatePhysicalSim(params: {
    orderId: string;
    simInventoryId: string;
    planSku: string;
    planName?: string | undefined;
    voiceMailEnabled: boolean;
    callWaitingEnabled: boolean;
    contactIdentity?: ContactIdentityData | undefined;
    assignmentDetails?: SimAssignmentDetails | undefined;
    isMnp?: boolean;
    mnp?: MnpConfig;
  }): Promise<{ phoneNumber: string; serialNumber: string }> {
    const {
      orderId,
      simInventoryId,
      planSku,
      planName,
      voiceMailEnabled,
      callWaitingEnabled,
      contactIdentity,
      assignmentDetails,
      isMnp = false,
      mnp,
    } = params;

    this.logger.log("Starting Physical SIM activation", {
      orderId,
      simInventoryId,
      planSku,
      isMnp,
      path: isMnp ? "PA05-19 (MNP)" : "PA02-01 (new)",
      voiceMailEnabled,
      callWaitingEnabled,
      hasContactIdentity: !!contactIdentity,
      mnpReserveNumber: mnp?.reserveNumber,
    });

    // Step 1 & 2: Fetch and validate SIM Inventory
    const simRecord = await this.simInventory.getAndValidateForActivation(simInventoryId);

    // Step 3: Map product to Freebit plan code
    const planCode = mapProductToFreebitPlanCode(planSku, planName);
    if (!planCode) {
      throw new SimActivationException(
        `Unable to map product to Freebit plan code. SKU: ${planSku}, Name: ${planName}`,
        { orderId, simInventoryId, planSku, planName }
      );
    }

    // Use phone number from SIM inventory
    const accountPhoneNumber = simRecord.phoneNumber;

    this.logger.log("Physical SIM inventory validated", {
      orderId,
      simInventoryId,
      accountPhoneNumber,
      ptNumber: simRecord.ptNumber,
      planCode,
      isMnp,
    });

    try {
      if (isMnp) {
        // Step 4 (MNP): Call Freebit PA05-19 (Semi-Black MNP Registration)
        // PA05-19 replaces PA02-01 for MNP transfers — it registers the account
        // and initiates the MNP transfer in a single call
        this.logger.log("Calling PA05-19 Semi-Black MNP Registration", {
          orderId,
          account: accountPhoneNumber,
          productNumber: simRecord.ptNumber,
          planCode,
          mnpMethod: "10",
        });

        await this.freebitFacade.registerSemiBlackAccount({
          account: accountPhoneNumber,
          productNumber: simRecord.ptNumber,
          planCode,
        });

        this.logger.log("PA05-19 Semi-Black MNP Registration successful", {
          orderId,
          account: accountPhoneNumber,
        });
      } else {
        // Step 4 (non-MNP): Call Freebit PA02-01 (Account Registration)
        this.logger.log("Calling PA02-01 Account Registration", {
          orderId,
          account: accountPhoneNumber,
          planCode,
          createType: "new",
        });

        await this.freebitFacade.registerAccount({
          account: accountPhoneNumber,
          planCode,
          createType: "new",
        });

        this.logger.log("PA02-01 Account Registration successful", {
          orderId,
          account: accountPhoneNumber,
        });
      }

      // Step 5: Call Freebit PA05-05 (Voice Options Registration)
      await this.registerVoiceOptionsIfAvailable({
        orderId,
        account: accountPhoneNumber,
        voiceMailEnabled,
        callWaitingEnabled,
        contactIdentity,
      });

      // Step 6: Update SIM Inventory status to "Assigned" with assignment details
      await this.simInventory.markAsAssigned(simInventoryId, assignmentDetails);

      this.logger.log("Physical SIM activated successfully", {
        orderId,
        simInventoryId,
        accountPhoneNumber,
        planCode,
        voiceMailEnabled,
        callWaitingEnabled,
      });

      // Return SIM data for WHMCS custom fields
      return {
        phoneNumber: simRecord.phoneNumber,
        serialNumber: simRecord.ptNumber,
      };
    } catch (error: unknown) {
      this.logger.error("Physical SIM activation failed", {
        orderId,
        simInventoryId,
        phoneNumber: simRecord.phoneNumber,
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  private buildMnpPayload(mnp?: MnpConfig) {
    if (!mnp?.reserveNumber) return;
    return {
      reserveNumber: mnp.reserveNumber,
      ...(mnp.reserveExpireDate && { reserveExpireDate: mnp.reserveExpireDate }),
      ...(mnp.lastnameKanji && { lastnameKanji: mnp.lastnameKanji }),
      ...(mnp.firstnameKanji && { firstnameKanji: mnp.firstnameKanji }),
      ...(mnp.lastnameZenKana && { lastnameZenKana: mnp.lastnameZenKana }),
      ...(mnp.firstnameZenKana && { firstnameZenKana: mnp.firstnameZenKana }),
      // Map Salesforce gender 'F' → Freebit gender 'W' (Weiblich)
      ...(mnp.gender && { gender: mapGenderToFreebit(mnp.gender) }),
      ...(mnp.birthday && { birthday: mnp.birthday }),
    };
  }

  private async registerVoiceOptionsIfAvailable(params: {
    orderId: string;
    account: string;
    voiceMailEnabled: boolean;
    callWaitingEnabled: boolean;
    contactIdentity?: ContactIdentityData | undefined;
  }): Promise<void> {
    const { orderId, account, voiceMailEnabled, callWaitingEnabled, contactIdentity } = params;

    if (!contactIdentity) {
      this.logger.warn("Skipping PA05-05: No contact identity data provided", {
        orderId,
        account,
      });
      return;
    }

    this.logger.log("Calling PA05-05 Voice Options Registration", {
      orderId,
      account,
      voiceMailEnabled,
      callWaitingEnabled,
    });

    await this.freebitFacade.registerVoiceOptions({
      account,
      voiceMailEnabled,
      callWaitingEnabled,
      identificationData: {
        lastnameKanji: contactIdentity.lastnameKanji,
        firstnameKanji: contactIdentity.firstnameKanji,
        lastnameKana: contactIdentity.lastnameKana,
        firstnameKana: contactIdentity.firstnameKana,
        gender: contactIdentity.gender,
        birthday: contactIdentity.birthday,
      },
    });

    this.logger.log("PA05-05 Voice Options Registration successful", {
      orderId,
      account,
    });
  }

  private readString(value: unknown): string | undefined {
    return typeof value === "string" ? value : undefined;
  }

  private readEnum<T extends string>(value: unknown, allowed: readonly T[]): T | undefined {
    return typeof value === "string" && allowed.includes(value as T) ? (value as T) : undefined;
  }

  private static readonly MNP_FIELD_MAPPINGS = [
    { key: "reserveNumber", sources: ["mnpNumber", "reserveNumber"] },
    { key: "reserveExpireDate", sources: ["mnpExpiry", "reserveExpireDate"] },
    { key: "account", sources: ["mvnoAccountNumber", "account"] },
    { key: "firstnameKanji", sources: ["portingFirstName", "firstnameKanji"] },
    { key: "lastnameKanji", sources: ["portingLastName", "lastnameKanji"] },
    { key: "firstnameZenKana", sources: ["portingFirstNameKatakana", "firstnameZenKana"] },
    { key: "lastnameZenKana", sources: ["portingLastNameKatakana", "lastnameZenKana"] },
    { key: "gender", sources: ["portingGender", "gender"] },
    { key: "birthday", sources: ["portingDateOfBirth", "birthday"] },
  ] as const;

  private extractMnpConfig(config: FulfillmentConfigurations) {
    const nested = config["mnp"];
    const hasNestedMnp = nested && typeof nested === "object";
    const source = hasNestedMnp ? nested : config;

    const isMnpFlag = this.readString(source["isMnp"] ?? config["isMnp"]);
    if (isMnpFlag && isMnpFlag.toLowerCase() !== "true") {
      this.logger.log("MNP extraction skipped: isMnp flag is not 'true'", {
        isMnpFlag,
        isMnpFlagType: typeof (source["isMnp"] ?? config["isMnp"]),
      });
      return;
    }

    const result: Record<string, string> = {};
    for (const { key, sources } of SimFulfillmentService.MNP_FIELD_MAPPINGS) {
      const value = this.readString(source[sources[0]] ?? source[sources[1]]);
      if (value) result[key] = value;
    }

    if (Object.keys(result).length === 0) {
      this.logger.log("MNP extraction: no MNP fields found in config", {
        hasNestedMnp,
        isMnpFlag: isMnpFlag ?? "not-set",
        checkedKeys: SimFulfillmentService.MNP_FIELD_MAPPINGS.flatMap(m => m.sources),
      });
      return;
    }

    this.logger.log("MNP config extracted", {
      hasReserveNumber: !!result["reserveNumber"],
      reserveNumberLength: result["reserveNumber"]?.length,
      hasReserveExpireDate: !!result["reserveExpireDate"],
      hasAccount: !!result["account"],
      hasIdentity: !!(result["firstnameKanji"] && result["lastnameKanji"]),
      hasKana: !!(result["firstnameZenKana"] && result["lastnameZenKana"]),
      gender: result["gender"] ?? "not-set",
      hasBirthday: !!result["birthday"],
      totalFieldsExtracted: Object.keys(result).length,
    });

    return result;
  }
}
