import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { getErrorMessage } from "@bff/core/utils/error.util";
import { CacheService } from "@bff/infra/cache/cache.service";
import { Invoice, InvoiceList } from "@customer-portal/domain/billing";
import { Subscription, SubscriptionList } from "@customer-portal/domain/subscriptions";
import { PaymentMethodList, PaymentGatewayList } from "@customer-portal/domain/payments";
import type { WhmcsClient } from "@customer-portal/domain/customer";

export interface CacheOptions {
  ttl?: number;
  tags?: string[];
}

export interface CacheKeyConfig {
  prefix: string;
  ttl: number;
  tags: string[];
}

@Injectable()
export class WhmcsCacheService {
  // Cache configuration for different data types
  private readonly cacheConfigs: Record<string, CacheKeyConfig> = {
    invoices: {
      prefix: "whmcs:invoices",
      ttl: 90, // 90 seconds - invoices change frequently
      tags: ["invoices", "billing"],
    },
    invoice: {
      prefix: "whmcs:invoice",
      ttl: 300, // 5 minutes - individual invoices change less frequently
      tags: ["invoice", "billing"],
    },
    subscriptions: {
      prefix: "whmcs:subscriptions",
      ttl: 300, // 5 minutes - subscriptions change less frequently
      tags: ["subscriptions", "services"],
    },
    subscription: {
      prefix: "whmcs:subscription",
      ttl: 600, // 10 minutes - individual subscriptions rarely change
      tags: ["subscription", "services"],
    },
    client: {
      prefix: "whmcs:client",
      ttl: 1800, // 30 minutes - client data rarely changes
      tags: ["client", "user"],
    },
    sso: {
      prefix: "whmcs:sso",
      ttl: 3600, // 1 hour - SSO tokens have their own expiry
      tags: ["sso", "auth"],
    },
    paymentMethods: {
      prefix: "whmcs:paymentmethods",
      ttl: 900, // 15 minutes - payment methods change occasionally
      tags: ["paymentmethods", "billing"],
    },
    paymentGateways: {
      prefix: "whmcs:paymentgateways",
      ttl: 3600, // 1 hour - payment gateways rarely change
      tags: ["paymentgateways", "config"],
    },
  };

