import { describe, it, expect } from 'vitest';

import { enforce } from '../../../n4s';

const runNumeric = (value: unknown) =>
  (
    enforce.isNumeric() as ReturnType<typeof enforce.isNumeric> & {
      run: (
        value: unknown,
      ) => ReturnType<ReturnType<typeof enforce.isNumeric>['run']>;
    }
  ).run(value);

describe('isNumeric', () => {
  describe('base predicate', () => {
    it('pass for numbers', () => {
      expect(enforce.isNumeric().run(0).pass).toBe(true);
      expect(enforce.isNumeric().run(143).pass).toBe(true);
      expect(enforce.isNumeric().run(-42).pass).toBe(true);
      expect(enforce.isNumeric().run(3.14).pass).toBe(true);
      expect(enforce.isNumeric().run(Infinity).pass).toBe(true);
      expect(enforce.isNumeric().run(-Infinity).pass).toBe(true);
    });

    it('pass for numeric strings', () => {
      expect(enforce.isNumeric().run('0').pass).toBe(true);
      expect(enforce.isNumeric().run('143').pass).toBe(true);
      expect(enforce.isNumeric().run('-42').pass).toBe(true);
      expect(enforce.isNumeric().run('3.14').pass).toBe(true);
    });

    it('fails for NaN', () => {
      const nan: any = NaN;
      expect(enforce.isNumeric().run(nan).pass).toBe(false);
    });

    it('fails for non-numeric strings', () => {
      expect(enforce.isNumeric().run('1hello').pass).toBe(false);
      expect(enforce.isNumeric().run('hi').pass).toBe(false);
      expect(enforce.isNumeric().run('').pass).toBe(false);
      expect(enforce.isNumeric().run('abc123').pass).toBe(false);
    });

    it('fails for other types', () => {
      // Type test: - testing that non-numeric types are rejected
      expect(runNumeric(true).pass).toBe(false);
      // Type test: - testing that non-numeric types are rejected
      expect(runNumeric(false).pass).toBe(false);
      // Type test: - testing that non-numeric types are rejected
      expect(runNumeric({}).pass).toBe(false);
      // Type test: - testing that non-numeric types are rejected
      expect(runNumeric([]).pass).toBe(false);
    });
  });

  describe('greaterThan', () => {
    it('pass when numeric string is greater', () => {
      expect(enforce.isNumeric().greaterThan(1).run('2').pass).toBe(true);
      expect(enforce.isNumeric().greaterThan(0).run('5').pass).toBe(true);
    });

    it('pass when number is greater', () => {
      expect(enforce.isNumeric().greaterThan(1).run(2).pass).toBe(true);
      expect(enforce.isNumeric().greaterThan(0).run(5).pass).toBe(true);
    });

    it('fails when value is not greater', () => {
      expect(enforce.isNumeric().greaterThan(5).run('5').pass).toBe(false);
      expect(enforce.isNumeric().greaterThan(5).run('3').pass).toBe(false);
      expect(enforce.isNumeric().greaterThan(5).run(3).pass).toBe(false);
    });
  });

  describe('lessThan', () => {
    it('pass when numeric string is less', () => {
      expect(enforce.isNumeric().lessThan(5).run('3').pass).toBe(true);
      expect(enforce.isNumeric().lessThan(0).run('-1').pass).toBe(true);
    });

    it('pass when number is less', () => {
      expect(enforce.isNumeric().lessThan(5).run(3).pass).toBe(true);
      expect(enforce.isNumeric().lessThan(0).run(-1).pass).toBe(true);
    });

    it('fails when value is not less', () => {
      expect(enforce.isNumeric().lessThan(5).run('5').pass).toBe(false);
      expect(enforce.isNumeric().lessThan(5).run('6').pass).toBe(false);
    });
  });

  describe('isBetween', () => {
    it('pass when numeric string is between', () => {
      expect(enforce.isNumeric().isBetween(0, 10).run('5').pass).toBe(true);
      expect(enforce.isNumeric().isBetween(0, 10).run('0').pass).toBe(true);
      expect(enforce.isNumeric().isBetween(0, 10).run('10').pass).toBe(true);
    });

    it('pass when number is between', () => {
      expect(enforce.isNumeric().isBetween(0, 10).run(5).pass).toBe(true);
    });

    it('fails when value is outside range', () => {
      expect(enforce.isNumeric().isBetween(0, 10).run('-1').pass).toBe(false);
      expect(enforce.isNumeric().isBetween(0, 10).run('11').pass).toBe(false);
    });
  });

  describe('numberEquals', () => {
    it('pass when numeric strings are equal', () => {
      expect(enforce.isNumeric().numberEquals('2').run('2').pass).toBe(true);
      expect(enforce.isNumeric().numberEquals(5).run('5').pass).toBe(true);
    });

    it('pass when number matches', () => {
      expect(enforce.isNumeric().numberEquals('2').run(2).pass).toBe(true);
    });

    it('fails when values are not equal', () => {
      expect(enforce.isNumeric().numberEquals('2').run('3').pass).toBe(false);
      expect(enforce.isNumeric().numberEquals(5).run('4').pass).toBe(false);
    });
  });

  describe('isEven', () => {
    it('pass for even numeric strings', () => {
      expect(enforce.isNumeric().isEven().run('0').pass).toBe(true);
      expect(enforce.isNumeric().isEven().run('2').pass).toBe(true);
      expect(enforce.isNumeric().isEven().run('42').pass).toBe(true);
    });

    it('pass for even numbers', () => {
      expect(enforce.isNumeric().isEven().run(2).pass).toBe(true);
      expect(enforce.isNumeric().isEven().run(42).pass).toBe(true);
    });

    it('fails for odd values', () => {
      expect(enforce.isNumeric().isEven().run('1').pass).toBe(false);
      expect(enforce.isNumeric().isEven().run('3').pass).toBe(false);
      expect(enforce.isNumeric().isEven().run(1).pass).toBe(false);
    });
  });

  describe('isOdd', () => {
    it('pass for odd numeric strings', () => {
      expect(enforce.isNumeric().isOdd().run('1').pass).toBe(true);
      expect(enforce.isNumeric().isOdd().run('3').pass).toBe(true);
      expect(enforce.isNumeric().isOdd().run('99').pass).toBe(true);
    });

    it('pass for odd numbers', () => {
      expect(enforce.isNumeric().isOdd().run(1).pass).toBe(true);
      expect(enforce.isNumeric().isOdd().run(3).pass).toBe(true);
    });

    it('fails for even values', () => {
      expect(enforce.isNumeric().isOdd().run('0').pass).toBe(false);
      expect(enforce.isNumeric().isOdd().run('2').pass).toBe(false);
      expect(enforce.isNumeric().isOdd().run(2).pass).toBe(false);
    });
  });

  describe('isNegative', () => {
    it('pass for negative numeric strings', () => {
      expect(enforce.isNumeric().isNegative().run('-1').pass).toBe(true);
      expect(enforce.isNumeric().isNegative().run('-42').pass).toBe(true);
    });

    it('pass for negative numbers', () => {
      expect(enforce.isNumeric().isNegative().run(-1).pass).toBe(true);
      expect(enforce.isNumeric().isNegative().run(-42).pass).toBe(true);
    });

    it('fails for positive values and zero', () => {
      expect(enforce.isNumeric().isNegative().run('0').pass).toBe(false);
      expect(enforce.isNumeric().isNegative().run('1').pass).toBe(false);
      expect(enforce.isNumeric().isNegative().run(1).pass).toBe(false);
    });
  });

  describe('toNumber', () => {
    it('transforms numeric strings to numbers in parse', () => {
      expect(enforce.isNumeric().toNumber().parse('1985')).toBe(1985);
    });

    it('coerces numeric strings in shape parsing', () => {
      const ChronoSchema = enforce.shape({
        travelerName: enforce.isString(),
        mission: enforce.isString(),
        birthYear: enforce.isNumeric().toNumber(),
        destinationYear: enforce.isNumeric(),
        plutoniumCores: enforce.isNumeric(),
      });

      expect(
        ChronoSchema.parse({
          travelerName: 'Marty McFly',
          mission: 'Save Doc',
          birthYear: '1968',
          destinationYear: '1955',
          plutoniumCores: 1,
        }),
      ).toEqual({
        travelerName: 'Marty McFly',
        mission: 'Save Doc',
        birthYear: 1968,
        destinationYear: '1955',
        plutoniumCores: 1,
      });
    });

    it('throws when parsing a value that is not a valid number', () => {
      expect(() => enforce.isNumeric().toNumber().parse('1.2.3')).toThrow();
    });
  });

  describe('isPositive', () => {
    it('pass for positive numeric strings', () => {
      expect(enforce.isNumeric().isPositive().run('1').pass).toBe(true);
      expect(enforce.isNumeric().isPositive().run('42').pass).toBe(true);
    });

    it('pass for positive numbers', () => {
      expect(enforce.isNumeric().isPositive().run(1).pass).toBe(true);
      expect(enforce.isNumeric().isPositive().run(42).pass).toBe(true);
    });

    it('fails for zero and negative values', () => {
      expect(enforce.isNumeric().isPositive().run('0').pass).toBe(false);
      expect(enforce.isNumeric().isPositive().run('-1').pass).toBe(false);
      expect(enforce.isNumeric().isPositive().run(-1).pass).toBe(false);
    });
  });
});
