import { CurrencyCode, DomainEntity, LanguageCode, Percentage, UniqueID } from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import {
  CustomerInvoiceItemDescription,
  ItemAmount,
  ItemDiscount,
  ItemQuantity,
} from "../../value-objects";
import { ItemTaxTotal, ItemTaxes } from "../item-taxes";

export interface CustomerInvoiceItemProps {
  description: Maybe<CustomerInvoiceItemDescription>;
  quantity: Maybe<ItemQuantity>; // Cantidad de unidades
  unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura

  discountPercentage: Maybe<ItemDiscount>; // % descuento

  taxes: ItemTaxes;

  languageCode: LanguageCode;
  currencyCode: CurrencyCode;
}

export interface ICustomerInvoiceItem {
  isNonValued: boolean;

  description: Maybe<CustomerInvoiceItemDescription>;

  quantity: Maybe<ItemQuantity>; // Cantidad de unidades
  unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura

  discountPercentage: Maybe<ItemDiscount>; // % descuento

  taxes: ItemTaxes;

  languageCode: LanguageCode;
  currencyCode: CurrencyCode;

  getSubtotalAmount(): ItemAmount;
  getDiscountAmount(): ItemAmount;

  getTaxableAmount(): ItemAmount;
  getTaxesAmount(): ItemAmount;
  getTotalAmount(): ItemAmount;
}

