import { Injectable, BadRequestException, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js";
import { SalesforceAccountService } from "@bff/integrations/salesforce/services/salesforce-account.service.js";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { sfOrderIdParamSchema } from "@customer-portal/domain/orders";
import type {
  OrderFulfillmentValidationResult,
  SalesforceOrderRecord,
} from "@customer-portal/domain/orders/providers";
import { salesforceAccountIdSchema } from "@customer-portal/domain/common";
import { PaymentValidatorService } from "./payment-validator.service.js";

/**
 * Handles all order fulfillment validation logic
 * Similar to OrderValidator but for fulfillment workflow
 */
@Injectable()
export class OrderFulfillmentValidator {
  constructor(
    @Inject(Logger) private readonly logger: Logger,
    private readonly salesforceFacade: SalesforceFacade,
    private readonly salesforceAccountService: SalesforceAccountService,
    private readonly mappingsService: MappingsService,
    private readonly paymentValidator: PaymentValidatorService
  ) {}

  /**
   * Complete validation for fulfillment request
   * Validates SF order, gets client ID, checks payment method, checks idempotency
   */
  async validateFulfillmentRequest(
    rawSfOrderId: string,
    idempotencyKey: string
  ): Promise<OrderFulfillmentValidationResult> {
    const { sfOrderId } = sfOrderIdParamSchema.parse({ sfOrderId: rawSfOrderId });

    this.logger.log("Starting fulfillment validation", {
      sfOrderId,
      idempotencyKey,
    });

    try {
      // 1. Validate Salesforce order exists and get details
      const sfOrder = await this.validateSalesforceOrder(sfOrderId);

      // 2. Check if already provisioned (idempotency)

      const existingWhmcsOrderId = sfOrder.WHMCS_Order_ID__c;
      if (existingWhmcsOrderId) {
        this.logger.log("Order already provisioned", {
          sfOrderId,
          whmcsOrderId: existingWhmcsOrderId,
        });

        return {
          sfOrder,
          clientId: 0, // Not needed for already provisioned
          isAlreadyProvisioned: true,
          whmcsOrderId: existingWhmcsOrderId,
        };
      }

      // 3. Get WHMCS client mapping
      // Validate AccountId using schema instead of manual type checks
      const accountId = salesforceAccountIdSchema.parse(sfOrder.AccountId);
      const mapping = await this.mappingsService.findBySfAccountId(accountId);
      let clientId: number;
      let userId: string | undefined;

      if (mapping?.whmcsClientId) {
        clientId = mapping.whmcsClientId;
        userId = mapping.userId;
      } else {
        // Fallback: Try to get WHMCS client ID from Salesforce Account's WH_Account__c field
        const sfAccount = await this.salesforceAccountService.getAccountDetails(accountId);
        const whmcsClientId = this.parseWhmcsClientIdFromField(sfAccount?.WH_Account__c);

        if (!whmcsClientId) {
          throw new BadRequestException(`No WHMCS client mapping found for account ${accountId}`);
        }

        this.logger.log(
          "Using WHMCS client ID from Salesforce Account field (no database mapping)",
          {
            accountId,
            whmcsClientId,
            whAccountField: sfAccount?.WH_Account__c,
          }
        );

        clientId = whmcsClientId;
        // Try to find userId by WHMCS client ID for payment validation
        const mappingByWhmcs = await this.mappingsService.findByWhmcsClientId(whmcsClientId);
        userId = mappingByWhmcs?.userId;
      }

      // 4. Validate payment method exists (skip if no userId available)
      if (userId) {
        await this.validatePaymentMethod(clientId, userId);
      } else {
        this.logger.warn("Skipping payment method validation - no userId available", {
          accountId,
          clientId,
        });
      }

      this.logger.log("Fulfillment validation completed successfully", {
        sfOrderId,
        clientId,
        accountId,
      });

      return {
        sfOrder,
        clientId,
        isAlreadyProvisioned: false,
      };
    } catch (error) {
      this.logger.error("Fulfillment validation failed", {
        error: extractErrorMessage(error),
        sfOrderId,
        idempotencyKey,
      });
      throw error;
    }
  }

  /**
   * Validate Salesforce order exists and is in valid state
   */
  private async validateSalesforceOrder(sfOrderId: string): Promise<SalesforceOrderRecord> {
    const order = await this.salesforceFacade.getOrder(sfOrderId);

    if (!order) {
      throw new BadRequestException(`Salesforce order ${sfOrderId} not found`);
    }

    if (order.Status === "Cancelled") {
      throw new BadRequestException(`Cannot provision cancelled order ${sfOrderId}`);
    }

    this.logger.log("Salesforce order validated", {
      sfOrderId,
      status: order.Status,
      activationStatus: order.Activation_Status__c,
      accountId: order.AccountId,
    });

    return order;
  }

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

  /**
   * Parse WHMCS client ID from the WH_Account__c field value
   * Format: "#9883 - Temuulen Ankhbayar" -> 9883
   */
  private parseWhmcsClientIdFromField(whAccountField: string | null | undefined): number | null {
    if (!whAccountField) {
      return null;
    }

    // Match "#<number>" pattern at the start of the string
    const match = whAccountField.match(/^#(\d+)/);
    if (!match || !match[1]) {
      this.logger.warn("Could not parse WHMCS client ID from WH_Account__c field", {
        whAccountField,
      });
      return null;
    }

    const clientId = Number.parseInt(match[1], 10);
    if (Number.isNaN(clientId) || clientId <= 0) {
      return null;
    }

    return clientId;
  }
}
