import { Injectable, BadRequestException, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { ZodError } from "zod";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
import { WhmcsConnectionFacade } from "@bff/integrations/whmcs/facades/whmcs.facade.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { normalizeToArray } from "@bff/core/utils/array.util.js";
import {
  orderWithSkuValidationSchema,
  type CreateOrderRequest,
  type OrderBusinessValidation,
} from "@customer-portal/domain/orders";
import type * as Providers from "@customer-portal/domain/subscriptions/providers";
import { SimServicesService } from "@bff/modules/services/application/sim-services.service.js";
import { InternetEligibilityService } from "@bff/modules/services/application/internet-eligibility.service.js";
import { OrderPricebookService, type PricebookProductMeta } from "./order-pricebook.service.js";
import { PaymentValidatorService } from "./payment-validator.service.js";
import { ResidenceCardService } from "@bff/modules/verification/residence-card.service.js";

type WhmcsProduct = Providers.WhmcsProductRaw;

/**
 * Handles all order validation logic - both format and business rules
 *
 * Note: Business validation rules have been moved to @customer-portal/domain/orders/validation
 * This service now focuses on infrastructure concerns (DB lookups, API calls, etc.)
 */
@Injectable()
export class OrderValidator {
  // eslint-disable-next-line max-params -- NestJS dependency injection requires all services to be injected via constructor
  constructor(
    @Inject(Logger) private readonly logger: Logger,
    private readonly mappings: MappingsService,
    private readonly whmcs: WhmcsConnectionFacade,
    private readonly pricebookService: OrderPricebookService,
    private readonly simCatalogService: SimServicesService,
    private readonly internetEligibilityService: InternetEligibilityService,
    private readonly paymentValidator: PaymentValidatorService,
    private readonly residenceCards: ResidenceCardService
  ) {}

  /**
   * Validate user mapping exists - simple business logic
   */
  async validateUserMapping(
    userId: string
  ): Promise<{ userId: string; sfAccountId: string; whmcsClientId: number }> {
    const mapping = await this.mappings.findByUserId(userId);

    if (!mapping) {
      this.logger.warn({ userId }, "User mapping not found");
      throw new BadRequestException("User account mapping is required before ordering");
    }

    if (!mapping.whmcsClientId) {
      this.logger.warn({ userId, mapping }, "WHMCS client ID missing from mapping");
      throw new BadRequestException("WHMCS integration is required before ordering");
    }

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

  /**
   * Validate payment method exists
   * Delegates to PaymentValidatorService for consistent validation
   */
  async validatePaymentMethod(userId: string, whmcsClientId: number): Promise<void> {
    return this.paymentValidator.validatePaymentMethodExists(userId, whmcsClientId);
  }

  /**
   * Validate Internet service doesn't already exist
   * In development, logs warning and allows order
   * In production, enforces the validation and blocks duplicate orders
   */
  async validateInternetDuplication(userId: string, whmcsClientId: number): Promise<void> {
    const isDevelopment = process.env["NODE_ENV"] === "development";

    try {
      const products = await this.whmcs.getClientsProducts({ clientid: whmcsClientId });
      const productContainer = products.products?.product;
      const existing = normalizeToArray(productContainer);

      // Check for active Internet products
      const activeInternetProducts = existing.filter((product: WhmcsProduct) => {
        const groupName = (product.groupname || product.translated_groupname || "").toLowerCase();
        const status = (product.status || "").toLowerCase();
        return groupName.includes("internet") && status === "active";
      });

      if (activeInternetProducts.length > 0) {
        const message = "An active Internet service already exists for this account";

        if (isDevelopment) {
          this.logger.warn(
            {
              userId,
              whmcsClientId,
              activeInternetCount: activeInternetProducts.length,
              environment: "development",
            },
            `[DEV MODE] ${message} - allowing order to proceed in development`
          );
          // In dev, just log warning and allow order
          return;
        }

        // In production, block the order
        this.logger.error(
          {
            userId,
            whmcsClientId,
            activeInternetCount: activeInternetProducts.length,
          },
          message
        );
        throw new BadRequestException(message);
      }
    } catch (e: unknown) {
      // If it's already a BadRequestException we threw, rethrow it
      if (e instanceof BadRequestException) {
        throw e;
      }

      // For other errors (like WHMCS API issues), handle differently based on environment
      const err = extractErrorMessage(e);
      this.logger.error({ err, userId, whmcsClientId }, "Internet duplicate check failed");

      if (isDevelopment) {
        this.logger.warn(
          { environment: "development" },
          "[DEV MODE] WHMCS check failed - allowing order to proceed in development"
        );
        return;
      }

      throw new BadRequestException(
        "Unable to verify existing Internet services. Please try again."
      );
    }
  }

  /**
   * Validate SKUs exist in Salesforce
   */
  async validateSKUs(
    skus: string[],
    pricebookId: string
  ): Promise<Map<string, PricebookProductMeta>> {
    const invalidSKUs: string[] = [];
    const meta = await this.pricebookService.fetchProductMeta(pricebookId, skus);
    const normalizedSkus = skus
      .map(sku => sku?.trim())
      .filter((sku): sku is string => Boolean(sku))
      .map(sku => ({ raw: sku, normalized: sku.toUpperCase() }));

    for (const sku of normalizedSkus) {
      if (!meta.has(sku.normalized)) {
        invalidSKUs.push(sku.raw);
      }
    }

    if (invalidSKUs.length > 0) {
      this.logger.error({ invalidSKUs }, "Invalid SKUs found in order");
      throw new BadRequestException(`Invalid products: ${invalidSKUs.join(", ")}`);
    }

    return meta;
  }

  /**
   * Parse and validate business constraints with Zod schema
   */
  private parseBusinessValidation(
    userId: string,
    body: CreateOrderRequest
  ): OrderBusinessValidation {
    try {
      return orderWithSkuValidationSchema.parse({
        ...body,
        userId,
      });
    } catch (error) {
      if (!(error instanceof ZodError)) {
        throw error;
      }

      const issues = error.issues.map(issue => {
        const path = issue.path.join(".");
        return path ? `${path}: ${issue.message}` : issue.message;
      });

      this.logger.error("Business validation failed", {
        userId,
        orderType: body.orderType,
        skus: body.skus,
        validationErrors: issues,
      });

      throw new BadRequestException({
        message: "Order business validation failed",
        errors: issues,
        statusCode: 400,
      });
    }
  }

  /**
   * Validate SIM-specific requirements (residence card, activation SKU)
   */
  private async validateSimRequirements(userId: string, skus: string[]): Promise<void> {
    const verification = await this.residenceCards.getStatusForUser(userId);

    if (verification.status === "not_submitted") {
      throw new BadRequestException(
        "Residence card submission required for SIM orders. Please upload your residence card and try again."
      );
    }

    if (verification.status === "rejected") {
      throw new BadRequestException(
        "Your residence card submission was rejected. Please resubmit your residence card and try again."
      );
    }

    const activationFees = await this.simCatalogService.getActivationFees();
    const activationSkus = new Set(
      activationFees
        .map(fee => fee.sku?.trim())
        .filter((sku): sku is string => Boolean(sku))
        .map(sku => sku.toUpperCase())
    );

    const hasActivationSku = skus.some(sku => {
      const normalized = sku?.trim().toUpperCase();
      return normalized ? activationSkus.has(normalized) : false;
    });

    if (!hasActivationSku) {
      this.logger.warn({ skus }, "SIM order missing activation SKU based on catalog metadata");
      throw new BadRequestException("SIM orders require an activation fee");
    }
  }

  /**
   * Validate Internet-specific requirements (eligibility, duplication)
   */
  private async validateInternetRequirements(userId: string, whmcsClientId: number): Promise<void> {
    const eligibility = await this.internetEligibilityService.getEligibilityDetailsForUser(userId);

    if (eligibility.status === "not_requested") {
      throw new BadRequestException(
        "Internet eligibility review is required before ordering. Please request an eligibility review from the Internet services page and try again."
      );
    }

    if (eligibility.status === "pending") {
      throw new BadRequestException(
        "Internet eligibility review is still in progress. Please wait for review to complete and try again."
      );
    }

    if (eligibility.status === "ineligible") {
      throw new BadRequestException(
        "Internet service is not available for your address. Please contact support if you believe this is incorrect."
      );
    }

    await this.validateInternetDuplication(userId, whmcsClientId);
  }

  /**
   * Complete order validation - performs ALL validation (format + business)
   */
  async validateCompleteOrder(
    userId: string,
    body: CreateOrderRequest
  ): Promise<{
    validatedBody: OrderBusinessValidation;
    userMapping: { userId: string; sfAccountId: string; whmcsClientId: number };
    pricebookId: string;
  }> {
    this.logger.log({ userId }, "Starting complete order validation");

    // 1. Business validation (format validation is performed in controller via Zod DTO)
    const businessValidatedBody = this.parseBusinessValidation(userId, body);

    // 2. User and payment validation
    const userMapping = await this.validateUserMapping(userId);
    await this.validatePaymentMethod(userId, userMapping.whmcsClientId);

    // 3. SKU validation
    const pricebookId = await this.pricebookService.findPortalPricebookId();
    await this.validateSKUs(businessValidatedBody.skus, pricebookId);

    // 4. Order-type-specific validation
    if (businessValidatedBody.orderType === "SIM") {
      await this.validateSimRequirements(userId, businessValidatedBody.skus);
    }

    if (businessValidatedBody.orderType === "Internet") {
      await this.validateInternetRequirements(userId, userMapping.whmcsClientId);
    }

    this.logger.log(
      {
        userId,
        orderType: businessValidatedBody.orderType,
        skuCount: businessValidatedBody.skus.length,
      },
      "Complete order validation passed"
    );

    return { validatedBody: businessValidatedBody, userMapping, pricebookId };
  }
}
