import { Injectable, Inject, BadRequestException, NotFoundException } from "@nestjs/common";
import {
  subscriptionListSchema,
  subscriptionStatusSchema,
  subscriptionStatsSchema,
} from "@customer-portal/domain/subscriptions";
import type {
  Subscription,
  SubscriptionList,
  SubscriptionStatus,
} from "@customer-portal/domain/subscriptions";
import type { Invoice, InvoiceItem, InvoiceList } from "@customer-portal/domain/billing";
import { WhmcsCacheService } from "@bff/integrations/whmcs/cache/whmcs-cache.service.js";
import { WhmcsConnectionFacade } from "@bff/integrations/whmcs/facades/whmcs.facade.js";
import { WhmcsClientService } from "@bff/integrations/whmcs/services/whmcs-client.service.js";
import { WhmcsInvoiceService } from "@bff/integrations/whmcs/services/whmcs-invoice.service.js";
import { WhmcsSubscriptionService } from "@bff/integrations/whmcs/services/whmcs-subscription.service.js";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
import { Logger } from "nestjs-pino";
import { safeOperation, OperationCriticality } from "@bff/core/utils/safe-operation.util.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";

export interface GetSubscriptionsOptions {
  status?: SubscriptionStatus;
}

/**
 * Subscriptions Orchestrator
 *
 * Coordinates subscription management operations across multiple
 * integration services (WHMCS, Salesforce).
 */
