import type { MoneyDTO } from "@erp/core/common";
import Dinero from "dinero.js";

type DineroPlain = { amount: number; precision: number; currency: string };

const isEmptyMoneyDTO = (dto?: MoneyDTO | null) =>
  !dto || dto.value?.trim?.() === "" || dto.scale?.trim?.() === "";

/**
 * Convierte un MoneyDTO a número con precisión (sin moneda).
 */
const toNumber = (dto?: MoneyDTO | null, fallbackScale = 2): number => {
  if (isEmptyMoneyDTO(dto)) {
    return 0;
  }
  const scale = Number(dto!.scale || fallbackScale);
  return Number(dto!.value || 0) / 10 ** scale;
};

/**
 * Convierte un MoneyDTO a cadena numérica con precisión (sin moneda).
 * Puede devolver cadena vacía
 */
const toNumericString = (dto?: MoneyDTO | null, fallbackScale = 2): string => {
  if (isEmptyMoneyDTO(dto)) {
    return "";
  }
  return toNumber(dto, fallbackScale).toString();
};

/**
 * Convierte número a MoneyDTO.
 */
const fromNumber = (amount: number, currency: string = "EUR", scale = 2): MoneyDTO => {
  return {
    value: String(Math.round(amount * 10 ** scale)),
    scale: String(scale),
    currency_code: currency,
  };
};

/**
 * Convierte cadena numérica a MoneyDTO.
 */
const fromNumericString = (amount?: string, currency: string = "EUR", scale = 2): MoneyDTO => {
  if (!amount || amount?.trim?.() === "") {
    return {
      value: "",
      scale: String(scale),
      currency_code: currency,
    };
  }
  return {
    value: String(Math.round(Number(amount) * 10 ** scale)),
    scale: String(scale),
    currency_code: currency,
  };
};

/**
 * Normaliza un MoneyDTO incompleto o malformado.
 */
const normalizeDTO = (dto: MoneyDTO, fallbackCurrency = "EUR"): Required<MoneyDTO> => {
  const v = /^-?\d+$/.test(dto?.value ?? "") ? dto.value : "0";
  const s = /^\d+$/.test(dto?.scale ?? "") ? dto.scale : "2";
  const c = (dto?.currency_code || fallbackCurrency) as string;
  return { value: v, scale: s, currency_code: c };
};

/**
 * Formatea un MoneyDTO según locale.
 */
const formatDTO = (dto: MoneyDTO, locale: string = "es-ES"): string => {
  const { value, scale, currency_code } = normalizeDTO(dto);
  const num = Number(value) / 10 ** Number(scale);
  return new Intl.NumberFormat(locale, { style: "currency", currency: currency_code }).format(num);
};

export const MoneyDTOHelper = {
  isEmpty: isEmptyMoneyDTO,
  toNumber,
  toNumericString,
  fromNumber,
  fromNumericString,
  formatDTO,
};

/**
 * Convierte un DTO a una instancia Dinero.js.
 */
function dineroFromDTO(dto: MoneyDTO, fallbackCurrency = "EUR"): Dinero.Dinero {
  const n = normalizeDTO(dto, fallbackCurrency);
  return Dinero({
    amount: Number.parseInt(n.value, 10),
    precision: Number.parseInt(n.scale, 10),
    currency: n.currency_code as string,
  });
}

/**
 * Convierte una instancia Dinero a un DTO.
 */
function dtoFromDinero(d: Dinero.Dinero): MoneyDTO {
  const { amount, precision, currency } = d.toObject() as DineroPlain;
  return {
    value: amount.toString(),
    scale: precision.toString(),
    currency_code: currency,
  };
}

/**
 * Suma una lista de MoneyDTO.
 */
function sumDTO(list: MoneyDTO[], fallbackCurrency = "EUR"): MoneyDTO {
  if (list.length === 0) return { value: "0", scale: "2", currency_code: fallbackCurrency };
  const sum = list.map((x) => dineroFromDTO(x, fallbackCurrency)).reduce((a, b) => a.add(b));
  return dtoFromDinero(sum);
}

/**
 * Multiplica un MoneyDTO por un número.
 */
function multiplyDTO(
  dto: MoneyDTO,
  multiplier: number,
  rounding: Dinero.RoundingMode = "HALF_EVEN",
  fallbackCurrency = "EUR"
): MoneyDTO {
  const d = dineroFromDTO(dto, fallbackCurrency).multiply(multiplier, rounding);
  return dtoFromDinero(d);
}

/**
 * Calcula un porcentaje de un MoneyDTO.
 */
function percentageDTO(
  dto: MoneyDTO,
  percent: number,
  rounding: Dinero.RoundingMode = "HALF_EVEN",
  fallbackCurrency = "EUR"
): MoneyDTO {
  const d = dineroFromDTO(dto, fallbackCurrency).percentage(percent, rounding);
  return dtoFromDinero(d);
}
