import { getErrorMessage } from "@bff/core/utils/error.util";
import { Logger } from "nestjs-pino";
import { Injectable, NotFoundException, Inject } from "@nestjs/common";
import { WhmcsOperationException } from "@bff/core/exceptions/domain-exceptions";
import {
  Invoice,
  InvoiceList,
  invoiceListSchema,
  invoiceSchema,
  Providers,
} from "@customer-portal/domain/billing";
import { WhmcsConnectionOrchestratorService } from "../connection/services/whmcs-connection-orchestrator.service";
import { WhmcsCurrencyService } from "./whmcs-currency.service";
import { WhmcsCacheService } from "../cache/whmcs-cache.service";
import type {
  WhmcsGetInvoicesParams,
  WhmcsCreateInvoiceParams,
  WhmcsUpdateInvoiceParams,
  WhmcsCapturePaymentParams,
} from "@customer-portal/domain/billing";
import type {
  WhmcsInvoiceListResponse,
  WhmcsInvoiceResponse,
  WhmcsCreateInvoiceResponse,
  WhmcsUpdateInvoiceResponse,
  WhmcsCapturePaymentResponse,
} from "@customer-portal/domain/billing";

export type InvoiceFilters = Partial<{
  status: "Paid" | "Unpaid" | "Cancelled" | "Overdue" | "Collections";
  page: number;
  limit: number;
}>;

@Injectable()
export class WhmcsInvoiceService {
  constructor(
    @Inject(Logger) private readonly logger: Logger,
    private readonly connectionService: WhmcsConnectionOrchestratorService,
    private readonly currencyService: WhmcsCurrencyService,
    private readonly cacheService: WhmcsCacheService
  ) {}

  /**
   * Get paginated invoices for a client with caching
   */
  async getInvoices(
    clientId: number,
    userId: string,
    filters: InvoiceFilters = {}
  ): Promise<InvoiceList> {
    const { status, page = 1, limit = 10 } = filters;

    try {
      // Try cache first
      const cached = await this.cacheService.getInvoicesList(userId, page, limit, status);
      if (cached) {
        this.logger.debug(`Cache hit for invoices: user ${userId}, page ${page}`);
        return cached;
      }

      // Calculate pagination for WHMCS API
      const limitstart = (page - 1) * limit;

      // Fetch from WHMCS API
      const params: WhmcsGetInvoicesParams = {
        userid: clientId, // WHMCS API uses 'userid' parameter, not 'clientid'
        limitstart,
        limitnum: limit,
        orderby: "date",
        order: "DESC",
        ...(status && { status: status as WhmcsGetInvoicesParams["status"] }),
      };

      const response: WhmcsInvoiceListResponse = await this.connectionService.getInvoices(params);
      const transformed = this.transformInvoicesResponse(response, clientId, page, limit);

      const result = invoiceListSchema.parse(transformed as unknown);

      // Cache the result
      await this.cacheService.setInvoicesList(userId, page, limit, status, result);

      this.logger.log(
        `Fetched ${result.invoices.length} invoices for client ${clientId}, page ${page}`
      );
      return result;
    } catch (error) {
      this.logger.error(`Failed to fetch invoices for client ${clientId}`, {
        error: getErrorMessage(error),
        filters,
      });
      throw error;
    }
  }

  /**
   * Get invoices with items (for subscription linking)
   * This method fetches invoices and then enriches them with item details
   */
  async getInvoicesWithItems(
    clientId: number,
    userId: string,
    filters: InvoiceFilters = {}
  ): Promise<InvoiceList> {
    try {
      // First get the basic invoices list
      const invoiceList = await this.getInvoices(clientId, userId, filters);

      // For each invoice, fetch the detailed version with items
      const invoicesWithItems = await Promise.all(
        invoiceList.invoices.map(async (invoice: Invoice) => {
          try {
            // Get detailed invoice with items
            const detailedInvoice = await this.getInvoiceById(clientId, userId, invoice.id);
            return invoiceSchema.parse(detailedInvoice);
          } catch (error) {
            this.logger.warn(
              `Failed to fetch details for invoice ${invoice.id}`,
              getErrorMessage(error)
            );
            // Return the basic invoice if detailed fetch fails
            return invoice;
          }
        })
      );

      const result: InvoiceList = {
        invoices: invoicesWithItems,
        pagination: invoiceList.pagination,
      };

      this.logger.log(
        `Fetched ${invoicesWithItems.length} invoices with items for client ${clientId}`
      );
      return result;
    } catch (error) {
      this.logger.error(`Failed to fetch invoices with items for client ${clientId}`, {
        error: getErrorMessage(error),
        filters,
      });
      throw error;
    }
  }

