import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { FreebitOrchestratorService } from "@bff/integrations/freebit/services/freebit-orchestrator.service";
import type { OrderDetails, OrderItemDetails } from "@customer-portal/domain/orders";
import { getErrorMessage } from "@bff/core/utils/error.util";
import {
  SimActivationException,
  OrderValidationException,
} from "@bff/core/exceptions/domain-exceptions";

export interface SimFulfillmentRequest {
  orderDetails: OrderDetails;
  configurations: Record<string, unknown>;
}

@Injectable()
export class SimFulfillmentService {
  constructor(
    private readonly freebit: FreebitOrchestratorService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  async fulfillSimOrder(request: SimFulfillmentRequest): Promise<void> {
    const { orderDetails, configurations } = request;

    this.logger.log("Starting SIM fulfillment", {
      orderId: orderDetails.id,
      orderType: orderDetails.orderType,
    });

    const simType = this.readEnum(configurations.simType, ["eSIM", "Physical SIM"]) ?? "eSIM";
    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;
    if (!planSku) {
      throw new OrderValidationException("SIM plan SKU not found", {
        orderId: orderDetails.id,
        itemId: simPlanItem.id,
      });
    }

    if (simType === "eSIM" && (!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 SIM activation", {
        orderId: orderDetails.id,
      });
    }

    if (simType === "eSIM") {
      if (!eid) {
        throw new SimActivationException("EID is required for eSIM activation", {
          orderId: orderDetails.id,
        });
      }
      await this.activateSim({
        account: phoneNumber,
        eid,
        planSku,
        simType: "eSIM",
        activationType,
        scheduledAt,
        mnp,
      });
    } else {
      await this.activateSim({
        account: phoneNumber,
        planSku,
        simType: "Physical SIM",
        activationType,
        scheduledAt,
        mnp,
      });
    }

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

  private async activateSim(
    params:
      | {
          account: string;
          eid: string;
          planSku: string;
          simType: "eSIM";
          activationType: "Immediate" | "Scheduled";
          scheduledAt?: string;
          mnp?: {
            reserveNumber?: string;
            reserveExpireDate?: string;
            account?: string;
            firstnameKanji?: string;
            lastnameKanji?: string;
            firstnameZenKana?: string;
            lastnameZenKana?: string;
            gender?: string;
            birthday?: string;
          };
        }
      | {
          account: string;
          eid?: string;
          planSku: string;
          simType: "Physical SIM";
          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, planSku, simType, activationType, scheduledAt, mnp } = params;

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

        this.logger.log("eSIM activated successfully", {
          account,
          planSku,
          scheduled: activationType === "Scheduled",
        });
      } else {
        await this.freebit.topUpSim(account, 0, {
          scheduledAt: activationType === "Scheduled" ? scheduledAt : undefined,
        });

        this.logger.log("Physical SIM activation scheduled", {
          account,
          planSku,
        });
      }
    } catch (error: unknown) {
      this.logger.error("SIM activation failed", {
        account,
        planSku,
        error: getErrorMessage(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 undefined;
    }

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

    return {
      reserveNumber,
      reserveExpireDate,
      account,
      firstnameKanji,
      lastnameKanji,
      firstnameZenKana,
      lastnameZenKana,
      gender,
      birthday,
    };
  }
}