  constructor(
    private readonly cacheService: CacheService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Get cached invoices list for a user
   */
  async getInvoicesList(
    userId: string,
    page: number,
    limit: number,
    status?: string
  ): Promise<InvoiceList | null> {
    const key = this.buildInvoicesKey(userId, page, limit, status);
    return this.get<InvoiceList>(key, "invoices");
  }

  /**
   * Cache invoices list for a user
   */
  async setInvoicesList(
    userId: string,
    page: number,
    limit: number,
    status: string | undefined,
    data: InvoiceList
  ): Promise<void> {
    const key = this.buildInvoicesKey(userId, page, limit, status);
    await this.set(key, data, "invoices", [`user:${userId}`]);
  }

  /**
   * Get cached individual invoice
   */
  async getInvoice(userId: string, invoiceId: number): Promise<Invoice | null> {
    const key = this.buildInvoiceKey(userId, invoiceId);
    return this.get<Invoice>(key, "invoice");
  }

  /**
   * Cache individual invoice
   */
  async setInvoice(userId: string, invoiceId: number, data: Invoice): Promise<void> {
    const key = this.buildInvoiceKey(userId, invoiceId);
    await this.set(key, data, "invoice", [`user:${userId}`, `invoice:${invoiceId}`]);
  }

  /**
   * Get cached subscriptions list for a user
   */
  async getSubscriptionsList(userId: string): Promise<SubscriptionList | null> {
    const key = this.buildSubscriptionsKey(userId);
    return this.get<SubscriptionList>(key, "subscriptions");
  }

  /**
   * Cache subscriptions list for a user
   */
  async setSubscriptionsList(userId: string, data: SubscriptionList): Promise<void> {
    const key = this.buildSubscriptionsKey(userId);
    await this.set(key, data, "subscriptions", [`user:${userId}`]);
  }

  /**
   * Get cached individual subscription
   */
  async getSubscription(userId: string, subscriptionId: number): Promise<Subscription | null> {
    const key = this.buildSubscriptionKey(userId, subscriptionId);
    return this.get<Subscription>(key, "subscription");
  }

  /**
   * Cache individual subscription
   */
  async setSubscription(userId: string, subscriptionId: number, data: Subscription): Promise<void> {
    const key = this.buildSubscriptionKey(userId, subscriptionId);
    await this.set(key, data, "subscription", [`user:${userId}`, `subscription:${subscriptionId}`]);
  }

  /**
   * Get cached client data
   * Returns WhmcsClient (type inferred from domain)
   */
  async getClientData(clientId: number): Promise<WhmcsClient | null> {
    const key = this.buildClientKey(clientId);
    return this.get<WhmcsClient>(key, "client");
  }

  /**
   * Cache client data
   */
  async setClientData(clientId: number, data: WhmcsClient) {
    const key = this.buildClientKey(clientId);
    await this.set(key, data, "client", [`client:${clientId}`]);
  }

  /**
   * Invalidate all cache for a specific user
   */
  async invalidateUserCache(userId: string): Promise<void> {
    try {
      const patterns = [
        `${this.cacheConfigs.invoices.prefix}:${userId}:*`,
        `${this.cacheConfigs.invoice.prefix}:${userId}:*`,
        `${this.cacheConfigs.subscriptions.prefix}:${userId}:*`,
        `${this.cacheConfigs.subscription.prefix}:${userId}:*`,
      ];

      await Promise.all(patterns.map(pattern => this.cacheService.delPattern(pattern)));

      this.logger.log(`Invalidated all cache for user ${userId}`);
    } catch (error) {
      this.logger.error(`Failed to invalidate cache for user ${userId}`, {
        error: getErrorMessage(error),
      });
    }
  }

  /**
   * Invalidate client data cache for a specific client
   */
  async invalidateClientCache(clientId: number): Promise<void> {
    try {
      const key = this.buildClientKey(clientId);
      await this.cacheService.del(key);

      this.logger.log(`Invalidated client cache for client ${clientId}`);
    } catch (error) {
      this.logger.error(`Failed to invalidate client cache for client ${clientId}`, {
        error: getErrorMessage(error),
      });
    }
  }

  /**
   * Invalidate cache by tags
   */
  async invalidateByTag(tag: string): Promise<void> {
    try {
      // This would require a more sophisticated cache implementation with tag support
      // For now, we'll use pattern-based invalidation
      const patterns = Object.values(this.cacheConfigs)
        .filter(config => config.tags.includes(tag))
        .map(config => `${config.prefix}:*`);

      await Promise.all(patterns.map(pattern => this.cacheService.delPattern(pattern)));

      this.logger.log(`Invalidated cache by tag: ${tag}`);
    } catch (error) {
      this.logger.error(`Failed to invalidate cache by tag ${tag}`, {
        error: getErrorMessage(error),
      });
    }
  }

  /**
   * Invalidate specific invoice cache
   */
  async invalidateInvoice(userId: string, invoiceId: number): Promise<void> {
    try {
      const specificKey = this.buildInvoiceKey(userId, invoiceId);
      const listPattern = `${this.cacheConfigs.invoices.prefix}:${userId}:*`;

      await Promise.all([
        this.cacheService.del(specificKey),
        this.cacheService.delPattern(listPattern),
      ]);

      this.logger.log(`Invalidated invoice cache for user ${userId}, invoice ${invoiceId}`);
    } catch (error) {
      this.logger.error(
        `Failed to invalidate invoice cache for user ${userId}, invoice ${invoiceId}`,
        { error: getErrorMessage(error) }
      );
    }
  }

  /**
   * Invalidate specific subscription cache
   */
  async invalidateSubscription(userId: string, subscriptionId: number): Promise<void> {
    try {
      const specificKey = this.buildSubscriptionKey(userId, subscriptionId);
      const listKey = this.buildSubscriptionsKey(userId);

      await Promise.all([this.cacheService.del(specificKey), this.cacheService.del(listKey)]);

      this.logger.log(
        `Invalidated subscription cache for user ${userId}, subscription ${subscriptionId}`
      );
    } catch (error) {
      this.logger.error(
        `Failed to invalidate subscription cache for user ${userId}, subscription ${subscriptionId}`,
        { error: getErrorMessage(error) }
      );
    }
  }

  /**
   * Get cached payment methods for a user
   */
  async getPaymentMethods(userId: string): Promise<PaymentMethodList | null> {
    const key = this.buildPaymentMethodsKey(userId);
    return this.get<PaymentMethodList>(key, "paymentMethods");
  }

  /**
   * Set payment methods cache for a user
   */
  async setPaymentMethods(userId: string, paymentMethods: PaymentMethodList): Promise<void> {
    const key = this.buildPaymentMethodsKey(userId);
    await this.set(key, paymentMethods, "paymentMethods", [userId]);
  }

  /**
   * Get cached payment gateways (global)
   */
  async getPaymentGateways(): Promise<PaymentGatewayList | null> {
    const key = "whmcs:paymentgateways:global";
    return this.get<PaymentGatewayList>(key, "paymentGateways");
  }

  /**
   * Set payment gateways cache (global)
   */
  async setPaymentGateways(paymentGateways: PaymentGatewayList): Promise<void> {
    const key = "whmcs:paymentgateways:global";
    await this.set(key, paymentGateways, "paymentGateways");
  }

  /**
   * Invalidate payment methods cache for a user
   */
  async invalidatePaymentMethods(userId: string): Promise<void> {
    try {
      const key = this.buildPaymentMethodsKey(userId);
      await this.cacheService.del(key);
      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);
    }
  }

