import { getErrorMessage } from "@bff/core/utils/error.util";
import { Logger } from "nestjs-pino";
import { Injectable, Inject } from "@nestjs/common";
import {
  PaymentMethodList,
  PaymentGateway,
  PaymentGatewayList,
  PaymentMethod,
  Providers,
} from "@customer-portal/domain/payments";
import {
  Providers as CatalogProviders,
  type WhmcsCatalogProductNormalized,
} from "@customer-portal/domain/catalog";
import { WhmcsConnectionOrchestratorService } from "../connection/services/whmcs-connection-orchestrator.service";
import { WhmcsCacheService } from "../cache/whmcs-cache.service";
import type { WhmcsCreateSsoTokenParams } from "@customer-portal/domain/customer";
import type { WhmcsGetPayMethodsParams } from "@customer-portal/domain/payments";
import type {
  WhmcsPaymentMethod,
  WhmcsPaymentMethodListResponse,
  WhmcsPaymentGateway,
  WhmcsPaymentGatewayListResponse,
} from "@customer-portal/domain/payments";

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

  /**
   * Get payment methods for a client
   */
  async getPaymentMethods(
    clientId: number,
    userId: string,
    options?: { fresh?: boolean }
  ): Promise<PaymentMethodList> {
    try {
      // Try cache first unless fresh requested
      if (!options?.fresh) {
        const cached = await this.cacheService.getPaymentMethods(userId);
        if (cached) {
          this.logger.debug(`Cache hit for payment methods: user ${userId}`);
          return cached;
        }
      }

      // Fetch pay methods (use the documented WHMCS structure)
      const params: WhmcsGetPayMethodsParams = {
        clientid: clientId,
      };
      const response: WhmcsPaymentMethodListResponse =
        await this.connectionService.getPaymentMethods(params);

      const paymentMethodsArray: WhmcsPaymentMethod[] = Array.isArray(response.paymethods)
        ? response.paymethods
        : [];

      let methods = paymentMethodsArray
        .map((pm: WhmcsPaymentMethod) => {
          try {
            return Providers.Whmcs.transformWhmcsPaymentMethod(pm);
          } catch (error) {
            this.logger.error(`Failed to transform payment method`, {
              error: getErrorMessage(error),
              clientId,
              userId,
            });
            return null;
          }
        })
        .filter((method): method is PaymentMethod => method !== null);

      // Mark the first method as default (per product decision)
      if (methods.length > 0) {
        methods = methods.map((m, index) => ({ ...m, isDefault: index === 0 }));
      }

      const result: PaymentMethodList = { paymentMethods: methods, totalCount: methods.length };
      if (!options?.fresh) {
        await this.cacheService.setPaymentMethods(userId, result);
      }
      return result;
    } catch (error) {
      this.logger.error(`Failed to fetch payment methods for client ${clientId}`, {
        error: getErrorMessage(error),
        userId,
      });
      throw error;
    }
  }

  /**
   * Centralized check used by both UI-aligned code paths and worker validation.
   * Returns true when the transformed list has at least one saved payment method.
   * Pass { fresh: true } to bypass cache for provisioning-time checks.
   */
  async hasPaymentMethod(
    clientId: number,
    userId: string,
    options?: { fresh?: boolean }
  ): Promise<boolean> {
    const list = await this.getPaymentMethods(clientId, userId, options);
    const count = list?.totalCount || 0;
    this.logger.debug("hasPaymentMethod check", { clientId, userId, count });
    return count > 0;
  }

  /**
   * Get available payment gateways
   */
  async getPaymentGateways(): Promise<PaymentGatewayList> {
    try {
      // Try cache first
      const cached = await this.cacheService.getPaymentGateways();
      if (cached) {
        this.logger.debug("Cache hit for payment gateways");
        return cached;
      }

      // Fetch from WHMCS API
      const response: WhmcsPaymentGatewayListResponse =
        await this.connectionService.getPaymentGateways();

      if (!response.gateways?.gateway) {
        this.logger.warn("No payment gateways found");
        return {
          gateways: [],
          totalCount: 0,
        };
      }

      // Transform payment gateways
      const gateways = response.gateways.gateway
        .map((whmcsGateway: WhmcsPaymentGateway) => {
          try {
            return Providers.Whmcs.transformWhmcsPaymentGateway(whmcsGateway);
          } catch (error) {
            this.logger.error(`Failed to transform payment gateway ${whmcsGateway.name}`, {
              error: getErrorMessage(error),
            });
            return null;
          }
        })
        .filter((gateway): gateway is PaymentGateway => gateway !== null);

      const result: PaymentGatewayList = {
        gateways,
        totalCount: gateways.length,
      };

      // Cache the result (cache for 1 hour since gateways don't change often)
      await this.cacheService.setPaymentGateways(result);

      this.logger.log(`Fetched ${gateways.length} payment gateways`);
      return result;
    } catch (error) {
      this.logger.error("Failed to fetch payment gateways", {
        error: getErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Create SSO token with payment method for invoice payment
   * This creates a direct link to the payment form with gateway pre-selected
   */
  async createPaymentSsoToken(
    clientId: number,
    invoiceId: number,
    paymentMethodId?: number,
    gatewayName?: string
  ): Promise<{ url: string; expiresAt: string }> {
    try {
      // Use WHMCS Friendly URL format for direct payment page access
      // This goes directly to the payment page, not just invoice view
      let invoiceUrl = `index.php?rp=/invoice/${invoiceId}/pay`;

      if (paymentMethodId) {
        // Pre-select specific saved payment method
        invoiceUrl += `&paymentmethod=${paymentMethodId}`;
      } else if (gatewayName) {
        // Pre-select specific gateway
        invoiceUrl += `&gateway=${gatewayName}`;
      }

      const params: WhmcsCreateSsoTokenParams = {
        client_id: clientId,
        destination: "sso:custom_redirect",
        sso_redirect_path: invoiceUrl,
      };

      const response = await this.connectionService.createSsoToken(params);

      const url = this.resolveRedirectUrl(response.redirect_url);
      this.debugLogRedirectHost(url);
      const result = {
        url,
        expiresAt: new Date(Date.now() + 60000).toISOString(), // ~60 seconds (per WHMCS spec)
      };

      this.logger.log(`Created payment SSO token for client ${clientId}, invoice ${invoiceId}`, {
        paymentMethodId,
        gatewayName,
        invoiceUrl,
      });
      return result;
    } catch (error) {
      this.logger.error(`Failed to create payment SSO token for client ${clientId}`, {
        error: getErrorMessage(error),
        invoiceId,
        paymentMethodId,
        gatewayName,
      });
      throw error;
    }
  }

  /**
   * Get products catalog
   */
  async getProducts(): Promise<WhmcsCatalogProductNormalized[]> {
    try {
      const response = await this.connectionService.getCatalogProducts();
      return CatalogProviders.Whmcs.transformWhmcsCatalogProductsResponse(response);
    } catch (error) {
      this.logger.error("Failed to get products", {
        error: getErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Invalidate payment methods cache for a user
   */
  async invalidatePaymentMethodsCache(userId: string): Promise<void> {
    try {
      await this.cacheService.invalidatePaymentMethods(userId);
      this.logger.log(`Invalidated payment methods cache for user ${userId}`);
    } catch (error) {
      this.logger.error(`Failed to invalidate payment methods cache for user ${userId}`, {
        error: getErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Normalize WHMCS SSO redirect URLs to absolute using configured base URL.
   */
  private resolveRedirectUrl(redirectUrl: string): string {
    if (!redirectUrl) return redirectUrl;
    const base = this.connectionService.getBaseUrl().replace(/\/+$/, "");
    const isAbsolute = /^https?:\/\//i.test(redirectUrl);
    if (!isAbsolute) {
      const path = redirectUrl.replace(/^\/+/, "");
      return `${base}/${path}`;
    }
    return redirectUrl;
  }

  /**
   * Debug helper: log only the host of the SSO URL (never the token) in non-production.
   */
  private debugLogRedirectHost(url: string): void {
    if (process.env.NODE_ENV === "production") return;
    try {
      const target = new URL(url);
      const base = new URL(this.connectionService.getBaseUrl());
      this.logger.debug("WHMCS Payment SSO redirect host", {
        redirectHost: target.host,
        redirectOrigin: target.origin,
        baseOrigin: base.origin,
      });
    } catch {
      // Ignore parse errors silently
    }
  }
}
