import { getErrorMessage } from "@bff/core/utils/error.util";
import { Injectable, NotFoundException, Inject, BadRequestException } from "@nestjs/common";
import {
  Subscription,
  SubscriptionList,
  subscriptionListSchema,
  subscriptionStatusSchema,
  subscriptionStatsSchema,
  type SubscriptionStatus,
} from "@customer-portal/domain/subscriptions";
import type { Invoice, InvoiceItem, InvoiceList } from "@customer-portal/domain/billing";
import { WhmcsService } from "@bff/integrations/whmcs/whmcs.service";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service";
import { Logger } from "nestjs-pino";
import type { Providers } from "@customer-portal/domain/subscriptions";

type WhmcsProduct = Providers.WhmcsRaw.WhmcsProductRaw;

export interface GetSubscriptionsOptions {
  status?: SubscriptionStatus;
}

@Injectable()
export class SubscriptionsService {
  constructor(
    private readonly whmcsService: WhmcsService,
    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;

    try {
      const mapping = await this.mappingsService.findByUserId(userId);
      if (!mapping?.whmcsClientId) {
        throw new NotFoundException("WHMCS client mapping not found");
      }

      const subscriptionList = await this.whmcsService.getSubscriptions(
        mapping.whmcsClientId,
        userId,
        { 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,
      });
    } catch (error) {
      this.logger.error(`Failed to get subscriptions for user ${userId}`, {
        error: getErrorMessage(error),
        options,
      });

      if (error instanceof NotFoundException) {
        throw error;
      }

      throw new Error(`Failed to retrieve subscriptions: ${getErrorMessage(error)}`);
    }
  }

  /**
   * Get individual subscription by ID
   */
  async getSubscriptionById(userId: string, subscriptionId: number): Promise<Subscription> {
    try {
      // 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 mapping = await this.mappingsService.findByUserId(userId);
      if (!mapping?.whmcsClientId) {
        throw new NotFoundException("WHMCS client mapping not found");
      }

      // Fetch subscription from WHMCS
      const subscription = await this.whmcsService.getSubscriptionById(
        mapping.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;
    } catch (error) {
      this.logger.error(`Failed to get subscription ${subscriptionId} for user ${userId}`, {
        error: getErrorMessage(error),
      });

      if (error instanceof NotFoundException || error instanceof BadRequestException) {
        throw error;
      }

      throw new Error(`Failed to retrieve subscription: ${getErrorMessage(error)}`);
    }
  }

  /**
   * 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: getErrorMessage(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: getErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Get suspended subscriptions for a user
   */
  async getSuspendedSubscriptions(userId: string): Promise<Subscription[]> {
    return this.getSubscriptionsByStatus(userId, "Suspended");
  }

  /**
   * Get cancelled subscriptions for a user
   */
  async getCancelledSubscriptions(userId: string): Promise<Subscription[]> {
    return this.getSubscriptionsByStatus(userId, "Cancelled");
  }

  /**
   * Get pending subscriptions for a user
   */
  async getPendingSubscriptions(userId: string): Promise<Subscription[]> {
    return this.getSubscriptionsByStatus(userId, "Pending");
  }

  /**
   * Get subscription statistics for a user
   */
  async getSubscriptionStats(userId: string): Promise<{
    total: number;
    active: number;
    completed: number;
    cancelled: number;
  }> {
    try {
      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);
    } catch (error) {
      this.logger.error(`Failed to generate subscription stats for user ${userId}`, {
        error: getErrorMessage(error),
      });
      throw error;
    }
  }

  /**
   * Get subscriptions expiring soon (within next 30 days)
   */
  async getExpiringSoon(userId: string, days: number = 30): Promise<Subscription[]> {
    try {
      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;
    } catch (error) {
      this.logger.error(`Failed to get expiring subscriptions for user ${userId}`, {
        error: getErrorMessage(error),
        days,
      });
      throw error;
    }
  }

