import {
  Injectable,
  NotFoundException,
  InternalServerErrorException,
  Inject,
} from "@nestjs/common";
import { Logger } from "nestjs-pino";
import {
  Invoice,
  InvoiceList,
  InvoiceListQuery,
  InvoiceStatus,
  invoiceSchema,
  invoiceListQuerySchema,
} from "@customer-portal/domain/billing";
import { validateUuidV4OrThrow } from "@customer-portal/domain/common";
import { WhmcsService } from "@bff/integrations/whmcs/whmcs.service";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service";
import { getErrorMessage } from "@bff/core/utils/error.util";

/**
 * Service responsible for retrieving invoices from WHMCS
 *
 * Validation is handled by Zod schemas at the entry point (controller).
 * This service focuses on business logic and data fetching.
 */
@Injectable()
export class InvoiceRetrievalService {
  constructor(
    private readonly whmcsService: WhmcsService,
    private readonly mappingsService: MappingsService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Get paginated invoices for a user
   * @param userId - User ID (should be validated by controller)
   * @param options - Query options (should be validated by controller using invoiceListQuerySchema)
   */
  async getInvoices(userId: string, options: InvoiceListQuery = {}): Promise<InvoiceList> {
    // Validate options against schema for internal calls
    const validatedOptions = invoiceListQuerySchema.parse(options);
    const { page = 1, limit = 10, status } = validatedOptions;

    try {
      // Get user mapping
      const mapping = await this.getUserMapping(userId);

      // Fetch invoices from WHMCS
      const invoiceList = await this.whmcsService.getInvoices(mapping.whmcsClientId, userId, {
        page,
        limit,
        status,
      });

      this.logger.log(`Retrieved ${invoiceList.invoices.length} invoices for user ${userId}`, {
        page,
        limit,
        status,
        totalItems: invoiceList.pagination?.totalItems,
      });

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

      if (error instanceof NotFoundException) {
        throw error;
      }

      throw new InternalServerErrorException("Failed to retrieve invoices");
    }
  }

  /**
   * Get individual invoice by ID
   * @param userId - User ID (should be validated by controller)
   * @param invoiceId - Invoice ID (should be validated by controller/schema)
   */
  async getInvoiceById(userId: string, invoiceId: number): Promise<Invoice> {
    try {
      // Validate invoice ID using schema
      invoiceSchema.shape.id.parse(invoiceId);

      // Get user mapping
      const mapping = await this.getUserMapping(userId);

      // Fetch invoice from WHMCS
      const invoice = await this.whmcsService.getInvoiceById(
        mapping.whmcsClientId,
        userId,
        invoiceId
      );

      this.logger.log(`Retrieved invoice ${invoiceId} for user ${userId}`);
      return invoice;
    } catch (error) {
      this.logger.error(`Failed to get invoice ${invoiceId} for user ${userId}`, {
        error: getErrorMessage(error),
      });

      if (error instanceof NotFoundException) {
        throw error;
      }

      throw new InternalServerErrorException("Failed to retrieve invoice");
    }
  }

  /**
   * Get invoices by status
   */
  async getInvoicesByStatus(
    userId: string,
    status: InvoiceStatus,
    options: Partial<InvoiceListQuery> = {}
  ): Promise<InvoiceList> {
    const { page = 1, limit = 10 } = options;

    try {
      // Cast to the subset of statuses supported by InvoiceListQuery
      const queryStatus = status as "Paid" | "Unpaid" | "Cancelled" | "Overdue" | "Collections";
      return await this.getInvoices(userId, { page, limit, status: queryStatus });
    } catch (error) {
      this.logger.error(`Failed to get ${status} invoices for user ${userId}`, {
        error: getErrorMessage(error),
        options,
      });

      if (error instanceof NotFoundException) {
        throw error;
      }

      throw new InternalServerErrorException("Failed to retrieve invoices");
    }
  }

  /**
   * Get unpaid invoices for a user
   */
  async getUnpaidInvoices(
    userId: string,
    options: Partial<InvoiceListQuery> = {}
  ): Promise<InvoiceList> {
    return this.getInvoicesByStatus(userId, "Unpaid", options);
  }

  /**
   * Get overdue invoices for a user
   */
  async getOverdueInvoices(
    userId: string,
    options: Partial<InvoiceListQuery> = {}
  ): Promise<InvoiceList> {
    return this.getInvoicesByStatus(userId, "Overdue", options);
  }

  /**
   * Get paid invoices for a user
   */
  async getPaidInvoices(
    userId: string,
    options: Partial<InvoiceListQuery> = {}
  ): Promise<InvoiceList> {
    return this.getInvoicesByStatus(userId, "Paid", options);
  }

  /**
   * Get cancelled invoices for a user
   */
  async getCancelledInvoices(
    userId: string,
    options: Partial<InvoiceListQuery> = {}
  ): Promise<InvoiceList> {
    return this.getInvoicesByStatus(userId, "Cancelled", options);
  }

  /**
   * Get invoices in collections for a user
   */
  async getCollectionsInvoices(
    userId: string,
    options: Partial<InvoiceListQuery> = {}
  ): Promise<InvoiceList> {
    return this.getInvoicesByStatus(userId, "Collections", options);
  }

  /**
   * Get user mapping with validation
   */
  private async getUserMapping(userId: string): Promise<{ userId: string; whmcsClientId: number }> {
    // Validate userId is a valid UUID
    validateUuidV4OrThrow(userId);

    const mapping = await this.mappingsService.findByUserId(userId);

    if (!mapping?.whmcsClientId) {
      throw new NotFoundException("WHMCS client mapping not found");
    }

    // WHMCS client ID validation - basic sanity check
    if (mapping.whmcsClientId < 1) {
      throw new NotFoundException("Invalid WHMCS client mapping");
    }

    return {
      userId,
      whmcsClientId: mapping.whmcsClientId,
    };
  }

  /**
   * Check if user has any invoices
   */
  async hasInvoices(userId: string): Promise<boolean> {
    try {
      const invoices = await this.getInvoices(userId, { page: 1, limit: 1 });
      return invoices.invoices.length > 0;
    } catch (error) {
      this.logger.warn(`Failed to check if user ${userId} has invoices`, {
        error: getErrorMessage(error),
      });
      return false;
    }
  }

  /**
   * Get invoice count by status
   */
  async getInvoiceCountByStatus(userId: string, status: InvoiceStatus): Promise<number> {
    try {
      const invoices = await this.getInvoicesByStatus(userId, status, { page: 1, limit: 1 });
      return invoices.pagination?.totalItems || 0;
    } catch (error) {
      this.logger.warn(`Failed to get ${status} invoice count for user ${userId}`, {
        error: getErrorMessage(error),
      });
      return 0;
    }
  }
}
