import Joi from "joi";
import { Currencies } from "../../../../../utilities/currencies";
import { RuleValidator } from "../../RuleValidator";
import { DomainError, handleDomainError } from "../../errors";
import { INullableValueObjectOptions, NullableValueObject } from "../NullableValueObject";
import { Result } from "../Result";

export interface ICurrencyData {
  symbol: string;
  name: string;
  symbol_native: string;
  decimal_digits: number;
  rounding: number;
  code: string;
  name_plural: string;
}

export interface ICurrencyDataOptions extends INullableValueObjectOptions {}

export class CurrencyData extends NullableValueObject<ICurrencyData> {
  public static readonly DEFAULT_CURRENCY_CODE = "EUR";
  public static readonly CURRENCIES = Currencies;

  get symbol(): string {
    return this.props ? String(this.props.symbol_native) : "";
  }

  get code(): string {
    return this.props ? String(this.props.code) : "";
  }

  protected static validate(value: string, options: ICurrencyDataOptions) {
    const rule = Joi.alternatives(
      RuleValidator.RULE_ALLOW_EMPTY.default(""),
      Joi.string()
        .uppercase()
        .valid(...Object.keys(Currencies))
        .label(String(options.label))
    );

    return RuleValidator.validate<string>(rule, value);
  }

  public static createFromCode(currencyCode: string, options: ICurrencyDataOptions = {}) {
    const _options = {
      ...options,
      label: options.label ? options.label : "current_code",
    };

    const validationResult = CurrencyData.validate(currencyCode, _options);

    if (validationResult.isFailure) {
      return Result.fail(
        handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options)
      );
    }
    return Result.ok(new CurrencyData(Currencies[validationResult.object]));
  }

  public static createDefaultCode() {
    return CurrencyData.createFromCode(CurrencyData.DEFAULT_CURRENCY_CODE);
  }

  public isEmpty(): boolean {
    return this.isNull() || this.props === undefined;
  }

  public toString = (): string => {
    return this.code;
  };

  public toPrimitive(): string {
    return this.toString();
  }
}
