import { TaxCatalogProvider } from "@erp/core";
import { Percentage, ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { z } from "zod/v4";

const DEFAULT_SCALE = 2;
const DEFAULT_MIN_VALUE = 0;
const DEFAULT_MAX_VALUE = 100;

const DEFAULT_MIN_SCALE = 0;
const DEFAULT_MAX_SCALE = 4;

export interface TaxProps {
  code: string; // iva_21
  name: string; // 21% IVA
  value: number; // 2100
  scale: number; // 2
}

export class Tax extends ValueObject<TaxProps> {
  static DEFAULT_SCALE = DEFAULT_SCALE;
  static MIN_VALUE = DEFAULT_MIN_VALUE;
  static MAX_VALUE = DEFAULT_MAX_VALUE;
  static MIN_SCALE = DEFAULT_MIN_SCALE;
  static MAX_SCALE = DEFAULT_MAX_SCALE;

  private static CODE_REGEX = /^[a-z0-9_:-]+$/;

  private _percentage!: Percentage;

  protected static validate(values: TaxProps) {
    const schema = z.object({
      value: z
        .number()
        .int()
        .min(Tax.MIN_VALUE, "La tasa de impuesto no puede ser negativa.")
        .max(Tax.MAX_VALUE * 10 ** Tax.MAX_SCALE, "La tasa de impuesto es demasiado alta."),
      scale: z
        .number()
        .int()
        .min(Tax.MIN_SCALE)
        .max(Tax.MAX_SCALE, `La escala debe estar entre ${Tax.MIN_SCALE} y ${Tax.MAX_SCALE}.`),
      name: z
        .string()
        .min(1, "El nombre del impuesto es obligatorio.")
        .max(100, "El nombre del impuesto no puede exceder 100 caracteres."),
      code: z
        .string()
        .min(1, "El código del impuesto es obligatorio.")
        .max(40, "El código del impuesto no puede exceder 40 caracteres.")
        .regex(Tax.CODE_REGEX, "El código contiene caracteres no permitidos."),
    });

    return schema.safeParse(values);
  }

  static create(props: TaxProps): Result<Tax> {
    const { value, scale = Tax.DEFAULT_SCALE, name, code } = props;

    const validationResult = Tax.validate({ value, scale, name, code });
    if (!validationResult.success) {
      return Result.fail(new Error(validationResult.error.issues.map((e) => e.message).join(", ")));
    }

    const realValue = value / 10 ** scale;
    if (realValue > Tax.MAX_VALUE) {
      return Result.fail(new Error("La tasa de impuesto no puede ser mayor a 100%."));
    }

    return Result.ok(new Tax({ value, scale, name, code }));
  }

  /**
   * Crea un Tax usando solo el 'code', resolviendo el resto de datos desde el catálogo.
   * @param code Código del impuesto (p.ej. "iva_21")
   * @param provider Proveedor del catálogo de impuestos
   */
  static createFromCode(code: string, provider: TaxCatalogProvider): Result<Tax, Error> {
    const normalized = (code ?? "").trim().toLowerCase();
    if (!normalized || !Tax.CODE_REGEX.test(normalized)) {
      return Result.fail(new Error(`Código de impuesto inválido: "${code}"`));
    }

    const maybeItem = provider.findByCode(normalized);
    if (maybeItem.isNone()) {
      return Result.fail(
        new Error(`Código de impuesto no encontrado en el catálogo: "${normalized}"`)
      );
    }

    const item = maybeItem.unwrap()!;
    // Delegamos en create para reusar validación y límites
    return Tax.create({
      value: Number(item.value),
      scale: Number(item.scale) ?? Tax.DEFAULT_SCALE,
      name: item.name,
      code: item.code, // guardamos el code tal cual viene del catálogo
    });
  }

  protected constructor(props: TaxProps) {
    super(props);
    this._percentage = Percentage.create({
      value: this.props.value,
      scale: this.props.scale,
    }).data;
  }

  get value(): number {
    return this.props.value;
  }
  get scale(): number {
    return this.props.scale;
  }
  get name(): string {
    return this.props.name;
  }
  get code(): string {
    return this.props.code;
  }

  get percentage(): Percentage {
    return this._percentage;
  }

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

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

  /** Devuelve el valor real de la tasa como número decimal (ej: 21.00) */
  toNumber(): number {
    return this.value / 10 ** this.scale;
  }

  /** Devuelve la tasa formateada como porcentaje (ej: "21.00%") */
  toString(): string {
    return `${this.toNumber().toFixed(this.scale)}%`;
  }

  isZero(): boolean {
    return this.toNumber() === 0;
  }
  isPositive(): boolean {
    return this.toNumber() > 0;
  }

  equalsTo(other: Tax): boolean {
    return this.equals(other);
  }
  greaterThan(other: Tax): boolean {
    return this.toNumber() > other.toNumber();
  }

  lessThan(other: Tax): boolean {
    return this.toNumber() < other.toNumber();
  }

  toJSON() {
    return {
      value: this.value,
      scale: this.scale,
      name: this.name,
      code: this.code,
      percentage: this.toNumber(),
      formatted: this.toString(),
    };
  }
}