@Injectable()
export class SubscriptionsOrchestrator {
  // eslint-disable-next-line max-params -- NestJS DI requires individual constructor injection
  constructor(
    private readonly whmcsSubscriptionService: WhmcsSubscriptionService,
    private readonly whmcsInvoiceService: WhmcsInvoiceService,
    private readonly whmcsClientService: WhmcsClientService,
    private readonly whmcsConnectionService: WhmcsConnectionFacade,
    private readonly cacheService: WhmcsCacheService,
    private readonly mappingsService: MappingsService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Get all subscriptions for a user
   */
  async getSubscriptions(
    userId: string,
    options: GetSubscriptionsOptions = {}
  ): Promise<SubscriptionList> {
    const { status } = options;
    const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(userId);

    return safeOperation(
      async () => {
        const subscriptionList = await this.whmcsSubscriptionService.getSubscriptions(
          whmcsClientId,
          userId,
          status === undefined ? {} : { status }
        );

        const parsed = subscriptionListSchema.parse(subscriptionList);

        let subscriptions = parsed.subscriptions;
        if (status) {
          const normalizedStatus = subscriptionStatusSchema.parse(status);
          subscriptions = subscriptions.filter(sub => sub.status === normalizedStatus);
        }

        return subscriptionListSchema.parse({
          subscriptions,
          totalCount: subscriptions.length,
        });
      },
      {
        criticality: OperationCriticality.CRITICAL,
        context: `Get subscriptions for user ${userId}`,
        logger: this.logger,
        rethrow: [NotFoundException, BadRequestException],
        fallbackMessage: "Failed to retrieve subscriptions",
      }
    );
  }

  /**
   * Get individual subscription by ID
   */
  async getSubscriptionById(userId: string, subscriptionId: number): Promise<Subscription> {
    // Validate subscription ID
    if (!subscriptionId || subscriptionId < 1) {
      throw new BadRequestException("Subscription ID must be a positive number");
    }

    // Get WHMCS client ID from user mapping
    const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(userId);

    return safeOperation(
      async () => {
        const subscription = await this.whmcsSubscriptionService.getSubscriptionById(
          whmcsClientId,
          userId,
          subscriptionId
        );

        this.logger.log(`Retrieved subscription ${subscriptionId} for user ${userId}`, {
          productName: subscription.productName,
          status: subscription.status,
          amount: subscription.amount,
          currency: subscription.currency,
        });

        return subscription;
      },
      {
        criticality: OperationCriticality.CRITICAL,
        context: `Get subscription ${subscriptionId} for user ${userId}`,
        logger: this.logger,
        rethrow: [NotFoundException, BadRequestException],
        fallbackMessage: "Failed to retrieve subscription",
      }
    );
  }

  /**
   * Get active subscriptions for a user
   */
  async getActiveSubscriptions(userId: string): Promise<Subscription[]> {
    try {
      const subscriptionList = await this.getSubscriptions(userId, {
        status: "Active",
      });
      return subscriptionList.subscriptions;
    } catch (error) {
      this.logger.error(`Failed to get active subscriptions for user ${userId}`, {
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Get subscriptions by status
   */
  async getSubscriptionsByStatus(
    userId: string,
    status: SubscriptionStatus
  ): Promise<Subscription[]> {
    try {
      const normalizedStatus = subscriptionStatusSchema.parse(status);
      const subscriptionList = await this.getSubscriptions(userId, { status: normalizedStatus });
      return subscriptionList.subscriptions;
    } catch (error) {
      this.logger.error(`Failed to get ${status} subscriptions for user ${userId}`, {
        error: extractErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Get subscription statistics for a user
   */
  async getSubscriptionStats(userId: string): Promise<{
    total: number;
    active: number;
    completed: number;
    cancelled: number;
  }> {
    return safeOperation(
      async () => {
        const subscriptionList = await this.getSubscriptions(userId);
        const subscriptions: Subscription[] = subscriptionList.subscriptions;

        const stats = {
          total: subscriptions.length,
          active: subscriptions.filter(s => s.status === "Active").length,
          completed: subscriptions.filter(s => s.status === "Completed").length,
          cancelled: subscriptions.filter(s => s.status === "Cancelled").length,
        };

        this.logger.log(`Generated subscription stats for user ${userId}`, stats);

        return subscriptionStatsSchema.parse(stats);
      },
      {
        criticality: OperationCriticality.CRITICAL,
        context: `Generate subscription stats for user ${userId}`,
        logger: this.logger,
        rethrow: [NotFoundException, BadRequestException],
      }
    );
  }

  /**
   * Get subscriptions expiring soon (within next 30 days)
   */
  async getExpiringSoon(userId: string, days: number = 30): Promise<Subscription[]> {
    return safeOperation(
      async () => {
        const subscriptionList = await this.getSubscriptions(userId);
        const subscriptions: Subscription[] = subscriptionList.subscriptions;

        const cutoffDate = new Date();
        cutoffDate.setDate(cutoffDate.getDate() + days);

        const expiringSoon = subscriptions.filter(subscription => {
          if (!subscription.nextDue || subscription.status !== "Active") {
            return false;
          }

          const nextDueDate = new Date(subscription.nextDue);
          return nextDueDate <= cutoffDate;
        });

        this.logger.log(
          `Found ${expiringSoon.length} subscriptions expiring within ${days} days for user ${userId}`
        );
        return expiringSoon;
      },
      {
        criticality: OperationCriticality.CRITICAL,
        context: `Get expiring subscriptions for user ${userId}`,
        logger: this.logger,
        rethrow: [NotFoundException, BadRequestException],
      }
    );
  }

  /**
   * Get recent subscription activity (newly created or status changed)
   */
  async getRecentActivity(userId: string, days: number = 30): Promise<Subscription[]> {
    return safeOperation(
      async () => {
        const subscriptionList = await this.getSubscriptions(userId);
        const subscriptions = subscriptionList.subscriptions;

        const cutoffDate = new Date();
        cutoffDate.setDate(cutoffDate.getDate() - days);

        const recentActivity = subscriptions.filter((subscription: Subscription) => {
          const registrationDate = new Date(subscription.registrationDate);
          return registrationDate >= cutoffDate;
        });

        this.logger.log(
          `Found ${recentActivity.length} recent subscription activities within ${days} days for user ${userId}`
        );
        return recentActivity;
      },
      {
        criticality: OperationCriticality.CRITICAL,
        context: `Get recent subscription activity for user ${userId}`,
        logger: this.logger,
        rethrow: [NotFoundException, BadRequestException],
      }
    );
  }

  /**
   * Search subscriptions by product name or domain
   */
  async searchSubscriptions(userId: string, query: string): Promise<Subscription[]> {
    if (!query || query.trim().length < 2) {
      throw new BadRequestException("Search query must be at least 2 characters long");
    }

    return safeOperation(
      async () => {
        const subscriptionList = await this.getSubscriptions(userId);
        const subscriptions = subscriptionList.subscriptions;

        const searchTerm = query.toLowerCase().trim();
        const matches = subscriptions.filter((subscription: Subscription) => {
          const productName = subscription.productName.toLowerCase();
          const domain = subscription.domain?.toLowerCase() || "";

          return productName.includes(searchTerm) || domain.includes(searchTerm);
        });

        this.logger.log(
          `Found ${matches.length} subscriptions matching query "${query}" for user ${userId}`
        );
        return matches;
      },
      {
        criticality: OperationCriticality.CRITICAL,
        context: `Search subscriptions for user ${userId}`,
        logger: this.logger,
        rethrow: [NotFoundException, BadRequestException],
      }
    );
  }

  /**
   * Get invoices related to a specific subscription
   */
  async getSubscriptionInvoices(
    userId: string,
    subscriptionId: number,
    options: { page?: number; limit?: number } = {}
  ): Promise<InvoiceList> {
    const { page = 1, limit = 10 } = options;

    return safeOperation(
      async () => {
        const cachedResult = await this.tryGetCachedInvoices(userId, subscriptionId, page, limit);
        if (cachedResult) return cachedResult;

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

        const relatedInvoices = await this.fetchAllRelatedInvoices(
          whmcsClientId,
          userId,
          subscriptionId,
          limit
        );

        const result = this.paginateInvoices(relatedInvoices, page, limit);
        await this.cacheInvoiceResults({
          userId,
          subscriptionId,
          page,
          limit,
          result,
          allInvoices: relatedInvoices,
        });

        this.logger.log("Retrieved invoices for subscription", {
          userId,
          subscriptionId,
          count: result.invoices.length,
          totalRelated: relatedInvoices.length,
        });

        return result;
      },
      {
        criticality: OperationCriticality.CRITICAL,
        context: `Get invoices for subscription ${subscriptionId}`,
        logger: this.logger,
        rethrow: [NotFoundException, BadRequestException],
        fallbackMessage: "Failed to retrieve subscription invoices",
      }
    );
  }

  private async tryGetCachedInvoices(
    userId: string,
    subscriptionId: number,
    page: number,
    limit: number
  ): Promise<InvoiceList | null> {
    const cached = await this.cacheService.getSubscriptionInvoices(
      userId,
      subscriptionId,
      page,
      limit
    );
    if (cached) {
      this.logger.debug("Cache hit for subscription invoices", { userId, subscriptionId });
      return cached;
    }

    const cachedAll = await this.cacheService.getSubscriptionInvoicesAll(userId, subscriptionId);
    if (cachedAll) {
      const result = this.paginateInvoices(cachedAll, page, limit);
      await this.cacheService.setSubscriptionInvoices(userId, subscriptionId, page, limit, result);
      return result;
    }

    return null;
  }

  private async fetchAllRelatedInvoices(
    whmcsClientId: number,
    userId: string,
    subscriptionId: number,
    limit: number
  ): Promise<Invoice[]> {
    const batchSize = Math.min(100, Math.max(limit, 25));
    const relatedInvoices: Invoice[] = [];
    let currentPage = 1;
    let totalPages = 1;

    do {
      // eslint-disable-next-line no-await-in-loop -- Sequential pagination required by WHMCS API
      const invoiceBatch = await this.whmcsInvoiceService.getInvoicesWithItems(
        whmcsClientId,
        userId,
        { page: currentPage, limit: batchSize }
      );

      totalPages = invoiceBatch.pagination.totalPages;

      for (const invoice of invoiceBatch.invoices) {
        if (!invoice.items?.length) continue;
        const hasMatch = invoice.items.some(
          (item: InvoiceItem) => item.serviceId === subscriptionId
        );
        if (hasMatch) relatedInvoices.push(invoice);
      }

      currentPage += 1;
    } while (currentPage <= totalPages);

    return relatedInvoices;
  }

  private paginateInvoices(invoices: Invoice[], page: number, limit: number): InvoiceList {
    const startIndex = (page - 1) * limit;
    const paginatedInvoices = invoices.slice(startIndex, startIndex + limit);

    return {
      invoices: paginatedInvoices,
      pagination: {
        page,
        totalPages: invoices.length === 0 ? 0 : Math.ceil(invoices.length / limit),
        totalItems: invoices.length,
      },
    };
  }

  private async cacheInvoiceResults(options: {
    userId: string;
    subscriptionId: number;
    page: number;
    limit: number;
    result: InvoiceList;
    allInvoices: Invoice[];
  }): Promise<void> {
    const { userId, subscriptionId, page, limit, result, allInvoices } = options;
    await this.cacheService.setSubscriptionInvoices(userId, subscriptionId, page, limit, result);
    await this.cacheService.setSubscriptionInvoicesAll(userId, subscriptionId, allInvoices);
  }

  /**
   * Invalidate subscription cache for a user
   */
  async invalidateCache(userId: string, subscriptionId?: number): Promise<void> {
    try {
      if (subscriptionId) {
        await this.whmcsSubscriptionService.invalidateSubscriptionCache(userId, subscriptionId);
      } else {
        await this.whmcsClientService.invalidateUserCache(userId);
      }

      const subscriptionSuffix = subscriptionId ? `, subscription ${subscriptionId}` : "";
      this.logger.log(`Invalidated subscription cache for user ${userId}${subscriptionSuffix}`);
    } catch (error) {
      this.logger.error(`Failed to invalidate subscription cache for user ${userId}`, {
        error: extractErrorMessage(error),
        subscriptionId,
      });
    }
  }

  /**
   * Health check for subscription service
   */
  async healthCheck(): Promise<{ status: string; details: unknown }> {
    try {
      const whmcsHealthy = await this.whmcsConnectionService.healthCheck();

      return {
        status: whmcsHealthy ? "healthy" : "unhealthy",
        details: {
          whmcsApi: whmcsHealthy ? "connected" : "disconnected",
          timestamp: new Date().toISOString(),
        },
      };
    } catch (error) {
      this.logger.error("Subscription service health check failed", {
        error: extractErrorMessage(error),
      });
      return {
        status: "unhealthy",
        details: {
          error: extractErrorMessage(error),
          timestamp: new Date().toISOString(),
        },
      };
    }
  }
}