  /**
   * Get individual invoice by ID with caching
   */
  async getInvoiceById(clientId: number, userId: string, invoiceId: number): Promise<Invoice> {
    try {
      // Try cache first
      const cached = await this.cacheService.getInvoice(userId, invoiceId);
      if (cached) {
        this.logger.debug(`Cache hit for invoice: user ${userId}, invoice ${invoiceId}`);
        return cached;
      }

      // Fetch from WHMCS API
      const response: WhmcsInvoiceResponse = await this.connectionService.getInvoice(invoiceId);

      if (!response.invoiceid) {
        throw new NotFoundException(`Invoice ${invoiceId} not found`);
      }

      // Verify the invoice belongs to this client
      const invoiceClientId = response.userid;
      if (invoiceClientId !== clientId) {
        throw new NotFoundException(`Invoice ${invoiceId} not found`);
      }

      // Transform invoice using domain mapper
      const defaultCurrency = this.currencyService.getDefaultCurrency();
      const invoice = Providers.Whmcs.transformWhmcsInvoice(response, {
        defaultCurrencyCode: defaultCurrency.code,
        defaultCurrencySymbol: defaultCurrency.prefix || defaultCurrency.suffix,
      });

      const parseResult = invoiceSchema.safeParse(invoice);
      if (!parseResult.success) {
        throw new WhmcsOperationException("Invalid invoice data after transformation", {
          invoiceId: invoice.id,
          validationErrors: parseResult.error.issues,
        });
      }

      // Cache the result
      await this.cacheService.setInvoice(userId, invoiceId, invoice);

      this.logger.log(`Fetched invoice ${invoiceId} for client ${clientId}`);
      return invoice;
    } catch (error) {
      this.logger.error(`Failed to fetch invoice ${invoiceId} for client ${clientId}`, {
        error: getErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Invalidate cache for a specific invoice
   */
  async invalidateInvoiceCache(userId: string, invoiceId: number): Promise<void> {
    await this.cacheService.invalidateInvoice(userId, invoiceId);
    this.logger.log(`Invalidated invoice cache for user ${userId}, invoice ${invoiceId}`);
  }

  private transformInvoicesResponse(
    response: WhmcsInvoiceListResponse,
    clientId: number,
    page: number,
    limit: number
  ): InvoiceList {
    if (!response.invoices?.invoice) {
      this.logger.warn(`No invoices found for client ${clientId}`);
      return {
        invoices: [],
        pagination: {
          page,
          totalPages: 0,
          totalItems: 0,
        },
      } satisfies InvoiceList;
    }

    const invoiceRecords = response.invoices.invoice;

    const invoices: Invoice[] = [];
    for (const whmcsInvoice of invoiceRecords) {
      try {
        // Transform using domain mapper
        const defaultCurrency = this.currencyService.getDefaultCurrency();
        const transformed = Providers.Whmcs.transformWhmcsInvoice(whmcsInvoice, {
          defaultCurrencyCode: defaultCurrency.code,
          defaultCurrencySymbol: defaultCurrency.prefix || defaultCurrency.suffix,
        });
        const parsed = invoiceSchema.parse(transformed as unknown);
        invoices.push(parsed);
      } catch (error) {
        this.logger.error(`Failed to transform WHMCS invoice ${whmcsInvoice?.id ?? "unknown"}`, {
          error: getErrorMessage(error),
          rawInvoice: whmcsInvoice,
        });
      }
    }

    this.logger.debug(`WHMCS GetInvoices Response Analysis for Client ${clientId}:`, {
      totalresults: response.totalresults,
      numreturned: response.numreturned,
      startnumber: response.startnumber,
      actualInvoicesReturned: invoices.length,
      requestParams: {
        userid: clientId,
        limitstart: (page - 1) * limit,
        limitnum: limit,
        orderby: "date",
        order: "DESC",
      },
    });

    const totalItems = response.totalresults || 0;
    const totalPages = Math.ceil(totalItems / limit);

    return {
      invoices,
      pagination: {
        page,
        totalPages,
        totalItems,
        nextCursor: page < totalPages ? (page + 1).toString() : undefined,
      },
    } satisfies InvoiceList;
  }

  // ========================================
  // Invoice Creation and Payment Methods (Used by SIM/Order services)
  // ========================================

  /**
   * Create a new invoice for a client
   */
  async createInvoice(params: {
    clientId: number;
    description: string;
    amount: number;
    currency?: string;
    dueDate?: Date;
    notes?: string;
  }): Promise<{ id: number; number: string; total: number; status: string }> {
    try {
      const dueDateStr = params.dueDate
        ? params.dueDate.toISOString().split("T")[0]
        : new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString().split("T")[0]; // 7 days from now

      const whmcsParams: WhmcsCreateInvoiceParams = {
        userid: params.clientId,
        status: "Unpaid",
        sendnotification: false, // Don't send email notification automatically
        duedate: dueDateStr,
        notes: params.notes,
        itemdescription1: params.description,
        itemamount1: params.amount,
        itemtaxed1: false, // No tax for data top-ups for now
      };

      const response: WhmcsCreateInvoiceResponse =
        await this.connectionService.createInvoice(whmcsParams);

      if (response.result !== "success") {
        throw new WhmcsOperationException(`WHMCS invoice creation failed: ${response.message}`, {
          clientId: params.clientId,
        });
      }

      this.logger.log(`Created WHMCS invoice ${response.invoiceid} for client ${params.clientId}`, {
        invoiceId: response.invoiceid,
        amount: params.amount,
        description: params.description,
      });

      return {
        id: response.invoiceid,
        number: `INV-${response.invoiceid}`,
        total: params.amount,
        status: response.status,
      };
    } catch (error) {
      this.logger.error(`Failed to create invoice for client ${params.clientId}`, {
        error: getErrorMessage(error),
        params,
      });
      throw error;
    }
  }

  /**
   * Update an existing invoice
   */
  async updateInvoice(params: {
    invoiceId: number;
    status?:
      | "Draft"
      | "Unpaid"
      | "Paid"
      | "Cancelled"
      | "Refunded"
      | "Collections"
      | "Payment Pending"
      | "Overdue";
    dueDate?: Date;
    notes?: string;
  }): Promise<{ success: boolean; message?: string }> {
    try {
      let statusForUpdate: WhmcsUpdateInvoiceParams["status"];
      if (params.status === "Payment Pending") {
        statusForUpdate = "Unpaid";
      } else {
        statusForUpdate = params.status;
      }

      const whmcsParams: WhmcsUpdateInvoiceParams = {
        invoiceid: params.invoiceId,
        status: statusForUpdate,
        duedate: params.dueDate ? params.dueDate.toISOString().split("T")[0] : undefined,
        notes: params.notes,
      };

      const response: WhmcsUpdateInvoiceResponse =
        await this.connectionService.updateInvoice(whmcsParams);

      if (response.result !== "success") {
        throw new WhmcsOperationException(`WHMCS invoice update failed: ${response.message}`, {
          invoiceId: params.invoiceId,
        });
      }

      this.logger.log(`Updated WHMCS invoice ${params.invoiceId}`, {
        invoiceId: params.invoiceId,
        status: params.status,
        notes: params.notes,
      });

      return {
        success: true,
        message: response.message,
      };
    } catch (error) {
      this.logger.error(`Failed to update invoice ${params.invoiceId}`, {
        error: getErrorMessage(error),
        params,
      });
      throw error;
    }
  }

  /**
   * Capture payment for an invoice using the client's default payment method
   */
  async capturePayment(params: {
    invoiceId: number;
    amount: number;
    currency?: string;
  }): Promise<{ success: boolean; transactionId?: string; error?: string }> {
    try {
      const whmcsParams: WhmcsCapturePaymentParams = {
        invoiceid: params.invoiceId,
      };

      const response: WhmcsCapturePaymentResponse =
        await this.connectionService.capturePayment(whmcsParams);

      if (response.result === "success") {
        this.logger.log(`Successfully captured payment for invoice ${params.invoiceId}`, {
          invoiceId: params.invoiceId,
          transactionId: response.transactionid,
          amount: response.amount,
        });

        // Invalidate invoice cache since status changed
        await this.cacheService.invalidateInvoice(`invoice-${params.invoiceId}`, params.invoiceId);

        return {
          success: true,
          transactionId: response.transactionid,
        };
      } else {
        this.logger.warn(`Payment capture failed for invoice ${params.invoiceId}`, {
          invoiceId: params.invoiceId,
          error: response.message || response.error,
        });

        // Return user-friendly error message instead of technical API error
        const userFriendlyError = this.getUserFriendlyPaymentError(
          response.message || response.error || "Unknown payment error"
        );

        return {
          success: false,
          error: userFriendlyError,
        };
      }
    } catch (error) {
      this.logger.error(`Failed to capture payment for invoice ${params.invoiceId}`, {
        error: getErrorMessage(error),
        params,
      });

      // Return user-friendly error message for exceptions
      const userFriendlyError = this.getUserFriendlyPaymentError(getErrorMessage(error));

      return {
        success: false,
        error: userFriendlyError,
      };
    }
  }

  /**
   * Convert technical payment errors to user-friendly messages
   */
  private getUserFriendlyPaymentError(technicalError: string): string {
    if (!technicalError) {
      return "Unable to process payment. Please try again or contact support.";
    }

    const errorLower = technicalError.toLowerCase();

    // WHMCS API permission errors
    if (errorLower.includes("invalid permissions") || errorLower.includes("not allowed")) {
      return "Payment processing is temporarily unavailable. Please contact support for assistance.";
    }

    // Authentication/authorization errors
    if (
      errorLower.includes("unauthorized") ||
      errorLower.includes("forbidden") ||
      errorLower.includes("403")
    ) {
      return "Payment processing is temporarily unavailable. Please contact support for assistance.";
    }

    // Network/timeout errors
    if (
      errorLower.includes("timeout") ||
      errorLower.includes("network") ||
      errorLower.includes("connection")
    ) {
      return "Payment processing timed out. Please try again.";
    }

    // Payment method errors
    if (
      errorLower.includes("payment method") ||
      errorLower.includes("card") ||
      errorLower.includes("insufficient funds")
    ) {
      return "Unable to process payment with your current payment method. Please check your payment details or try a different method.";
    }

    // Generic API errors
    if (errorLower.includes("api") || errorLower.includes("http") || errorLower.includes("error")) {
      return "Payment processing failed. Please try again or contact support if the issue persists.";
    }

    // Default fallback
    return "Unable to process payment. Please try again or contact support.";
  }
}
