import { Injectable, Inject, BadRequestException } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { ConfigService } from "@nestjs/config";
import { FreebitFacade } from "@bff/integrations/freebit/facades/freebit.facade.js";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
import { SimValidationService } from "../sim-validation.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import type { SimTopUpRequest } from "@customer-portal/domain/sim";
import { SimBillingService } from "../queries/sim-billing.service.js";
import { SimNotificationService } from "../support/sim-notification.service.js";
import { SimTopUpPricingService } from "../queries/sim-topup-pricing.service.js";

@Injectable()
export class SimTopUpService {
  // eslint-disable-next-line max-params -- NestJS DI requires individual constructor injection
  constructor(
    private readonly freebitService: FreebitFacade,
    private readonly mappingsService: MappingsService,
    private readonly simValidation: SimValidationService,
    private readonly simBilling: SimBillingService,
    private readonly simNotification: SimNotificationService,
    private readonly configService: ConfigService,
    private readonly simTopUpPricing: SimTopUpPricingService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  private get whmcsBaseUrl(): string {
    return (
      this.configService.get<string>("WHMCS_BASE_URL") ||
      "https://accounts.asolutions.co.jp/includes/api.php"
    );
  }

  private get freebitBaseUrl(): string {
    return this.configService.get<string>("FREEBIT_BASE_URL") || "https://i1.mvno.net/emptool/api";
  }

  /**
   * Top up SIM data quota with payment processing
   * Pricing: 1GB = 500 JPY
   */
  async topUpSim(userId: string, subscriptionId: number, request: SimTopUpRequest): Promise<void> {
    let latestAccount = "";

    await this.simNotification.runWithNotification(
      "Top Up Data",
      {
        baseContext: { userId, subscriptionId, quotaMb: request.quotaMb },
        enrichSuccess: meta => ({
          account: meta.account,
          costJpy: meta.costJpy,
          invoiceId: meta.invoiceId,
          transactionId: meta.transactionId,
        }),
        enrichError: () => ({ account: latestAccount }),
      },
      async () => {
        const result = await this.executeTopUp(userId, subscriptionId, request);
        latestAccount = result.account;
        return result;
      }
    );
  }

  private async executeTopUp(
    userId: string,
    subscriptionId: number,
    request: SimTopUpRequest
  ): Promise<{ account: string; costJpy: number; invoiceId: number; transactionId?: string }> {
    const validation = await this.simValidation.validateSimSubscription(userId, subscriptionId);
    const account = validation.account;

    this.validateQuotaRequest(request);

    const pricing = await this.simTopUpPricing.getTopUpPricing();
    const units = Math.ceil(request.quotaMb / 1000);
    const costJpy = units * pricing.pricePerGbJpy;

    const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(userId);

    this.logger.log(`Starting SIM top-up process for subscription ${subscriptionId}`, {
      userId,
      subscriptionId,
      account,
      quotaMb: request.quotaMb,
      quotaGb: (request.quotaMb / 1000).toFixed(2),
      costJpy,
    });

    const billing = await this.simBilling.createOneTimeCharge({
      clientId: whmcsClientId,
      userId,
      description: `SIM Data Top-up: ${units}GB for ${account}`,
      amountJpy: costJpy,
      currency: "JPY",
      dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
      notes: `Subscription ID: ${subscriptionId}, Phone: ${account}`,
      failureNotesPrefix: "Payment capture failed",
      publicErrorMessage: "SIM top-up failed: payment could not be processed",
      metadata: { subscriptionId },
    });

    const freebitResult = await this.executeFreebitTopUp({
      account,
      quotaMb: request.quotaMb,
      billing,
      userId,
      subscriptionId,
    });

    this.logger.log(`Successfully topped up SIM for subscription ${subscriptionId}`, {
      userId,
      subscriptionId,
      account,
      quotaMb: request.quotaMb,
      costJpy,
      invoiceId: billing.invoice.id,
      transactionId: billing.transactionId,
    });

    await this.sendTopUpNotificationEmail({
      account,
      units,
      costJpy,
      whmcsClientId,
      billing,
      quotaMb: request.quotaMb,
      freebitResult,
    });

    const result: { account: string; costJpy: number; invoiceId: number; transactionId?: string } =
      {
        account,
        costJpy,
        invoiceId: billing.invoice.id,
      };
    if (billing.transactionId) {
      result.transactionId = billing.transactionId;
    }
    return result;
  }

  private validateQuotaRequest(request: SimTopUpRequest): void {
    if (request.quotaMb <= 0) {
      throw new BadRequestException("Quota must be greater than 0MB");
    }
  }

  private async executeFreebitTopUp(params: {
    account: string;
    quotaMb: number;
    billing: { invoice: { id: number; number: string }; transactionId?: string };
    userId: string;
    subscriptionId: number;
  }): Promise<{ resultCode: string; status: { message: string; statusCode: string } }> {
    const { account, quotaMb, billing, userId, subscriptionId } = params;
    try {
      await this.freebitService.topUpSim(account, quotaMb, {});
      return { resultCode: "100", status: { message: "OK", statusCode: "200" } };
    } catch (freebitError) {
      await this.handleFreebitFailureAfterPayment(
        freebitError,
        billing.invoice,
        billing.transactionId || "unknown",
        userId,
        subscriptionId,
        account,
        quotaMb
      );
      return { resultCode: "100", status: { message: "OK", statusCode: "200" } };
    }
  }

  private async sendTopUpNotificationEmail(params: {
    account: string;
    units: number;
    costJpy: number;
    whmcsClientId: number;
    billing: { invoice: { id: number; status: string }; transactionId?: string };
    quotaMb: number;
    freebitResult: { resultCode: string; status: { message: string; statusCode: string } };
  }): Promise<void> {
    const { account, units, costJpy, whmcsClientId, billing, quotaMb, freebitResult } = params;
    const today = new Date();
    const dateStr = today.toISOString().split("T")[0];
    const dueDate = new Date(today.getTime() + 2 * 24 * 60 * 60 * 1000).toISOString().split("T")[0];

    await this.simNotification.sendApiResultsEmail("API results", [
      {
        url: this.whmcsBaseUrl,
        senddata: {
          itemdescription1: `Top-up data (${units}GB)\nSIM Number: ${account}`,
          itemamount1: String(costJpy),
          userid: String(whmcsClientId),
          date: dateStr,
          responsetype: "json",
          itemtaxed1: "1",
          action: "CreateInvoice",
          duedate: dueDate,
          paymentmethod: "stripe",
          sendinvoice: "1",
        },
        result: {
          result: "success",
          invoiceid: billing.invoice.id,
          status: billing.invoice.status,
        },
      },
      {
        url: this.whmcsBaseUrl,
        senddata: { responsetype: "json", action: "CapturePayment", invoiceid: billing.invoice.id },
        result: { result: "success" },
      },
      {
        url: `${this.freebitBaseUrl}/master/addSpec/`,
        json: { quota: quotaMb, kind: "MVNO", account, authKey: "[REDACTED]" },
        result: freebitResult,
      },
    ]);
  }

  /**
   * Handle Freebit API failure after successful payment
   */
  // eslint-disable-next-line max-params -- grouping would reduce clarity for error handling context
  private async handleFreebitFailureAfterPayment(
    freebitError: unknown,
    invoice: { id: number; number: string },
    transactionId: string,
    userId: string,
    subscriptionId: number,
    account: string,
    quotaMb: number
  ): Promise<void> {
    this.logger.error(
      `Freebit API failed after successful payment for subscription ${subscriptionId}`,
      {
        error: extractErrorMessage(freebitError),
        userId,
        subscriptionId,
        account,
        quotaMb,
        invoiceId: invoice.id,
        transactionId,
        paymentCaptured: true,
      }
    );

    // Add a note to the invoice about the Freebit failure
    try {
      await this.simBilling.appendInvoiceNote(
        invoice.id,
        `Payment successful but SIM top-up failed: ${extractErrorMessage(
          freebitError
        )}. Manual intervention required.`
      );

      this.logger.log(`Added failure note to invoice ${invoice.id}`, {
        invoiceId: invoice.id,
        reason: "Freebit API failure after payment",
      });
    } catch (updateError) {
      this.logger.error(`Failed to update invoice ${invoice.id} with failure note`, {
        invoiceId: invoice.id,
        updateError: extractErrorMessage(updateError),
        originalError: extractErrorMessage(freebitError),
      });
    }

    // Refund logic is handled by the caller (via top-up failure handling)
    // Automatic refunds should be implemented at the payment processing layer
    // to ensure consistency across all failure scenarios.
    // For manual refunds, use the WHMCS admin panel or dedicated refund endpoints.

    throw new Error(
      `Payment was processed but SIM data top-up failed. Please contact support with invoice ${invoice.number} for assistance.`
    );
  }
}
