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";
import { WhmcsConnectionOrchestratorService } from "@bff/integrations/whmcs/connection/services/whmcs-connection-orchestrator.service";
import { getErrorMessage } from "@bff/core/utils/error.util";
import {
  createOrderRequestSchema,
  orderWithSkuValidationSchema,
  type CreateOrderRequest,
  type OrderBusinessValidation,
} from "@customer-portal/domain/orders";
import type { Providers } from "@customer-portal/domain/subscriptions";

type WhmcsProduct = Providers.WhmcsRaw.WhmcsProductRaw;
import { SimCatalogService } from "@bff/modules/catalog/services/sim-catalog.service";
import { OrderPricebookService, type PricebookProductMeta } from "./order-pricebook.service";
import { PaymentValidatorService } from "./payment-validator.service";

/**
 * 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 {
  constructor(
    @Inject(Logger) private readonly logger: Logger,
    private readonly mappings: MappingsService,
    private readonly whmcs: WhmcsConnectionOrchestratorService,
    private readonly pricebookService: OrderPricebookService,
    private readonly simCatalogService: SimCatalogService,
    private readonly paymentValidator: PaymentValidatorService
  ) {}

  /**
   * Validate request format and structure using direct Zod validation
   */
  validateRequestFormat(rawBody: unknown): CreateOrderRequest {
    try {
      this.logger.debug({ bodyType: typeof rawBody }, "Starting request format validation");

      // Use direct Zod validation with .parse() - throws ZodError on failure
      const validatedBody = createOrderRequestSchema.parse(rawBody);

      this.logger.debug(
        {
          orderType: validatedBody.orderType,
          skuCount: validatedBody.skus.length,
          hasConfigurations: !!validatedBody.configurations,
        },
        "Request format validation passed"
      );

      return validatedBody;
    } catch (error) {
      if (error instanceof ZodError) {
        const errorMessages = error.issues.map(issue => {
          const path = issue.path.join(".");
          return path ? `${path}: ${issue.message}` : issue.message;
        });

        this.logger.error({ errors: errorMessages }, "Zod validation failed");

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

      const errorMessage = error instanceof Error ? error.message : String(error);
      this.logger.error({ error: errorMessage }, "Request format validation failed");
      throw error;
    }
  }

  /**
   * 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 || undefined,
      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 = Array.isArray(productContainer)
        ? productContainer
        : productContainer
          ? [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 = getErrorMessage(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;
  }

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

    // 1. Format validation (replaces DTO validation)
    const validatedBody = this.validateRequestFormat(rawBody);

    // 1b. Business validation (ensures userId-specific constraints)
    let businessValidatedBody: OrderBusinessValidation;
    try {
      const skuValidatedBody = orderWithSkuValidationSchema.parse({
        ...validatedBody,
        userId,
      });
      businessValidatedBody = skuValidatedBody;
    } catch (error) {
      if (error instanceof ZodError) {
        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: validatedBody.orderType,
          skus: validatedBody.skus,
          validationErrors: issues,
        });
        
        throw new BadRequestException({
          message: "Order business validation failed",
          errors: issues,
          statusCode: 400,
        });
      }
      throw error;
    }

    // 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();
    const productMeta = await this.validateSKUs(businessValidatedBody.skus, pricebookId);

    if (businessValidatedBody.orderType === "SIM") {
      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 = businessValidatedBody.skus.some(sku => {
        const normalized = sku?.trim().toUpperCase();
        if (!normalized) {
          return false;
        }
        return activationSkus.has(normalized);
      });

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

    // 4. Order-specific business validation
    if (businessValidatedBody.orderType === "Internet") {
      await this.validateInternetDuplication(userId, userMapping.whmcsClientId);
    }

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

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