import { JsonTaxCatalogProvider } from "@erp/core";
import { Tax, Taxes } from "@erp/core/api";
import {
  CurrencyCode,
  DomainError,
  LanguageCode,
  Percentage,
  TextValue,
  UniqueID,
  UtcDate,
  ValidationErrorCollection,
  ValidationErrorDetail,
  extractOrPushError,
  maybeFromNullableVO,
} from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import {
  CreateCustomerInvoiceItemRequestDTO,
  CreateCustomerInvoiceRequestDTO,
} from "../../../../common/dto";
import {
  CustomerInvoiceItem,
  CustomerInvoiceItemDescription,
  CustomerInvoiceItemProps,
  CustomerInvoiceItems,
  CustomerInvoiceNumber,
  CustomerInvoiceProps,
  CustomerInvoiceSerie,
  CustomerInvoiceStatus,
  ItemAmount,
  ItemDiscount,
  ItemQuantity,
} from "../../../domain";

/**
 * Convierte el DTO a las props validadas (CustomerProps).
 * No construye directamente el agregado.
 *
 * @param dto - DTO con los datos de la factura de cliente
 * @returns 
 
 *
 */

export class CreateCustomerInvoicePropsMapper {
  private readonly taxCatalog: JsonTaxCatalogProvider;
  private errors: ValidationErrorDetail[] = [];
  private languageCode?: LanguageCode;
  private currencyCode?: CurrencyCode;

  constructor(params: { taxCatalog: JsonTaxCatalogProvider }) {
    this.taxCatalog = params.taxCatalog;
    this.errors = [];
  }

  public map(dto: CreateCustomerInvoiceRequestDTO) {
    try {
      this.errors = [];

      const defaultStatus = CustomerInvoiceStatus.createDraft();

      const invoiceId = extractOrPushError(UniqueID.create(dto.id), "id", this.errors);
      const companyId = extractOrPushError(
        UniqueID.create(dto.company_id),
        "company_id",
        this.errors
      );
      const customerId = extractOrPushError(
        UniqueID.create(dto.customer_id),
        "customer_id",
        this.errors
      );

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

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

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

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

      const notes = extractOrPushError(
        maybeFromNullableVO(dto.notes, (value) => TextValue.create(value)),
        "notes",
        this.errors
      );

      this.languageCode = extractOrPushError(
        LanguageCode.create(dto.language_code),
        "language_code",
        this.errors
      );

      this.currencyCode = extractOrPushError(
        CurrencyCode.create(dto.currency_code),
        "currency_code",
        this.errors
      );

      const discountPercentage = extractOrPushError(
        Percentage.create({
          value: Number(dto.discount_percentage.value),
          scale: Number(dto.discount_percentage.scale),
        }),
        "discount_percentage",
        this.errors
      );

      const items = this.mapItems(dto.items);

      if (this.errors.length > 0) {
        return Result.fail(
          new ValidationErrorCollection("Customer invoice props mapping failed", this.errors)
        );
      }

      const invoiceProps: CustomerInvoiceProps = {
        companyId: companyId!,
        status: defaultStatus!,

        invoiceNumber: invoiceNumber!,
        invoiceDate: invoiceDate!,

        operationDate: operationDate!,
        series: series!,

        notes: notes!,

        customerId: customerId!,

        languageCode: this.languageCode!,
        currencyCode: this.currencyCode!,

        discountPercentage: discountPercentage!,

        taxes: Taxes.create([]),
        items: items,
      };

      return Result.ok({ id: invoiceId!, props: invoiceProps });
    } catch (err: unknown) {
      return Result.fail(new DomainError("Customer invoice props mapping failed", { cause: err }));
    }
  }

  private mapItems(items: CreateCustomerInvoiceItemRequestDTO[]) {
    const invoiceItems = CustomerInvoiceItems.create({
      currencyCode: this.currencyCode!,
      languageCode: this.languageCode!,
      items: [],
    });

    items.forEach((item, index) => {
      const description = extractOrPushError(
        maybeFromNullableVO(item.description, (value) =>
          CustomerInvoiceItemDescription.create(value)
        ),
        "description",
        this.errors
      );

      const quantity = extractOrPushError(
        maybeFromNullableVO(item.quantity, (value) => ItemQuantity.create(value)),
        "quantity",
        this.errors
      );

      const unitAmount = extractOrPushError(
        maybeFromNullableVO(item.unit_amount, (value) => ItemAmount.create(value)),
        "unit_amount",
        this.errors
      );

      const discountPercentage = extractOrPushError(
        maybeFromNullableVO(item.discount_percentage, (value) => ItemDiscount.create(value)),
        "discount_percentage",
        this.errors
      );

      const taxes = this.mapTaxes(item, index);

      const itemProps: CustomerInvoiceItemProps = {
        currencyCode: this.currencyCode!,
        languageCode: this.languageCode!,
        description: description!,
        quantity: quantity!,
        unitAmount: unitAmount!,
        discountPercentage: discountPercentage!,
        taxes,
      };

      const itemResult = CustomerInvoiceItem.create(itemProps);
      if (itemResult.isSuccess) {
        invoiceItems.add(itemResult.data);
      } else {
        this.errors.push({
          path: `items[${index}]`,
          message: itemResult.error.message,
        });
      }
    });
    return invoiceItems;
  }

  private mapTaxes(item: CreateCustomerInvoiceItemRequestDTO, itemIndex: number) {
    const taxes = Taxes.create([]);

    item.taxes.split(",").every((tax_code, taxIndex) => {
      const taxResult = Tax.createFromCode(tax_code, this.taxCatalog);
      if (taxResult.isSuccess) {
        taxes.add(taxResult.data);
      } else {
        this.errors.push({
          path: `items[${itemIndex}].taxes[${taxIndex}]`,
          message: taxResult.error.message,
        });
      }
    });
    return taxes;
  }
}