  /**
   * Get recent subscription activity (newly created or status changed)
   */
  async getRecentActivity(userId: string, days: number = 30): Promise<Subscription[]> {
    try {
      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;
    } catch (error) {
      this.logger.error(`Failed to get recent subscription activity for user ${userId}`, {
        error: getErrorMessage(error),
        days,
      });
      throw error;
    }
  }

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

      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;
    } catch (error) {
      this.logger.error(`Failed to search subscriptions for user ${userId}`, {
        error: getErrorMessage(error),
        query,
      });
      throw error;
    }
  }

  /**
   * 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;
    const batchSize = Math.min(100, Math.max(limit, 25));

    try {
      // Validate subscription exists and belongs to user
      await this.getSubscriptionById(userId, subscriptionId);

      // Get WHMCS client ID from user mapping
      const mapping = await this.mappingsService.findByUserId(userId);
      if (!mapping?.whmcsClientId) {
        throw new NotFoundException("WHMCS client mapping not found");
      }

      const relatedInvoices: Invoice[] = [];
      let currentPage = 1;
      let totalPages = 1;

      do {
        const invoiceBatch = await this.whmcsService.getInvoicesWithItems(
          mapping.whmcsClientId,
          userId,
          { page: currentPage, limit: batchSize }
        );

        totalPages = invoiceBatch.pagination.totalPages;

        invoiceBatch.invoices.forEach(invoice => {
          if (!invoice.items?.length) {
            return;
          }

          const hasMatchingService = invoice.items.some(
            (item: InvoiceItem) => item.serviceId === subscriptionId
          );

          if (hasMatchingService) {
            relatedInvoices.push(invoice);
          }
        });

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

      // Apply pagination to filtered results
      const startIndex = (page - 1) * limit;
      const endIndex = startIndex + limit;
      const paginatedInvoices = relatedInvoices.slice(startIndex, endIndex);

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

      this.logger.log(
        `Retrieved ${paginatedInvoices.length} invoices for subscription ${subscriptionId}`,
        {
          userId,
          subscriptionId,
          totalRelated: relatedInvoices.length,
          page,
          limit,
        }
      );

      return result;
    } catch (error) {
      this.logger.error(`Failed to get invoices for subscription ${subscriptionId}`, {
        error: getErrorMessage(error),
        userId,
        subscriptionId,
        options,
      });

      if (error instanceof NotFoundException) {
        throw error;
      }

      throw new Error(`Failed to retrieve subscription invoices: ${getErrorMessage(error)}`);
    }
  }

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

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

  /**
   * Health check for subscription service
   */
  async healthCheck(): Promise<{ status: string; details: unknown }> {
    try {
      const whmcsHealthy = await this.whmcsService.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: getErrorMessage(error),
      });
      return {
        status: "unhealthy",
        details: {
          error: getErrorMessage(error),
          timestamp: new Date().toISOString(),
        },
      };
    }
  }

  private async checkUserHasExistingSim(userId: string): Promise<boolean> {
    try {
      const mapping = await this.mappingsService.findByUserId(userId);
      if (!mapping?.whmcsClientId) {
        return false;
      }

      const productsResponse = await this.whmcsService.getClientsProducts({
        clientid: mapping.whmcsClientId,
      });
      const productContainer = productsResponse.products?.product;
      const services = Array.isArray(productContainer)
        ? productContainer
        : productContainer
          ? [productContainer]
          : [];

      return services.some((service: WhmcsProduct) => {
        const group = typeof service.groupname === "string" ? service.groupname.toLowerCase() : "";
        const status = typeof service.status === "string" ? service.status.toLowerCase() : "";
        return group.includes("sim") && status === "active";
      });
    } catch (error: unknown) {
      this.logger.warn(`Failed to check existing SIM for user ${userId}`, {
        error: getErrorMessage(error),
      });
      return false;
    }
  }
}
