import { ISequelizeQueryMapper, MapperParamsType, SequelizeQueryMapper } from "@erp/core/api";

import {
  ValidationErrorCollection,
  ValidationErrorDetail,
  extractOrPushError,
} from "@repo/rdx-ddd";

import {
  CurrencyCode,
  LanguageCode,
  Percentage,
  UniqueID,
  UtcDate,
  maybeFromNullableVO,
} from "@repo/rdx-ddd";

import { Maybe, Result } from "@repo/rdx-utils";
import {
  CustomerInvoiceNumber,
  CustomerInvoiceSerie,
  CustomerInvoiceStatus,
  InvoiceAmount,
  InvoiceRecipient,
} from "../../../domain";
import { CustomerInvoiceModel } from "../../sequelize";
import { InvoiceRecipientListMapper } from "./invoice-recipient.list.mapper";

export type CustomerInvoiceListDTO = {
  id: UniqueID;
  companyId: UniqueID;

  isProforma: boolean;
  invoiceNumber: Maybe<CustomerInvoiceNumber>;
  status: CustomerInvoiceStatus;
  series: Maybe<CustomerInvoiceSerie>;

  invoiceDate: UtcDate;
  operationDate: Maybe<UtcDate>;

  reference: Maybe<string>;
  description: Maybe<string>;

  customerId: UniqueID;
  recipient: InvoiceRecipient;

  languageCode: LanguageCode;
  currencyCode: CurrencyCode;

  taxes: string;

  discountPercentage: Percentage;

  subtotalAmount: InvoiceAmount;
  discountAmount: InvoiceAmount;
  taxableAmount: InvoiceAmount;
  taxesAmount: InvoiceAmount;
  totalAmount: InvoiceAmount;
};

export interface ICustomerInvoiceListMapper
  extends ISequelizeQueryMapper<CustomerInvoiceModel, CustomerInvoiceListDTO> {}

export class CustomerInvoiceListMapper
  extends SequelizeQueryMapper<CustomerInvoiceModel, CustomerInvoiceListDTO>
  implements ICustomerInvoiceListMapper
{
  private _recipientMapper: InvoiceRecipientListMapper;

  constructor() {
    super();
    this._recipientMapper = new InvoiceRecipientListMapper();
  }

  public mapToDTO(
    raw: CustomerInvoiceModel,
    params?: MapperParamsType
  ): Result<CustomerInvoiceListDTO, Error> {
    const errors: ValidationErrorDetail[] = [];

    // 1) Valores escalares (atributos generales)
    const attributes = this.mapAttributesToDTO(raw, { errors, ...params });

    // 2) Recipient (snapshot en la factura o include)
    const recipientResult = this._recipientMapper.mapToDTO(raw, {
      errors,
      attributes,
      ...params,
    });

    if (recipientResult.isFailure) {
      errors.push({
        path: "recipient",
        message: recipientResult.error.message,
      });
    }

    // 3) Taxes
    const taxes = raw.taxes.map((tax) => tax.tax_code).join(", ");

    // 5) Si hubo errores de mapeo, devolvemos colección de validación
    if (errors.length > 0) {
      return Result.fail(
        new ValidationErrorCollection("Customer invoice mapping failed [mapToDTO]", errors)
      );
    }

    return Result.ok({
      id: attributes.invoiceId!,
      companyId: attributes.companyId!,
      isProforma: attributes.isProforma,
      status: attributes.status!,
      series: attributes.series!,
      invoiceNumber: attributes.invoiceNumber!,
      invoiceDate: attributes.invoiceDate!,
      operationDate: attributes.operationDate!,

      description: attributes.description!,

      customerId: attributes.customerId!,
      recipient: recipientResult.data,

      languageCode: attributes.languageCode!,
      currencyCode: attributes.currencyCode!,

      taxes,

      discountPercentage: attributes.discountPercentage!,
      subtotalAmount: attributes.subtotalAmount!,
      discountAmount: attributes.discountAmount!,
      taxableAmount: attributes.taxableAmount!,
      taxesAmount: attributes.taxesAmount!,
      totalAmount: attributes.totalAmount!,
    });
  }

  private mapAttributesToDTO(raw: CustomerInvoiceModel, params?: MapperParamsType) {
    const { errors } = params as {
      errors: ValidationErrorDetail[];
    };

    const invoiceId = extractOrPushError(UniqueID.create(raw.id), "id", errors);
    const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);

    const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);

    const isProforma = Boolean(raw.is_proforma);

    const status = extractOrPushError(CustomerInvoiceStatus.create(raw.status), "status", errors);

    const series = extractOrPushError(
      maybeFromNullableVO(raw.series, (value) => CustomerInvoiceSerie.create(value)),
      "serie",
      errors
    );

    const invoiceNumber = extractOrPushError(
      maybeFromNullableVO(raw.invoice_number, (value) => CustomerInvoiceNumber.create(value)),
      "invoice_number",
      errors
    );

    const invoiceDate = extractOrPushError(
      UtcDate.createFromISO(raw.invoice_date),
      "invoice_date",
      errors
    );

    const operationDate = extractOrPushError(
      maybeFromNullableVO(raw.operation_date, (value) => UtcDate.createFromISO(value)),
      "operation_date",
      errors
    );

    const description = extractOrPushError(
      maybeFromNullableVO(raw.description, (value) => Result.ok(String(value))),
      "description",
      errors
    );

    const languageCode = extractOrPushError(
      LanguageCode.create(raw.language_code),
      "language_code",
      errors
    );

    const currencyCode = extractOrPushError(
      CurrencyCode.create(raw.currency_code),
      "currency_code",
      errors
    );

    const discountPercentage = extractOrPushError(
      Percentage.create({
        value: raw.discount_percentage_value,
        scale: raw.discount_percentage_scale,
      }),
      "discount_percentage_value",
      errors
    );

    const subtotalAmount = extractOrPushError(
      InvoiceAmount.create({
        value: raw.subtotal_amount_value,
        currency_code: currencyCode?.code,
      }),
      "subtotal_amount_value",
      errors
    );

    const discountAmount = extractOrPushError(
      InvoiceAmount.create({
        value: raw.discount_amount_value,
        currency_code: currencyCode?.code,
      }),
      "discount_amount_value",
      errors
    );

    const taxableAmount = extractOrPushError(
      InvoiceAmount.create({
        value: raw.taxable_amount_value,
        currency_code: currencyCode?.code,
      }),
      "taxable_amount_value",
      errors
    );

    const taxesAmount = extractOrPushError(
      InvoiceAmount.create({
        value: raw.taxes_amount_value,
        currency_code: currencyCode?.code,
      }),
      "taxes_amount_value",
      errors
    );

    const totalAmount = extractOrPushError(
      InvoiceAmount.create({
        value: raw.total_amount_value,
        currency_code: currencyCode?.code,
      }),
      "total_amount_value",
      errors
    );

    return {
      invoiceId,
      companyId,
      customerId,
      isProforma,
      status,
      series,
      invoiceNumber,
      invoiceDate,
      operationDate,
      description,
      languageCode,
      currencyCode,
      discountPercentage,
      subtotalAmount,
      discountAmount,
      taxableAmount,
      taxesAmount,
      totalAmount,
    };
  }
}
