import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
import {
  UniqueID,
  ValidationErrorCollection,
  ValidationErrorDetail,
  extractOrPushError,
  maybeFromNullableVO,
  toNullable,
} from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import {
  CustomerInvoice,
  CustomerInvoiceItem,
  CustomerInvoiceItemDescription,
  CustomerInvoiceItemProps,
  CustomerInvoiceProps,
  ItemAmount,
  ItemDiscount,
  ItemQuantity,
  ItemTaxes,
} from "../../../domain";
import { CustomerInvoiceItemCreationAttributes, CustomerInvoiceItemModel } from "../../sequelize";
import { ItemTaxesDomainMapper } from "./item-taxes.mapper";

export interface ICustomerInvoiceItemDomainMapper
  extends ISequelizeDomainMapper<
    CustomerInvoiceItemModel,
    CustomerInvoiceItemCreationAttributes,
    CustomerInvoiceItem
  > {}

export class CustomerInvoiceItemDomainMapper
  extends SequelizeDomainMapper<
    CustomerInvoiceItemModel,
    CustomerInvoiceItemCreationAttributes,
    CustomerInvoiceItem
  >
  implements ICustomerInvoiceItemDomainMapper
{
  private _taxesMapper: ItemTaxesDomainMapper;

  constructor(params: MapperParamsType) {
    super();
    this._taxesMapper = new ItemTaxesDomainMapper(params);
  }

  private mapAttributesToDomain(
    source: CustomerInvoiceItemModel,
    params?: MapperParamsType
  ): Partial<CustomerInvoiceItemProps> & { itemId?: UniqueID } {
    const { errors, index, attributes } = params as {
      index: number;
      errors: ValidationErrorDetail[];
      attributes: Partial<CustomerInvoiceProps>;
    };

    const itemId = extractOrPushError(
      UniqueID.create(source.item_id),
      `items[${index}].item_id`,
      errors
    );

    const description = extractOrPushError(
      maybeFromNullableVO(source.description, (v) => CustomerInvoiceItemDescription.create(v)),
      `items[${index}].description`,
      errors
    );

    const quantity = extractOrPushError(
      maybeFromNullableVO(source.quantity_value, (v) => ItemQuantity.create({ value: v })),
      `items[${index}].quantity`,
      errors
    );

    const unitAmount = extractOrPushError(
      maybeFromNullableVO(source.unit_amount_value, (value) =>
        ItemAmount.create({ value, currency_code: attributes.currencyCode!.code })
      ),
      `items[${index}].unit_amount`,
      errors
    );

    const discountPercentage = extractOrPushError(
      maybeFromNullableVO(source.discount_percentage_value, (v) =>
        ItemDiscount.create({ value: v })
      ),
      `items[${index}].discount_percentage`,
      errors
    );

    return {
      itemId,

      languageCode: attributes.languageCode,
      currencyCode: attributes.currencyCode,
      description,
      quantity,
      unitAmount,
      discountPercentage,
    };
  }

  public mapToDomain(
    source: CustomerInvoiceItemModel,
    params?: MapperParamsType
  ): Result<CustomerInvoiceItem, Error> {
    const { errors, index } = params as {
      index: number;
      errors: ValidationErrorDetail[];
      attributes: Partial<CustomerInvoiceProps>;
    };

    // 1) Valores escalares (atributos generales)
    const attributes = this.mapAttributesToDomain(source, params);

    // 2) Taxes (colección a nivel de item/línea)
    const taxesResults = this._taxesMapper.mapToDomainCollection(
      source.taxes,
      source.taxes.length,
      {
        attributes,
        ...params,
      }
    );

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

    const taxes = ItemTaxes.create(taxesResults.data.getAll());

    // 3) Construcción del elemento de dominio
    const createResult = CustomerInvoiceItem.create(
      {
        languageCode: attributes.languageCode!,
        currencyCode: attributes.currencyCode!,
        description: attributes.description!,
        quantity: attributes.quantity!,
        unitAmount: attributes.unitAmount!,
        discountPercentage: attributes.discountPercentage!,
        taxes,
      },
      attributes.itemId
    );

    if (createResult.isFailure) {
      return Result.fail(
        new ValidationErrorCollection("Invoice item entity creation failed", [
          { path: `items[${index}]`, message: createResult.error.message },
        ])
      );
    }

    return createResult;
  }

  public mapToPersistence(
    source: CustomerInvoiceItem,
    params?: MapperParamsType
  ): Result<CustomerInvoiceItemCreationAttributes, Error> {
    const { errors, index, parent } = params as {
      index: number;
      parent: CustomerInvoice;
      errors: ValidationErrorDetail[];
    };

    const taxesResults = this._taxesMapper.mapToPersistenceArray(source.taxes, params);

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

    const allAmounts = source.getAllAmounts();

    return Result.ok({
      item_id: source.id.toPrimitive(),
      invoice_id: parent.id.toPrimitive(),
      position: index,

      description: toNullable(source.description, (v) => v.toPrimitive()),

      quantity_value: toNullable(source.quantity, (v) => v.toPrimitive().value),
      quantity_scale:
        toNullable(source.quantity, (v) => v.toPrimitive().scale) ?? ItemQuantity.DEFAULT_SCALE,

      unit_amount_value: toNullable(source.unitAmount, (v) => v.toPrimitive().value),
      unit_amount_scale:
        toNullable(source.unitAmount, (v) => v.toPrimitive().scale) ?? ItemAmount.DEFAULT_SCALE,

      subtotal_amount_value: allAmounts.subtotalAmount.value,
      subtotal_amount_scale: allAmounts.subtotalAmount.scale,

      discount_percentage_value: toNullable(
        source.discountPercentage,
        (v) => v.toPrimitive().value
      ),
      discount_percentage_scale:
        toNullable(source.discountPercentage, (v) => v.toPrimitive().scale) ??
        ItemDiscount.DEFAULT_SCALE,

      discount_amount_value: allAmounts.discountAmount.value,
      discount_amount_scale: allAmounts.discountAmount.scale,

      taxable_amount_value: allAmounts.taxableAmount.value,
      taxable_amount_scale: allAmounts.taxableAmount.value,

      taxes_amount_value: allAmounts.taxesAmount.value,
      taxes_amount_scale: allAmounts.taxesAmount.scale,

      total_amount_value: allAmounts.totalAmount.value,
      total_amount_scale: allAmounts.totalAmount.scale,

      taxes: taxesResults.data,
    });
  }
}
