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 {
  // eslint-disable-next-line max-params -- NestJS DI requires individual constructor injection
  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 }> {
    const idemKey = idempotencyKey || randomUUID();
    const cacheKey = `sim-activation:${userId}:${idemKey}`;

    const existing = await this.checkExistingActivation(cacheKey, userId, idemKey, req.msisdn);
    if (existing) return existing;

    const processingKey = `${cacheKey}:processing`;
    await this.acquireProcessingLock(processingKey);

    try {
      this.validateActivationRequest(req, userId);
      const whmcsClientId = await this.mappings.getWhmcsClientIdOrThrow(userId);
      return await this.executeActivation({
        userId,
        req,
        whmcsClientId,
        cacheKey,
        processingKey,
      });
    } catch (error) {
      await this.releaseProcessingLock(processingKey);
      throw error;
    }
  }

  private async checkExistingActivation(
    cacheKey: string,
    userId: string,
    idemKey: string,
    msisdn: string
  ): Promise<{ success: boolean; invoiceId: number; transactionId?: string } | null> {
    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,
      });
    }
    return existing;
  }

  private async acquireProcessingLock(processingKey: string): Promise<void> {
    const isProcessing = await this.cache.exists(processingKey);
    if (isProcessing) {
      throw new ConflictException("SIM activation already in progress. Please wait and retry.");
    }
    await this.cache.set(processingKey, true, 300);
  }

  private async releaseProcessingLock(processingKey: string): Promise<void> {
    await this.cache.del(processingKey);
  }

  private validateActivationRequest(req: SimOrderActivationRequest, userId: string): void {
    if (req.simType === "eSIM" && (!req.eid || req.eid.length < 15)) {
      throw new BadRequestException("EID is required for eSIM and must be valid");
    }
    if (!req.msisdn || req.msisdn.trim() === "") {
      throw new BadRequestException("Phone number (msisdn) is required for SIM activation");
    }

    if (req.activationType === "Scheduled") {
      this.simSchedule.ensureYyyyMmDd(req.scheduledAt, "scheduledAt");
    } else {
      this.simSchedule.validateOptionalYyyyMmDd(req.scheduledAt, "scheduledAt");
    }

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

  private async executeActivation(options: {
    userId: string;
    req: SimOrderActivationRequest;
    whmcsClientId: number;
    cacheKey: string;
    processingKey: string;
  }): Promise<{ success: boolean; invoiceId: number; transactionId?: string }> {
    const { userId, req, whmcsClientId, cacheKey, processingKey } = options;
    let billingResult:
      | { invoice: { id: number; number: string }; transactionId?: string }
      | undefined;

    try {
      billingResult = await this.createActivationCharge(whmcsClientId, userId, req);
      await this.activateEsim(req);
      await this.updateSimAddons(req);
      await this.createMonthlySubscription(req, whmcsClientId);

      const result = this.buildActivationResult(billingResult);
      await this.cache.set(cacheKey, result, 86400);
      await this.releaseProcessingLock(processingKey);

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

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

  private async createActivationCharge(
    clientId: number,
    userId: string,
    req: SimOrderActivationRequest
  ): Promise<{ invoice: { id: number; number: string }; transactionId?: string }> {
    return this.simBilling.createOneTimeCharge({
      clientId,
      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 },
    });
  }

  private async activateEsim(req: SimOrderActivationRequest): Promise<void> {
    const shipDate =
      req.activationType === "Scheduled" && req.scheduledAt !== undefined
        ? { shipDate: req.scheduledAt }
        : {};
    const mnpData = req.mnp
      ? {
          mnp: {
            reserveNumber: req.mnp.reserveNumber || "",
            reserveExpireDate: req.mnp.reserveExpireDate || "",
          },
        }
      : {};

    await this.freebit.activateEsimAccountNew({
      account: req.msisdn,
      eid: req.eid!,
      planCode: req.planSku,
      contractLine: "5G",
      ...shipDate,
      ...mnpData,
    });
  }

  private async updateSimAddons(req: SimOrderActivationRequest): Promise<void> {
    if (!req.addons || (!req.addons.voiceMail && !req.addons.callWaiting)) return;

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

  private async createMonthlySubscription(
    req: SimOrderActivationRequest,
    whmcsClientId: number
  ): Promise<void> {
    if (req.monthlyAmountJpy <= 0) return;

    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,
    });
  }

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

    if (billingResult.transactionId !== undefined) {
      result.transactionId = billingResult.transactionId;
    }

    return result;
  }
}
