import { test, describe, expect } from 'vitest';
import {
  baseDomainValidationSchema,
  dnsNameValidationSchema,
  dualStackValidationSchema,
  hostPrefixValidationSchema,
  ipBlockValidationSchema,
  ipValidationSchema,
  macAddressValidationSchema,
  nameValidationSchema,
  noProxyValidationSchema,
  pullSecretValidationSchema,
} from './..';
import { TFunction } from 'react-i18next';
import { CLUSTER_NAME_MAX_LENGTH } from './constants';

const t: TFunction = (t: string) => t;

describe('validationSchemas', () => {
  test('noProxyValidationSchema accepts input', async () => {
    const valid = [
      '.x.com',
      '.x.com,.y.org, .z.net   , u.foo.bar',
      '.svc',
      'localhost',
      '9999',
      '.9999',
      'kraken',
      'a-x.com',
      '.x.com,',
    ];

    await Promise.all(
      valid.map((value) =>
        noProxyValidationSchema(t)
          .validate(value)
          .catch(() => expect(value).toBe('was rejected but is valid')),
      ),
    );
  });

  test('noProxyValidationSchema rejects input', async () => {
    const invalid = ['.x.com.', '.x.com,.y.org., .z.net', '-x.com'];

    let counter = 0;
    await Promise.all(
      invalid.map((value) =>
        noProxyValidationSchema(t)
          .validate(value)
          .then(
            () => expect(value).toBe('should be rejected since it is invalid'),
            () => counter++,
          ),
      ),
    );

    expect(counter).toBe(invalid.length);
  });

  test('nameValidationSchema for clusters', async () => {
    const usedClusterNames = ['cluster-one', 'cluster-two'];
    const valid = [
      'cluster-one-foo',
      'cluster',
      'aa',
      'a9',
      '0a',
      'a'.repeat(CLUSTER_NAME_MAX_LENGTH),
    ];
    const invalid = [
      'cluster$',
      'cluster-',
      's',
      '-a',
      'x.y',
      'a@',
      'a%',
      'b'.repeat(CLUSTER_NAME_MAX_LENGTH + 1),
      ...usedClusterNames,
    ];

    const validationSchema = nameValidationSchema(t, usedClusterNames, '', true, false);

    await Promise.all(
      valid.map((value) =>
        validationSchema
          .validate(value)
          .catch((msg: string) => expect(value).toBe(`was rejected but is valid: ${msg}`)),
      ),
    );

    let counter = 0;
    await Promise.all(
      invalid.map((value) =>
        validationSchema.validate(value).then(
          () => expect(value).toBe('should be rejected since it is invalid'),
          () => counter++,
        ),
      ),
    );

    expect(counter).toBe(invalid.length);
  });

  test('pullSecretValidationSchema', async () => {
    const valid = ['', '{"auths": {}}'];
    const invalid = ['{}', '{"foo":"bar"}', '"auths":"{}"', '{"auths": "foo"}'];

    await Promise.all(
      valid.map((value) =>
        pullSecretValidationSchema(t)
          .validate(value)
          .catch((msg: string) => expect(value).toBe(`was rejected but is valid: ${msg}`)),
      ),
    );

    let counter = 0;
    await Promise.all(
      invalid.map((value) =>
        pullSecretValidationSchema(t)
          .validate(value)
          .then(
            () => expect(value).toBe('should be rejected since it is invalid'),
            () => counter++,
          ),
      ),
    );

    expect(counter).toBe(invalid.length);
  });

  test('ipValidationSchema', async () => {
    const valid = [
      '1.1.1.1',
      '0.0.0.0',
      '255.255.255.255',
      '2345:0425:2CA1:0000:0000:0567:5673:23b5',
      '2345:0425:2CA1:0::0567:5673:23b5',
      '::',
    ];
    const invalid = [
      '',
      '1.1.1.1.',
      '1.1.1',
      '1.1.1.',
      '255.255.255.256',
      '1.1.1.a',
      '1.1.1.*',
      '2345:0425:2CA1:0:0:0567:5673:',
    ];

    await Promise.all(
      valid.map((value) =>
        ipValidationSchema(t)
          .validate(value)
          .catch((msg: string) => expect(value).toBe(`was rejected but is valid: ${msg}`)),
      ),
    );

    let counter = 0;
    await Promise.all(
      invalid.map((value) =>
        ipValidationSchema(t)
          .validate(value)
          .then(
            () => expect(value).toBe('should be rejected since it is invalid'),
            () => counter++,
          ),
      ),
    );

    expect(counter).toBe(invalid.length);
  });

  test('macAddressValidationSchema', async () => {
    const valid = ['', '00:00:0A:BB:28:FC', '00:00:0A:BB:28:fc'];
    const invalid = [
      ':00:0A:BB:28:FC',
      '0:00:0A:BB:28:FC',
      '00:00:0A:BB:28:FG',
      '00:00:0A:BB:28:Fg',
      '00:00:0A:BB:28:',
      '00000ABB28FC',
      '00:00:0A:BB:28',
      '00:00:0A:BB:28 FC',
      '00:00:0A:BB:28-FC',
    ];

    await Promise.all(
      valid.map((value) =>
        macAddressValidationSchema(t)
          .validate(value)
          .catch((msg: string) => expect(value).toBe(`was rejected but is valid: ${msg}`)),
      ),
    );

    let counter = 0;
    await Promise.all(
      invalid.map((value) =>
        macAddressValidationSchema(t)
          .validate(value)
          .then(
            () => expect(value).toBe('should be rejected since it is invalid'),
            () => counter++,
          ),
      ),
    );

    expect(counter).toBe(invalid.length);
  });

  test('ipBlockValidationSchema', async () => {
    const valid = ['192.1.1.0/24', '192.1.0.0/16', '193.0.0.0/8'];
    const invalid = [
      '',
      '192.168.1.0',
      '192.168.1.0/24',
      '192.1.1.0/16',
      '192.0.0.0/8' /* Overlap */,
    ];

    const validationSchema = ipBlockValidationSchema(['192.168.1.0'], t);
    await Promise.all(
      valid.map((value) =>
        validationSchema
          .validate(value)
          .catch((msg: string) => expect(value).toBe(`was rejected but is valid: ${msg}`)),
      ),
    );

    let counter = 0;
    await Promise.all(
      invalid.map((value) =>
        validationSchema.validate(value).then(
          () => expect(value).toBe('should be rejected since it is invalid'),
          () => counter++,
        ),
      ),
    );

    expect(counter).toBe(invalid.length);
  });

  test('dnsNameValidationSchema', async () => {
    const valid = ['', 'aa.com', 'aa.com.com.com.com', 'aa.com.' + 'b'.repeat(63)];
    const invalid = [
      '00:00:0A:BB:28:FC',
      '192.1.1.1',
      '.aa.com',
      'aa.com.',
      'a',
      'aa',
      'aa,com',
      'aa.com.' + 'b'.repeat(63 + 1),
      'aa.com/bb',
    ];

    await Promise.all(
      valid.map((value) =>
        dnsNameValidationSchema(t)
          .validate(value)
          .catch((msg: string) => expect(value).toBe(`was rejected but is valid: ${msg}`)),
      ),
    );

    let counter = 0;
    await Promise.all(
      invalid.map((value) =>
        dnsNameValidationSchema(t)
          .validate(value)
          .then(
            () => expect(value).toBe('should be rejected since it is invalid'),
            () => counter++,
          ),
      ),
    );

    expect(counter).toBe(invalid.length);
  });

  test('hostPrefixValidationSchema for IPv4', async () => {
    const valid = [16, 25];
    const invalid = ['', 'a', 0, 1, 33, 15, 26];

    const validationSchema = hostPrefixValidationSchema('192.168.0.0/16', t);
    await Promise.all(
      valid.map((value) =>
        validationSchema
          .validate(value)
          .catch((msg: string) => expect(value).toBe(`was rejected but is valid: ${msg}`)),
      ),
    );

    let counter = 0;
    await Promise.all(
      invalid.map((value) =>
        validationSchema.validate(value).then(
          () => expect(value).toBe('should be rejected since it is invalid'),
          () => counter++,
        ),
      ),
    );

    expect(counter).toBe(invalid.length);
  });

  test('hostPrefixValidationSchema for IPv6', async () => {
    const valid = [8, 32, 128];
    const invalid = ['', 'a', 0, 1, 7, 129];

    const validationSchema = hostPrefixValidationSchema('2002::1234:abcd:ffff:c0a8:101/64', t);
    await Promise.all(
      valid.map((value) =>
        validationSchema
          .validate(value)
          .catch((msg: string) => expect(value).toBe(`was rejected but is valid: ${msg}`)),
      ),
    );

    let counter = 0;
    await Promise.all(
      invalid.map((value) =>
        validationSchema.validate(value).then(
          () => expect(value).toBe('should be rejected since it is invalid'),
          () => counter++,
        ),
      ),
    );

    expect(counter).toBe(invalid.length);
  });

  test('baseDomainNameValidationSchema', async () => {
    const valid = ['a.com', 'co', '1c'];
    const invalid = [
      '    ',
      'a g',
      'iamnotavaliddnsdomain-iamnotavaliddnsdomain-iamnotavaliddnsdomain',
    ];

    await Promise.all(
      valid.map((value) =>
        baseDomainValidationSchema(t)
          .validate(value)
          .catch(() => expect(value).toBe(`was rejected but is valid`)),
      ),
    );

    let counter = 0;
    await Promise.all(
      invalid.map((value) =>
        baseDomainValidationSchema(t)
          .validate(value)
          .then(
            () => expect(value).toBe('should be rejected since it is invalid'),
            () => counter++,
          ),
      ),
    );

    expect(counter).toBe(invalid.length);
  });

  describe('dualStackValidationSchema', () => {
    describe('OCP < 4.12 (legacy behavior)', () => {
      const schema = dualStackValidationSchema('machine networks', t, '4.11');

      test('validates IPv4 as primary and IPv6 as secondary', async () => {
        const validValues = [[{ cidr: '192.168.1.0/24' }, { cidr: '2001:db8::/64' }]];

        for (const value of validValues) {
          await expect(schema.validate(value)).resolves.toBe(value);
        }
      });

      test('rejects IPv6 as primary for OCP < 4.12', async () => {
        const invalidValues = [[{ cidr: '2001:db8::/64' }, { cidr: '192.168.1.0/24' }]];

        for (const value of invalidValues) {
          await expect(schema.validate(value)).rejects.toThrow(
            'First network has to be IPv4 subnet',
          );
        }
      });
    });

    describe('OCP >= 4.12 (new behavior)', () => {
      const schema = dualStackValidationSchema('machine networks', t, '4.12');

      test('validates IPv4 as primary and IPv6 as secondary', async () => {
        const validValues = [[{ cidr: '192.168.1.0/24' }, { cidr: '2001:db8::/64' }]];

        for (const value of validValues) {
          await expect(schema.validate(value)).resolves.toBe(value);
        }
      });

      test('validates IPv6 as primary and IPv4 as secondary', async () => {
        const validValues = [[{ cidr: '2001:db8::/64' }, { cidr: '192.168.1.0/24' }]];

        for (const value of validValues) {
          await expect(schema.validate(value)).resolves.toBe(value);
        }
      });
    });

    describe('no version specified (defaults to legacy behavior)', () => {
      const schema = dualStackValidationSchema('machine networks', t);

      test('validates IPv4 as primary and IPv6 as secondary', async () => {
        const validValues = [[{ cidr: '192.168.1.0/24' }, { cidr: '2001:db8::/64' }]];

        for (const value of validValues) {
          await expect(schema.validate(value)).resolves.toBe(value);
        }
      });

      test('rejects IPv6 as primary when no version specified', async () => {
        const invalidValues = [[{ cidr: '2001:db8::/64' }, { cidr: '192.168.1.0/24' }]];

        for (const value of invalidValues) {
          await expect(schema.validate(value)).rejects.toThrow(
            'First network has to be IPv4 subnet',
          );
        }
      });
    });

    describe('edge cases', () => {
      test('validates maximum 2 networks', async () => {
        const schema = dualStackValidationSchema('machine networks', t, '4.12');
        const invalidValue = [
          { cidr: '192.168.1.0/24' },
          { cidr: '2001:db8::/64' },
          { cidr: '10.0.0.0/24' },
        ];

        await expect(schema.validate(invalidValue)).rejects.toThrow(
          'ai:Maximum number of {{field}} subnets in dual stack is 2',
        );
      });
    });
  });
});
