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

/**
 * 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
}

export interface SimFulfillmentRequest {
  orderDetails: OrderDetails;
  configurations: Record<string, unknown>;
  /** 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;
}

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

@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,
    } = 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 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") {
      // eSIM activation flow
      if (!eid || eid.length < 15) {
        throw new SimActivationException("EID is required for eSIM and must be valid", {
          orderId: orderDetails.id,
          simType,
          eidLength: eid?.length,
        });
      }

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

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

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

      return {
        activated: true,
        simType: "eSIM",
        phoneNumber,
      };
    } else {
      // Physical SIM activation flow (PA05-18 + PA02-01 + PA05-05)
      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,
      });

      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;
    planSku: string;
    activationType: "Immediate" | "Scheduled";
    scheduledAt?: string;
    mnp?: {
      reserveNumber?: string;
      reserveExpireDate?: string;
      account?: string;
      firstnameKanji?: string;
      lastnameKanji?: string;
      firstnameZenKana?: string;
      lastnameZenKana?: string;
      gender?: string;
      birthday?: string;
    };
  }): Promise<void> {
    const { account, eid, planSku, activationType, scheduledAt, mnp } = params;

    try {
      await this.freebitFacade.activateEsimAccountNew({
        account,
        eid,
        planCode: planSku,
        contractLine: "5G",
        ...(activationType === "Scheduled" && scheduledAt && { shipDate: scheduledAt }),
        ...(mnp?.reserveNumber &&
          mnp?.reserveExpireDate && {
            mnp: {
              reserveNumber: mnp.reserveNumber,
              reserveExpireDate: mnp.reserveExpireDate,
            },
          }),
        ...(mnp && {
          identity: {
            ...(mnp.firstnameKanji && { firstnameKanji: mnp.firstnameKanji }),
            ...(mnp.lastnameKanji && { lastnameKanji: mnp.lastnameKanji }),
            ...(mnp.firstnameZenKana && { firstnameZenKana: mnp.firstnameZenKana }),
            ...(mnp.lastnameZenKana && { lastnameZenKana: mnp.lastnameZenKana }),
            ...(mnp.gender && { gender: mnp.gender }),
            ...(mnp.birthday && { birthday: mnp.birthday }),
          },
        }),
      });

      this.logger.log("eSIM activated successfully", {
        account,
        planSku,
        scheduled: activationType === "Scheduled",
      });
    } catch (error: unknown) {
      this.logger.error("eSIM activation failed", {
        account,
        planSku,
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Activate Physical SIM via Freebit PA05-18 + PA02-01 + PA05-05 APIs
   *
   * Flow for Physical SIMs:
   * 1. Fetch SIM Inventory details from Salesforce
   * 2. Validate SIM status is "Available"
   * 3. Map product SKU to Freebit plan code
   * 4. Call Freebit PA05-18 (Semi-Black Registration) - MUST be called first!
   * 5. Call Freebit PA02-01 (Account Registration) with createType="add"
   * 6. Call Freebit PA05-05 (Voice Options) to configure voice features
   * 7. Update SIM Inventory status to "Assigned"
   *
   * Note: PA05-18 must be called before PA02-01, otherwise PA02-01 will fail
   * with error 210 "アカウント不在エラー" (Account not found error).
   */
  private async activatePhysicalSim(params: {
    orderId: string;
    simInventoryId: string;
    planSku: string;
    planName?: string | undefined;
    voiceMailEnabled: boolean;
    callWaitingEnabled: boolean;
    contactIdentity?: ContactIdentityData | undefined;
  }): Promise<{ phoneNumber: string; serialNumber: string }> {
    const {
      orderId,
      simInventoryId,
      planSku,
      planName,
      voiceMailEnabled,
      callWaitingEnabled,
      contactIdentity,
    } = params;

    this.logger.log("Starting Physical SIM activation (PA05-18 + PA02-01 + PA05-05)", {
      orderId,
      simInventoryId,
      planSku,
      voiceMailEnabled,
      callWaitingEnabled,
      hasContactIdentity: !!contactIdentity,
    });

    // 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;
    // PT Number is the productNumber required for PA05-18
    const productNumber = simRecord.ptNumber;

    this.logger.log("Physical SIM inventory validated", {
      orderId,
      simInventoryId,
      accountPhoneNumber,
      productNumber,
      planCode,
    });

    try {
      // Step 4: Call Freebit PA05-18 (Semi-Black Registration) - MUST be first!
      this.logger.log("Calling PA05-18 Semi-Black Registration", {
        orderId,
        account: accountPhoneNumber,
        productNumber,
        planCode,
      });

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

      this.logger.log("PA05-18 Semi-Black Registration successful", {
        orderId,
        account: accountPhoneNumber,
      });

      // Step 5: Call Freebit PA02-01 (Account Registration) with createType="add"
      // Note: After PA05-18, we use createType="add" (not "new")
      this.logger.log("Calling PA02-01 Account Registration", {
        orderId,
        account: accountPhoneNumber,
        planCode,
        createType: "add",
      });

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

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

      // Step 6: Call Freebit PA05-05 (Voice Options Registration)
      // Only call if we have contact identity data
      if (contactIdentity) {
        this.logger.log("Calling PA05-05 Voice Options Registration", {
          orderId,
          account: accountPhoneNumber,
          voiceMailEnabled,
          callWaitingEnabled,
        });

        await this.freebitFacade.registerVoiceOptions({
          account: accountPhoneNumber,
          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: accountPhoneNumber,
        });
      } else {
        this.logger.warn("Skipping PA05-05: No contact identity data provided", {
          orderId,
          account: accountPhoneNumber,
        });
      }

      // Step 7: Update SIM Inventory status to "Assigned"
      await this.simInventory.markAsAssigned(simInventoryId);

      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 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 extractMnpConfig(config: Record<string, unknown>) {
    const nested = config["mnp"];
    const source =
      nested && typeof nested === "object" ? (nested as Record<string, unknown>) : config;

    const isMnpFlag = this.readString(source["isMnp"] ?? config["isMnp"]);
    if (isMnpFlag && isMnpFlag !== "true") {
      return;
    }

    const reserveNumber = this.readString(source["mnpNumber"] ?? source["reserveNumber"]);
    const reserveExpireDate = this.readString(source["mnpExpiry"] ?? source["reserveExpireDate"]);
    const account = this.readString(source["mvnoAccountNumber"] ?? source["account"]);
    const firstnameKanji = this.readString(source["portingFirstName"] ?? source["firstnameKanji"]);
    const lastnameKanji = this.readString(source["portingLastName"] ?? source["lastnameKanji"]);
    const firstnameZenKana = this.readString(
      source["portingFirstNameKatakana"] ?? source["firstnameZenKana"]
    );
    const lastnameZenKana = this.readString(
      source["portingLastNameKatakana"] ?? source["lastnameZenKana"]
    );
    const gender = this.readString(source["portingGender"] ?? source["gender"]);
    const birthday = this.readString(source["portingDateOfBirth"] ?? source["birthday"]);

    if (
      !reserveNumber &&
      !reserveExpireDate &&
      !account &&
      !firstnameKanji &&
      !lastnameKanji &&
      !firstnameZenKana &&
      !lastnameZenKana &&
      !gender &&
      !birthday
    ) {
      return;
    }

    // Build object with only defined properties (for exactOptionalPropertyTypes)
    return {
      ...(reserveNumber && { reserveNumber }),
      ...(reserveExpireDate && { reserveExpireDate }),
      ...(account && { account }),
      ...(firstnameKanji && { firstnameKanji }),
      ...(lastnameKanji && { lastnameKanji }),
      ...(firstnameZenKana && { firstnameZenKana }),
      ...(lastnameZenKana && { lastnameZenKana }),
      ...(gender && { gender }),
      ...(birthday && { birthday }),
    };
  }
}