export class CustomerInvoiceItem
  extends DomainEntity<CustomerInvoiceItemProps>
  implements ICustomerInvoiceItem
{
  protected _isNonValued!: boolean;

  public static create(
    props: CustomerInvoiceItemProps,
    id?: UniqueID
  ): Result<CustomerInvoiceItem, Error> {
    const item = new CustomerInvoiceItem(props, id);

    // Reglas de negocio / validaciones
    // ...
    // ...

    return Result.ok(item);
  }

  protected constructor(props: CustomerInvoiceItemProps, id?: UniqueID) {
    super(props, id);

    this._isNonValued = this.quantity.isNone() || this.unitAmount.isNone();
  }

  get isNonValued(): boolean {
    return this._isNonValued;
  }

  get description(): Maybe<CustomerInvoiceItemDescription> {
    return this.props.description;
  }

  get quantity(): Maybe<ItemQuantity> {
    return this.props.quantity;
  }

  get unitAmount(): Maybe<ItemAmount> {
    return this.props.unitAmount;
  }

  get discountPercentage(): Maybe<Percentage> {
    return this.props.discountPercentage;
  }

  get languageCode(): LanguageCode {
    return this.props.languageCode;
  }

  get currencyCode(): CurrencyCode {
    return this.props.currencyCode;
  }

  get taxes(): ItemTaxes {
    return this.props.taxes;
  }

  getProps(): CustomerInvoiceItemProps {
    return this.props;
  }

  toPrimitive() {
    return this.getProps();
  }

  /**
   * @private
   * @summary Calcula el importe de descuento a partir del subtotal y el porcentaje.
   * @param subtotalAmount - Importe subtotal.
   * @returns El importe de descuento calculado.
   */
  private _getDiscountAmount(subtotalAmount: ItemAmount): ItemAmount {
    const discount = this.discountPercentage.match(
      (percentage) => percentage,
      () => ItemDiscount.zero()
    );
    return subtotalAmount.percentage(discount);
  }

  /**
   * @private
   * @summary Calcula el importe imponible restando el descuento al subtotal.
   * @param subtotalAmount - Importe subtotal.
   * @param discountAmount - Importe de descuento.
   * @returns El importe imponible resultante.
   */
  private _getTaxableAmount(subtotalAmount: ItemAmount, discountAmount: ItemAmount): ItemAmount {
    return subtotalAmount.subtract(discountAmount);
  }

  /**
   * @private
   * @summary Calcula el importe total de impuestos sobre la base imponible.
   * @param taxableAmount - Importe imponible.
   * @returns El importe de impuestos calculado.
   */
  private _getTaxesAmount(taxableAmount: ItemAmount): ItemAmount {
    return this.props.taxes.getTaxesAmount(taxableAmount);
  }

  /**
   * @private
   * @summary Calcula el importe total sumando base imponible e impuestos.
   * @param taxableAmount - Importe imponible.
   * @param taxesAmount - Importe de impuestos.
   * @returns El importe total del ítem.
   */
  private _getTotalAmount(taxableAmount: ItemAmount, taxesAmount: ItemAmount): ItemAmount {
    return taxableAmount.add(taxesAmount);
  }

  /**
   * @summary Calcula el subtotal del ítem (cantidad × importe unitario).
   * @returns Un `ItemAmount` con el subtotal del ítem.
   * @remarks
   * Si la cantidad o el importe unitario no están definidos, se asumen valores cero.
   */
  public getSubtotalAmount(): ItemAmount {
    const curCode = this.currencyCode.code;
    const quantity = this.quantity.match(
      (quantity) => quantity,
      () => ItemQuantity.zero()
    );
    const unitAmount = this.unitAmount.match(
      (unitAmount) => unitAmount,
      () => ItemAmount.zero(curCode)
    );

    return unitAmount.multiply(quantity);
  }

  /**
   * @summary Calcula el importe total de descuento del ítem.
   * @returns Un `ItemAmount` con el importe descontado.
   */
  public getDiscountAmount(): ItemAmount {
    return this._getDiscountAmount(this.getSubtotalAmount());
  }

  /**
   * @summary Calcula el importe imponible (subtotal − descuento).
   * @returns Un `ItemAmount` con la base imponible del ítem.
   */
  public getTaxableAmount(): ItemAmount {
    return this._getTaxableAmount(this.getSubtotalAmount(), this.getDiscountAmount());
  }

  /**
   * @summary Calcula el importe total de impuestos aplicados al ítem.
   * @returns Un `ItemAmount` con el total de impuestos.
   */
  public getTaxesAmount(): ItemAmount {
    return this._getTaxesAmount(this.getTaxableAmount());
  }

  /**
   * @summary Calcula el importe total final del ítem (base imponible + impuestos).
   * @returns Un `ItemAmount` con el importe total.
   */
  public getTotalAmount(): ItemAmount {
    const taxableAmount = this.getTaxableAmount();
    const taxesAmount = this._getTaxesAmount(taxableAmount);

    return this._getTotalAmount(taxableAmount, taxesAmount);
  }

  /**
   * @summary Obtiene los importes de impuestos agrupados por tipo de impuesto.
   * @returns Una colección con la base imponible y el importe de impuestos por cada tipo.
   */
  public getTaxesAmountByTaxes(): ItemTaxTotal[] {
    return this.taxes.getTaxesAmountByTaxes(this.getTaxableAmount());
  }

  /**
   * @summary Devuelve todos los importes calculados del ítem en un único objeto.
   * @returns Un objeto con las propiedades:
   * - `subtotalAmount`
   * - `discountAmount`
   * - `taxableAmount`
   * - `taxesAmount`
   * - `totalAmount`
   * @remarks
   * Este método es útil para mostrar todos los cálculos en la interfaz de usuario
   * o serializar el ítem con sus valores calculados.
   */
  public getAllAmounts() {
    const subtotalAmount = this.getSubtotalAmount();
    const discountAmount = this._getDiscountAmount(subtotalAmount);
    const taxableAmount = this._getTaxableAmount(subtotalAmount, discountAmount);
    const taxesAmount = this._getTaxesAmount(taxableAmount);
    const totalAmount = this._getTotalAmount(taxableAmount, taxesAmount);

    return {
      subtotalAmount,
      discountAmount,
      taxableAmount,
      taxesAmount,
      totalAmount,
    };
  }
}
