import { Injectable, Inject, BadRequestException } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import {
  validationSuccess,
  validationFailure,
  type ValidationResult,
  type ValidationError,
} from "./base-validator.interface.js";
import { UserMappingValidator, type UserMappingData } from "./user-mapping.validator.js";
import { PaymentValidator } from "./payment.validator.js";
import { SkuValidator } from "./sku.validator.js";
import { SimOrderValidator } from "./sim-order.validator.js";
import { InternetOrderValidator } from "./internet-order.validator.js";
import type { CheckoutItem } from "@customer-portal/domain/orders";

/**
 * Order validation options
 */
export interface OrderValidationOptions {
  /** Whether order contains SIM products */
  hasSim: boolean;
  /** Whether order contains Internet products */
  hasInternet: boolean;
  /** Pricebook ID for SKU validation */
  pricebookId: string;
}

/**
 * Validated order context returned on successful validation
 */
export interface ValidatedOrderContext {
  /** User mapping data (userId, sfAccountId, whmcsClientId) */
  mapping: UserMappingData;
  /** Validated SKUs */
  skus: string[];
}

/**
 * Order Composite Validator
 *
 * Coordinates all order validation steps in sequence.
 * Collects all errors rather than failing fast for better UX.
 */
@Injectable()
export class OrderCompositeValidator {
  // eslint-disable-next-line max-params -- Composite validator requires all sub-validators as dependencies
  constructor(
    @Inject(Logger) private readonly logger: Logger,
    private readonly userMappingValidator: UserMappingValidator,
    private readonly paymentValidator: PaymentValidator,
    private readonly skuValidator: SkuValidator,
    private readonly simOrderValidator: SimOrderValidator,
    private readonly internetOrderValidator: InternetOrderValidator
  ) {}

  /**
   * Validate an order creation request
   *
   * Steps:
   * 1. User mapping validation (required IDs exist)
   * 2. Payment method validation (has payment on file)
   * 3. SKU validation (all SKUs are valid)
   * 4. Product-specific validation (SIM requirements, internet eligibility)
   *
   * @param userId - User ID to validate
   * @param cartItems - Cart items to validate
   * @param options - Validation options (hasSim, hasInternet, pricebookId)
   */
  async validate(
    userId: string,
    cartItems: CheckoutItem[],
    options: OrderValidationOptions
  ): Promise<ValidationResult<ValidatedOrderContext>> {
    const errors: ValidationError[] = [];
    const skus = cartItems.map(item => item.sku);

    // Step 1: User mapping validation
    const mappingResult = await this.userMappingValidator.validate(userId);
    if (!mappingResult.success) {
      errors.push(...(mappingResult.errors ?? []));
      // Can't continue without mapping - return early
      return validationFailure(errors);
    }
    const mapping = mappingResult.data!;

    // Step 2: Payment validation
    const paymentResult = await this.paymentValidator.validate(userId, mapping.whmcsClientId);
    if (!paymentResult.success) {
      errors.push(...(paymentResult.errors ?? []));
    }

    // Step 3: SKU validation
    const skuResult = await this.skuValidator.validate(skus, options.pricebookId);
    if (!skuResult.success) {
      errors.push(...(skuResult.errors ?? []));
    }

    // Step 4: Product-specific validation
    if (options.hasSim) {
      const simResult = await this.simOrderValidator.validate(userId, skus);
      if (!simResult.success) {
        errors.push(...(simResult.errors ?? []));
      }
    }

    if (options.hasInternet) {
      const internetResult = await this.internetOrderValidator.validate(
        userId,
        mapping.whmcsClientId
      );
      if (!internetResult.success) {
        errors.push(...(internetResult.errors ?? []));
      }
    }

    // Return collected errors or success
    if (errors.length > 0) {
      this.logger.warn({ userId, errorCount: errors.length }, "Order validation failed");
      return validationFailure(errors);
    }

    return validationSuccess({
      mapping,
      skus,
    });
  }

  /**
   * Validate and throw BadRequestException with all errors
   */
  async validateOrThrow(
    userId: string,
    cartItems: CheckoutItem[],
    options: OrderValidationOptions
  ): Promise<ValidatedOrderContext> {
    const result = await this.validate(userId, cartItems, options);

    if (!result.success) {
      const messages = result.errors?.map(e => e.message).join("; ");
      throw new BadRequestException(messages ?? "Order validation failed");
    }

    return result.data!;
  }
}
