import { Injectable, BadRequestException, Inject, ConflictException } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { FreebitFacade } from "@bff/integrations/freebit/facades/freebit.facade.js";
import { WhmcsOrderService } from "@bff/integrations/whmcs/services/whmcs-order.service.js";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
import { CacheService } from "@bff/infra/cache/cache.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import type { SimOrderActivationRequest } from "@customer-portal/domain/sim";
import { randomUUID } from "crypto";
import { SimBillingService } from "./sim-management/services/queries/sim-billing.service.js";
import { SimScheduleService } from "./sim-management/services/support/sim-schedule.service.js";

@Injectable()
export class SimOrderActivationService {
  constructor(
    private readonly freebit: FreebitFacade,
    private readonly whmcsOrderService: WhmcsOrderService,
    private readonly mappings: MappingsService,
    private readonly cache: CacheService,
    private readonly simBilling: SimBillingService,
    private readonly simSchedule: SimScheduleService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  async activate(
    userId: string,
    req: SimOrderActivationRequest,
    idempotencyKey?: string
  ): Promise<{ success: boolean; invoiceId: number; transactionId?: string }> {
    // Generate idempotency key if not provided
    const idemKey = idempotencyKey || randomUUID();
    const cacheKey = `sim-activation:${userId}:${idemKey}`;

    // Check if already processed
    const existing = await this.cache.get<{
      success: boolean;
      invoiceId: number;
      transactionId?: string;
    }>(cacheKey);
    if (existing) {
      this.logger.log("Returning cached SIM activation result (idempotent)", {
        userId,
        idempotencyKey: idemKey,
        msisdn: req.msisdn,
      });
      return existing;
    }

    // Check if operation is currently processing
    const processingKey = `${cacheKey}:processing`;
    const isProcessing = await this.cache.exists(processingKey);
    if (isProcessing) {
      throw new ConflictException("SIM activation already in progress. Please wait and retry.");
    }

    // Mark as processing (5 minute TTL)
    await this.cache.set(processingKey, true, 300);

    const releaseProcessingFlag = async () => {
      await this.cache.del(processingKey);
    };

    if (req.simType === "eSIM" && (!req.eid || req.eid.length < 15)) {
      await releaseProcessingFlag();
      throw new BadRequestException("EID is required for eSIM and must be valid");
    }
    if (!req.msisdn || req.msisdn.trim() === "") {
      await releaseProcessingFlag();
      throw new BadRequestException("Phone number (msisdn) is required for SIM activation");
    }
    try {
      if (req.activationType === "Scheduled") {
        this.simSchedule.ensureYyyyMmDd(req.scheduledAt, "scheduledAt");
      } else {
        this.simSchedule.validateOptionalYyyyMmDd(req.scheduledAt, "scheduledAt");
      }
    } catch (error) {
      await releaseProcessingFlag();
      throw error;
    }

    if (req.simType !== "eSIM") {
      this.logger.warn("Physical SIM activation blocked", {
        userId,
        simType: req.simType,
        msisdn: req.msisdn,
      });
      await releaseProcessingFlag();
      throw new BadRequestException(
        "Physical SIM activations are temporarily disabled. Please choose eSIM."
      );
    }

    let whmcsClientId: number;
    try {
      whmcsClientId = await this.mappings.getWhmcsClientIdOrThrow(userId);
    } catch (error) {
      await releaseProcessingFlag();
      throw error;
    }

    let billingResult:
      | {
          invoice: { id: number; number: string };
          transactionId?: string;
        }
      | undefined;

    try {
      billingResult = await this.simBilling.createOneTimeCharge({
        clientId: whmcsClientId,
        userId,
        description: `SIM Activation Fee (${req.planSku}) for ${req.msisdn}`,
        amountJpy: req.oneTimeAmountJpy,
        currency: "JPY",
        dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
        notes: `SIM activation fee for ${req.msisdn}, plan ${req.planSku}. Monthly billing will start on the 1st of next month.`,
        failureNotesPrefix: "SIM activation payment failed",
        publicErrorMessage:
          "Unable to process SIM activation payment. Please update your payment method.",
        metadata: { userId, msisdn: req.msisdn },
      });

      await this.freebit.activateEsimAccountNew({
        account: req.msisdn,
        eid: req.eid!,
        planCode: req.planSku,
        contractLine: "5G",
        ...(req.activationType === "Scheduled" && req.scheduledAt !== undefined
          ? { shipDate: req.scheduledAt }
          : {}),
        ...(req.mnp
          ? {
              mnp: {
                reserveNumber: req.mnp.reserveNumber || "",
                reserveExpireDate: req.mnp.reserveExpireDate || "",
              },
            }
          : {}),
      });

      if (req.addons && (req.addons.voiceMail || req.addons.callWaiting)) {
        await this.freebit.updateSimFeatures(req.msisdn, {
          voiceMailEnabled: !!req.addons.voiceMail,
          callWaitingEnabled: !!req.addons.callWaiting,
        });
      }

      if (req.monthlyAmountJpy > 0) {
        const nextBillingIso = this.simSchedule.firstDayOfNextMonthIsoDate();
        await this.whmcsOrderService.addOrder({
          clientId: whmcsClientId,
          items: [
            {
              productId: req.planSku,
              billingCycle: "monthly",
              quantity: 1,
              configOptions: {
                phone_number: req.msisdn,
                activation_date: nextBillingIso,
              },
              customFields: {
                sim_type: req.simType,
                eid: req.eid || "",
              },
            },
          ],
          paymentMethod: "mailin",
          notes: `Monthly SIM plan billing for ${req.msisdn}, plan ${req.planSku}. Billing starts on ${nextBillingIso}.`,
          noinvoice: false,
          noinvoiceemail: true,
          noemail: true,
        });

        this.logger.log("Monthly subscription created", {
          account: req.msisdn,
          amount: req.monthlyAmountJpy,
          nextDueDate: nextBillingIso,
        });
      }

      this.logger.log("SIM activation completed", {
        account: req.msisdn,
        invoiceId: billingResult.invoice.id,
      });

      const result: { success: boolean; invoiceId: number; transactionId?: string } = {
        success: true,
        invoiceId: billingResult.invoice.id,
        ...(billingResult.transactionId === undefined
          ? {}
          : { transactionId: billingResult.transactionId }),
      };

      await this.cache.set(cacheKey, result, 86400);
      await releaseProcessingFlag();
      return result;
    } catch (err) {
      await releaseProcessingFlag();
      if (billingResult?.invoice) {
        await this.simBilling.appendInvoiceNote(
          billingResult.invoice.id,
          `Freebit activation failed after payment: ${extractErrorMessage(err)}`
        );
      }
      throw err;
    }
  }
}
