import { Customer, AssignedPayer } from '@isa/crm/data-access';
import { Buyer, Payer } from '../schemas';

/**
 * Adapter for converting CRM customer data to checkout-api format.
 *
 * Handles three distinct conversion scenarios:
 * 1. **Customer to Buyer** (`toBuyer`): Converts customer for checkout as the buyer.
 * 2. **Customer to Payer** (`toPayerFromCustomer`): Uses customer as payer (self-paying).
 * 3. **AssignedPayer to Payer** (`toPayerFromAssignedPayer`): Unwraps separate payer entity.
 *
 * **Key Patterns:**
 * - Buyer conversion includes `dateOfBirth` and `source` field
 * - Payer from customer omits `source`, sets default `payerStatus: 0`
 * - AssignedPayer unwrapping handles `EntityContainer` structure with null safety
 */
export class CustomerAdapter {
  private static readonly ADAPTER_NAME = 'CustomerAdapter';

  /**
   * Converts Customer to checkout-api Buyer.
   *
   * @remarks
   * Used when the customer is the buyer in a checkout flow. Maps all relevant
   * customer information including personal details, address, and organization.
   *
   * The buyer includes a `source` field referencing the customer entity ID,
   * and preserves the `dateOfBirth` field which is specific to buyers.
   *
   * Type mapping: `customerType` (CRM) → `buyerType` (Checkout)
   *
   * @param customer - Customer from CRM service
   * @returns Buyer compatible with checkout-api
   *
   * @example
   * ```typescript
   * const customer = await crmService.getCustomer(123);
   * const buyer = CustomerAdapter.toBuyer(customer);
   * await checkoutService.complete({ buyer, ... });
   * ```
   */
  static toBuyer(customer: Customer): Buyer {
    return {
      source: customer.id,
      reference: {
        id: customer.id,
        // Explicitly omit externalReference to avoid TypeScript errors
        // (generated DTOs require externalStatus when externalReference is present)
      },
      buyerType: customer.customerType,
      buyerNumber: customer.customerNumber,
      gender: customer.gender,
      title: customer.title,
      firstName: customer.firstName,
      lastName: customer.lastName,
      dateOfBirth: customer.dateOfBirth,
      communicationDetails: customer.communicationDetails
        ? { ...customer.communicationDetails }
        : undefined,
      organisation: customer.organisation
        ? { ...customer.organisation }
        : undefined,
      address: customer.address ? { ...customer.address } : undefined,
    };
  }

  /**
   * Converts Customer to checkout-api Payer.
   *
   * @remarks
   * Used when the customer acts as their own payer (self-paying scenarios,
   * B2B customers, staff, customer card holders without separate billing address).
   *
   * **Important differences from toBuyer:**
   * - No `source` field (indicates derived data, not a separate entity)
   * - No `dateOfBirth` field (not relevant for payer)
   * - Sets `payerStatus: 0` (active status by default)
   *
   * Type mapping: `customerType` (CRM) → `payerType` (Checkout)
   *
   * @param customer - Customer from CRM service
   * @returns Payer compatible with checkout-api
   *
   * @example
   * ```typescript
   * const customer = await crmService.getCustomer(123);
   * const payer = CustomerAdapter.toPayerFromCustomer(customer);
   * await checkoutService.complete({ payer, ... });
   * ```
   */
  static toPayerFromCustomer(customer: Customer): Payer {
    return {
      reference: {
        id: customer.id,
        // Explicitly omit externalReference to avoid TypeScript errors
        // (generated DTOs require externalStatus when externalReference is present)
      },
      payerType: customer.customerType as Payer['payerType'],
      payerNumber: customer.customerNumber,
      payerStatus: 0, // Default status: active
      gender: customer.gender,
      title: customer.title,
      firstName: customer.firstName,
      lastName: customer.lastName,
      communicationDetails: customer.communicationDetails
        ? { ...customer.communicationDetails }
        : undefined,
      organisation: customer.organisation
        ? { ...customer.organisation }
        : undefined,
      address: customer.address ? { ...customer.address } : undefined,
    };
  }

  /**
   * Converts AssignedPayer (EntityContainer wrapper) to checkout-api Payer.
   *
   * @remarks
   * Used when customer has a separate billing address/payer entity assigned.
   * Handles the CRM's `EntityContainer` structure which wraps payer data.
   *
   * **Container structure:**
   * ```typescript
   * assignedPayer: {
   *   payer: {
   *     id: number;
   *     data?: PayerDTO;  // Actual payer data
   *     displayName?: string;
   *     enabled?: boolean;
   *   }
   * }
   * ```
   *
   * Returns `null` if the payer data is missing or the container is invalid.
   * Includes `source` field as this references a persistent payer entity.
   *
   * @param assignedPayer - AssignedPayer container from CRM service
   * @returns Payer compatible with checkout-api, or null if data is missing
   *
   * @example
   * ```typescript
   * const assignedPayer = customer.payers.find(p => p.payer.id === selectedPayerId);
   * const payer = CustomerAdapter.toPayerFromAssignedPayer(assignedPayer);
   * if (payer) {
   *   await checkoutService.complete({ payer, ... });
   * }
   * ```
   */
  static toPayerFromAssignedPayer(assignedPayer: AssignedPayer): Payer | null {
    const payer = assignedPayer?.payer?.data;

    if (!payer) {
      return null;
    }

    return {
      reference: {
        id: payer.id,
        // Explicitly omit externalReference to avoid TypeScript errors
        // (generated DTOs require externalStatus when externalReference is present)
      },
      payerType: payer.payerType,
      payerNumber: payer.payerNumber,
      payerStatus: payer.payerStatus,
      gender: payer.gender,
      title: payer.title,
      firstName: payer.firstName,
      lastName: payer.lastName,
      communicationDetails: payer.communicationDetails
        ? { ...payer.communicationDetails }
        : undefined,
      organisation: payer.organisation ? { ...payer.organisation } : undefined,
      address: payer.address ? { ...payer.address } : undefined,
      source: payer.id,
    };
  }

  /**
   * Type guard for Customer
   *
   * @param value - Value to check
   * @returns true if value is a valid Customer with required fields
   */
  static isValidCustomer(value: unknown): value is Customer {
    if (typeof value !== 'object' || value === null) return false;

    const customer = value as Customer;
    return (
      typeof customer.id === 'number' &&
      (customer.customerNumber === undefined ||
        typeof customer.customerNumber === 'string')
    );
  }

  /**
   * Type guard for AssignedPayer
   *
   * @param value - Value to check
   * @returns true if value is a valid AssignedPayer with container structure
   */
  static isValidAssignedPayer(value: unknown): value is AssignedPayer {
    if (typeof value !== 'object' || value === null) return false;

    const assignedPayer = value as AssignedPayer;
    return (
      typeof assignedPayer.payer === 'object' &&
      assignedPayer.payer !== null &&
      typeof assignedPayer.payer.id === 'number'
    );
  }

  static extractCustomerFeatures(customer: Customer): Record<string, any> {
    const features = customer.features || [];
    return features.reduce((acc: Record<string, string>, feature: any) => {
      acc[feature.key] = feature.key;
      return acc;
    }, {});
  }
}