  /**
   * Invalidate payment gateways cache (global)
   */
  async invalidatePaymentGateways(): Promise<void> {
    try {
      const key = "whmcs:paymentgateways:global";
      await this.cacheService.del(key);
      this.logger.log("Invalidated payment gateways cache");
    } catch (error) {
      this.logger.error("Failed to invalidate payment gateways cache", error);
    }
  }

  /**
   * Generic get method with configuration
   */
  private async get<T>(key: string, _configKey: string): Promise<T | null> {
    try {
      const data = await this.cacheService.get<T>(key);
      if (data) {
        this.logger.debug(`Cache hit: ${key}`);
      }
      return data;
    } catch (error) {
      this.logger.error(`Cache get error for key ${key}`, { error: getErrorMessage(error) });
      return null;
    }
  }

  /**
   * Generic set method with configuration
   */
  private async set<T>(
    key: string,
    data: T,
    _configKey: string,
    _additionalTags: string[] = []
  ): Promise<void> {
    try {
      const config = this.cacheConfigs[_configKey];
      await this.cacheService.set(key, data, config.ttl);
      this.logger.debug(`Cache set: ${key} (TTL: ${config.ttl}s)`);
    } catch (error) {
      this.logger.error(`Cache set error for key ${key}`, { error: getErrorMessage(error) });
    }
  }

  /**
   * Build cache key for invoices list
   */
  private buildInvoicesKey(userId: string, page: number, limit: number, status?: string): string {
    return `${this.cacheConfigs.invoices.prefix}:${userId}:${page}:${limit}:${status || "all"}`;
  }

  /**
   * Build cache key for individual invoice
   */
  private buildInvoiceKey(userId: string, invoiceId: number): string {
    return `${this.cacheConfigs.invoice.prefix}:${userId}:${invoiceId}`;
  }

  /**
   * Build cache key for subscriptions list
   */
  private buildSubscriptionsKey(userId: string): string {
    return `${this.cacheConfigs.subscriptions.prefix}:${userId}`;
  }

  /**
   * Build cache key for individual subscription
   */
  private buildSubscriptionKey(userId: string, subscriptionId: number): string {
    return `${this.cacheConfigs.subscription.prefix}:${userId}:${subscriptionId}`;
  }

  /**
   * Build cache key for client data
   */
  private buildClientKey(clientId: number): string {
    return `${this.cacheConfigs.client.prefix}:${clientId}`;
  }

  /**
   * Build cache key for payment methods
   */
  private buildPaymentMethodsKey(userId: string): string {
    return `${this.cacheConfigs.paymentMethods.prefix}:${userId}`;
  }

  /**
   * Get cache statistics
   */
  getCacheStats(): {
    totalKeys: number;
    keysByType: Record<string, number>;
  } {
    // This would require Redis SCAN or similar functionality
    // For now, return a placeholder
    return {
      totalKeys: 0,
      keysByType: {},
    };
  }

  /**
   * Clear all WHMCS cache
   */
  async clearAllCache(): Promise<void> {
    try {
      const patterns = Object.values(this.cacheConfigs).map(config => `${config.prefix}:*`);
      await Promise.all(patterns.map(pattern => this.cacheService.delPattern(pattern)));
      this.logger.warn("Cleared all WHMCS cache");
    } catch (error) {
      this.logger.error("Failed to clear all WHMCS cache", { error: getErrorMessage(error) });
    }
  }
}
