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 { 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 _getDiscountAmount(subtotalAmount: ItemAmount): ItemAmount {
    const discount = this.discountPercentage.match(
      (percentage) => percentage,
      () => ItemDiscount.zero()
    );
    return subtotalAmount.percentage(discount);
  }

  private _getTaxableAmount(subtotalAmount: ItemAmount, discountAmount: ItemAmount): ItemAmount {
    return subtotalAmount.subtract(discountAmount);
  }

  private _getTaxesAmount(taxableAmount: ItemAmount): ItemAmount {
    return this.props.taxes.getTaxesAmount(taxableAmount);
  }

  private _getTotalAmount(taxableAmount: ItemAmount, taxesAmount: ItemAmount): ItemAmount {
    return taxableAmount.add(taxesAmount);
  }

  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);
  }

  public getDiscountAmount(): ItemAmount {
    return this._getDiscountAmount(this.getSubtotalAmount());
  }

  public getTaxableAmount(): ItemAmount {
    return this._getTaxableAmount(this.getSubtotalAmount(), this.getDiscountAmount());
  }

  public getTaxesAmount(): ItemAmount {
    return this._getTaxesAmount(this.getTaxableAmount());
  }

  public getTotalAmount(): ItemAmount {
    const taxableAmount = this.getTaxableAmount();
    const taxesAmount = this._getTaxesAmount(taxableAmount);

    return this._getTotalAmount(taxableAmount, taxesAmount);
  }

  public getTaxesAmountByTaxes() {
    return this.taxes.getTaxesAmountByTaxes(this.getTaxableAmount());
  }

  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,
    };
  }
}
