import { Result } from "@rdx/utils";
import { z } from "zod";
import { ValueObject } from "./value-object";

const DEFAULT_SCALE = 2;

interface IPercentageProps {
  amount: number;
  scale: number;
}

interface IPercentage {
  amount: number;
  scale: number;

  getValue(): IPercentageProps;
  toNumber(): number;
  toString(): string;
}

export class Percentage extends ValueObject<IPercentageProps> implements IPercentage {
  public static readonly DEFAULT_SCALE = DEFAULT_SCALE;
  public static readonly MIN_VALUE = 0;
  public static readonly MAX_VALUE = 100;

  public static readonly MIN_SCALE = 0;
  public static readonly MAX_SCALE = 2;

  protected static validate(values: IPercentageProps) {
    const schema = z.object({
      amount: z.number().int().min(Percentage.MIN_VALUE, "La cantidad no puede ser negativa."),
      scale: z
        .number()
        .int()
        .min(Percentage.MIN_SCALE)
        .max(
          Percentage.MAX_SCALE,
          `La escala debe estar entre ${Percentage.MIN_SCALE} y ${Percentage.MAX_SCALE}.`
        ),
    });

    return schema.safeParse(values);
  }

  static create(props: { amount: number; scale?: number }): Result<Percentage> {
    const { amount, scale = Percentage.DEFAULT_SCALE } = props;

    const validationResult = Percentage.validate({ amount, scale });
    if (!validationResult.success) {
      return Result.fail(new Error(validationResult.error.errors.map((e) => e.message).join(", ")));
    }

    // Cálculo del valor real del porcentaje
    const realValue = amount / Math.pow(10, scale);

    // Validación de rango
    if (realValue > Percentage.MAX_VALUE) {
      return Result.fail(new Error("El porcentaje no puede ser mayor a 100%."));
    }

    return Result.ok(new Percentage({ amount, scale }));
  }

  get amount(): number {
    return this.props.amount;
  }

  get scale(): number {
    return this.props.scale;
  }

  getValue(): IPercentageProps {
    return this.props;
  }

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

  toNumber(): number {
    return this.amount / Math.pow(10, this.scale);
  }

  toString(): string {
    return `${this.toNumber().toFixed(this.scale)}%`;
  }
}
