import {
  SUPPORTED_RECORD_TYPES,
  RecordMinLengthKind,
  ErrorKeyKind,
  WarningKeyKind,
  ParserInvalidArgumentsError,
  ParsingError,
} from '../../src';
import { ExampleDomain } from '../../example/example_types';
import { getErrorText } from '../../example/example_consumer';
import * as complexMock from '../mocks/complex';
import * as validMock from '../mocks/valid';
import * as invalidApiMock from '../mocks/invalid_api';
import * as invalidClientMock from '../mocks/invalid_client';
import * as edgeMock from '../mocks/edge';
import {
  testImportZoneFileAndCallApi,
  testImportZoneFile,
  spyOnCreateApi,
  MockSupportedRecordType,
} from '../utils';

type MockWithoutTypeForKey<Key extends string> = {
  [K in Key]?: ExampleDomain.ClientRecord[];
};
type MockTypeForKey<Key extends string> = {
  [K in MockSupportedRecordType]: MockWithoutTypeForKey<Key>;
};

type IValidMockKeyType = keyof typeof validMock;
type IInvalidApiMockKeyType = keyof typeof invalidApiMock;
type IComplexMockKeyType = keyof typeof complexMock;

describe('Import zone file (features)', () => {
  const validTestCases: MockTypeForKey<IValidMockKeyType> = {
    GENERAL: {
      ttlWithNameSimilarToTTL: [
        {
          name: '100.canada.igloo.com.',
          endpoints: ['2.3.4.5'],
          type: 'A',
          ttl: 42,
        },
      ],
      backwardsTTL: [
        {
          name: 'eh.igloo.com.',
          endpoints: ['2.3.4.5'],
          type: 'A',
          ttl: 42,
        },
      ],
      optionalTTLAndClass: [
        {
          name: 'neither.igloo.com.',
          endpoints: ['1.2.3.4'],
          type: 'A',
          ttl: 4242,
        },
        {
          name: 'ttl.igloo.com.',
          endpoints: ['1.2.3.4'],
          type: 'A',
          ttl: 42,
        },
        {
          name: 'class.igloo.com.',
          endpoints: ['1.2.3.4'],
          type: 'A',
          ttl: 4242,
        },
        {
          name: 'ttl.class.igloo.com.',
          endpoints: ['1.2.3.4'],
          type: 'A',
          ttl: 42,
        },
        {
          name: 'class.ttl.igloo.com.',
          endpoints: ['1.2.3.4'],
          type: 'A',
          ttl: 42,
        },
      ],
      comments1: [
        {
          name: 'eh.igloo.com.',
          endpoints: ['2.3.4.5'],
          type: 'A',
          ttl: 42,
        },
        {
          name: 'www.igloo.com.',
          endpoints: ['1.2.3.4'],
          type: 'A',
          ttl: 42,
        },
      ],
      nameNotTooLong: [
        {
          name:
            '1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.igloo.com.',
          endpoints: ['1.2.3.4'],
          ttl: 60,
          type: 'A',
        },
      ],
      utfCharacters: [
        {
          name: '©.igloo.com.',
          endpoints: ['1.2.3.4'],
          ttl: 42,
          type: 'A',
        },
        {
          name: '¿.igloo.com.',
          endpoints: ['1.2.3.4'],
          ttl: 42,
          type: 'A',
        },
      ],
      escapeInTXT: [
        {
          name: 'www.igloo.com.',
          endpoints: ['"\\101\\"'],
          ttl: 42,
          type: 'TXT',
        },
      ],
      escapeInCNAMEValue: [
        {
          name: 'www.igloo.com.',
          endpoints: ['\\008\\99.igloo.com.'],
          ttl: 42,
          type: 'CNAME',
        },
      ],
      defaultOrigin: [
        { name: 'igloo.com.', endpoints: ['1.2.3.4'], ttl: 42, type: 'A' },
        {
          name: 'www.igloo.com.',
          endpoints: ['1.2.3.4'],
          ttl: 42,
          type: 'A',
        },
      ],
      specialTTL: [
        {
          name: 'eh.igloo.com.',
          endpoints: ['2.3.4.5'],
          ttl: 42,
          type: 'A',
        },
      ],
    },
    AAAA: {
      aaaaWithNameAndTopLevelTTL: [
        {
          name: 'example.igloo.com.',
          endpoints: ['::1'],
          ttl: 42,
          type: 'AAAA',
        },
      ],
      aaaaWithAtNameAndInlineTTL: [
        {
          name: 'igloo.com.',
          endpoints: ['::1'],
          ttl: 42,
          type: 'AAAA',
        },
      ],
    },
    A: {
      aWithNameAndTopLevelTTL: [
        {
          name: 'example.igloo.com.',
          endpoints: ['127.0.0.1'],
          ttl: 42,
          type: 'A',
        },
      ],
      aWithAtNameAndInlineTTL: [
        {
          name: 'igloo.com.',
          endpoints: ['127.0.0.1'],
          ttl: 42,
          type: 'A',
        },
      ],
    },
    CAA: {
      caaExampleJustIssueWithInNoNameTopLevelTTL: [
        {
          name: 'igloo.com.',
          endpoints: ['0 issue "ca.igloo.com"'],
          ttl: 42,
          type: 'CAA',
        },
      ],
      caaExampleJustIssueNoNameTopLevelTTL: [
        {
          name: 'igloo.com.',
          endpoints: ['0 issue "ca.igloo.com"'],
          ttl: 42,
          type: 'CAA',
        },
      ],
      caaExampleIssueAndIoDefAndTopLevelTTL: [
        {
          name: 'igloo.com.',
          endpoints: [
            '0 issue "ca.igloo.com"',
            '0 iodef "mailto:security@igloo.com"',
            '0 iodef "http://iodef.igloo.com/"',
          ],
          ttl: 42,
          type: 'CAA',
        },
      ],
      caaExampleIssueAndIoDefAndTopLevelTTLNoQuotes: [
        {
          name: 'igloo.com.',
          endpoints: [
            '0 issue ca.igloo.com',
            '0 iodef mailto:security@igloo.com',
            '0 iodef http://iodef.igloo.com/',
          ],
          ttl: 42,
          type: 'CAA',
        },
      ],
      caaExampleJustIoDef: [
        {
          name: 'igloo.com.',
          endpoints: ['0 iodef "mailto:security@igloo.com"'],
          type: 'CAA',
          ttl: 42,
        },
      ],
      caaExampleInlineTTL: [
        {
          name: 'igloo.com.',
          endpoints: [
            '0 issue "ca.igloo.com"',
            '0 iodef "mailto:security@igloo.com"',
          ],
          ttl: 42,
          type: 'CAA',
        },
      ],
      caaExampleJustIssue: [
        {
          name: 'igloo.com.',
          endpoints: ['0 issue "ca.igloo.com"'],
          ttl: 42,
          type: 'CAA',
        },
      ],
    },
    MX: {
      mxExampleAt: [
        {
          name: 'igloo.com.',
          endpoints: ['0 mail1', '10 mail2'],
          ttl: 42,
          type: 'MX',
        },
      ],
    },
    CNAME: {
      cnameWithTopLevelTTL: [
        {
          name: 'mail1.igloo.com.',
          endpoints: ['mail'],
          ttl: 42,
          type: 'CNAME',
        },
        {
          name: 'mail2.igloo.com.',
          endpoints: ['mail'],
          ttl: 42,
          type: 'CNAME',
        },
      ],
      cnameWithInlineAndSpacesTTL: [
        {
          name: 'mail1.igloo.com.',
          endpoints: ['mail'],
          ttl: 42,
          type: 'CNAME',
        },
        {
          name: 'mail2.igloo.com.',
          endpoints: ['mail'],
          ttl: 60,
          type: 'CNAME',
        },
      ],
    },
    NAPTR: {
      naptrExampleTopLevelTTL: [
        {
          name: 'igloo.com.',
          endpoints: [
            '100 10 "U" "E2U+sip" "!^.*$!sip:customer-service@example.com!" .',
            '102 10 "U" "E2U+email" "!^.*$!mailto:information@example.com!" .',
          ],
          ttl: 42,
          type: 'NAPTR',
        },
      ],
      naptrExample1: [
        {
          name: 'rfc3403-1.igloo.com.',
          endpoints: [
            '100 10 "u" "sip+E2U" "!^.*$!sip:information@foo.se!i" .',
          ],
          ttl: 42,
          type: 'NAPTR',
        },
        {
          name: 'rfc3403-2.igloo.com.',
          endpoints: ['100 50 "a" "z3950+N2L+N2C" "" cidserver.igloo.com.'],
          ttl: 42,
          type: 'NAPTR',
        },
      ],
      naptrExampleInlineTTLAndNested: [
        {
          name: 'rfc3403-1.igloo.com.',
          endpoints: [
            '100 10 "u" "sip+E2U" "!^.*$!sip:information@foo.se!i" .',
          ],
          ttl: 42,
          type: 'NAPTR',
        },
        {
          name: 'rfc3403-2.igloo.com.',
          endpoints: ['100 50 "a" "z3950+N2L+N2C" "" cidserver.igloo.com.'],
          ttl: 42,
          type: 'NAPTR',
        },
        {
          name: 'igloo.com.',
          endpoints: [
            '99 98 "B" "E2U+siz" "" .',
            '100 10 "U" "E2U+sip" "!^.*$!sip:customer-service@example.com!" .',
            '102 10 "U" "E2U+email" "!^.*$!mailto:information@example.com!" .',
          ],
          ttl: 42,
          type: 'NAPTR',
        },
      ],
    },
    TXT: {
      txtExampleTopLevelTTL: [
        {
          name: 'treefrog.igloo.com.',
          endpoints: [
            '"v=spf1 a mx a:mail.treefrog.ca a:webmail.treefrog.ca ip4:76.75.250.33 ?all"',
          ],
          ttl: 42,
          type: 'TXT',
        },
        {
          name: 'treemonkey.igloo.com.',
          endpoints: ['"v=DKIM1; k=rsa; p=MIGf..."'],
          ttl: 42,
          type: 'TXT',
        },
        {
          name: 'treemonkey1.igloo.com.',
          endpoints: ['"v=DKIM1; k=rsa; p=MIGf..."'],
          ttl: 42,
          type: 'TXT',
        },
      ],
      txtExampleInlineTTL: [
        {
          name: 'treefrog.igloo.com.',
          endpoints: [
            '"v=spf1 a mx a:mail.treefrog.ca a:webmail.treefrog.ca ip4:76.75.250.33 ?all"',
          ],
          ttl: 42,
          type: 'TXT',
        },
        {
          name: 'treemonkey.igloo.com.',
          endpoints: ['"v=DKIM1; k=rsa; p=MIGf..."'],
          ttl: 42,
          type: 'TXT',
        },
        {
          name: 'treemonkey1.igloo.com.',
          endpoints: ['"v=DKIM1; k=rsa; p=MIGf..."'],
          ttl: 42,
          type: 'TXT',
        },
      ],
      /** @idea would we want to join in this case? */
      txtExampleNested: [
        {
          name: 'treefrog.igloo.com.',
          endpoints: ['"eh=true"', '"moose=DKIM1; beaver=canada;"'],
          ttl: 42,
          type: 'TXT',
        },
      ],
      parensInTXT: [
        {
          name: 'eh.igloo.com.',
          endpoints: ['"Some text (Moose)"', '"spaced ( spaces1 spaces2    )"'],
          ttl: 42,
          type: 'TXT',
        },
      ],
    },
    NS: {
      nsExampleWithAtAndTopLevelTTL: [],
      nsExampleWithNoNameAndTopLevelTTL: [],
      nsExampleWithNamesAndTopLevelTTL: [
        {
          name: 'syrup.igloo.com.',
          endpoints: ['NS1.NAMESERVER.NET.'],
          ttl: 42,
          type: 'NS',
        },
        {
          name: 'beaver.igloo.com.',
          endpoints: ['NS2.NAMESERVER.NET.'],
          type: 'NS',
          ttl: 42,
        },
      ],
      nsExampleInlineTTL: [
        {
          name: 'eh.igloo.com.',
          endpoints: ['NS1.NAMESERVER.NET.'],
          ttl: 42,
          type: 'NS',
        },
        {
          name: 'canada.igloo.com.',
          endpoints: ['NS2.NAMESERVER.NET.'],
          ttl: 42,
          type: 'NS',
        },
      ],
      nsExampleNestedWithTopLevelTTL: [
        {
          name: 'moose.igloo.com.',
          endpoints: ['NS1.NAMESERVER.NET.', 'NS2.NAMESERVER.NET.'],
          ttl: 42,
          type: 'NS',
        },
      ],
    },
    PTR: {
      ptrExampleOneEntryTopLevelTTL: [
        {
          endpoints: ['HOST1.igloo.com.'],
          name: '99.11.igloo.com.',
          type: 'PTR',
          ttl: 42,
        },
      ],
      ptrExampleInlineTTL: [
        {
          name: '1.0.168.192.igloo.com.',
          endpoints: ['HOST1.igloo.com.'],
          type: 'PTR',
          ttl: 42,
        },
        {
          name: '2.0.168.192.igloo.com.',
          endpoints: ['HOST2.igloo.com.'],
          type: 'PTR',
          ttl: 42,
        },
      ],
      ptrExampleTopLevelTTL: [
        {
          name: '1.0.168.192.igloo.com.',
          endpoints: ['HOST1.igloo.com.'],
          type: 'PTR',
          ttl: 42,
        },
        {
          name: '2.0.168.192.igloo.com.',
          endpoints: ['HOST2.igloo.com.'],
          type: 'PTR',
          ttl: 42,
        },
      ],
      ptrExampleNestedTopLevelTTL: [
        {
          name: '3.30.168.192.igloo.com',
          endpoints: ['HOST3.igloo.com.'],
          type: 'PTR',
          ttl: 42,
        },
        {
          name: '4.30.168.192.igloo.com',
          endpoints: [
            'HOST4.igloo.com.',
            'HOST5.igloo.com.',
            'HOST6.igloo.com.',
          ],
          type: 'PTR',
          ttl: 42,
        },
      ],
      ptrExampleDecimals: [
        {
          endpoints: ['HOST3.igloo.com.'],
          name: '10.3.168.192.igloo.com',
          ttl: 42,
          type: 'PTR',
        },
        {
          endpoints: ['HOST4.igloo.com.'],
          name: '10.4.168.192.igloo.com',
          ttl: 42,
          type: 'PTR',
        },
      ],
      ptrMultipleOrigins: [
        {
          endpoints: ['HOST3.igloo.com.'],
          name: '3.30.168.192.igloo.com',
          ttl: 41,
          type: 'PTR',
        },
        {
          endpoints: [
            'HOST4.igloo.com.',
            'HOST5.igloo.com.',
            'HOST6.igloo.com.',
          ],
          name: '4.30.168.192.igloo.com',
          ttl: 42,
          type: 'PTR',
        },
        {
          endpoints: ['HOST3.igloo.com.'],
          name: '10.3.168.192.igloo.com',
          ttl: 45,
          type: 'PTR',
        },
        {
          endpoints: ['HOST4.igloo.com.'],
          name: '10.4.168.192.igloo.com',
          ttl: 46,
          type: 'PTR',
        },
      ],
    },
    SPF: {
      spfMaxValues: [
        {
          name: 'eh.igloo.com.',
          endpoints: ['v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a -all'],
          ttl: 42,
          type: 'SPF',
        },
      ],
      spfMinValues: [
        {
          name: 'eh.igloo.com.',
          endpoints: ['v=spf1 ip:4:192.168.0.1/16-all'],
          ttl: 42,
          type: 'SPF',
        },
      ],
      spfExampleOneEntryInlineTTL: [
        {
          name: 'test.igloo.com.',
          endpoints: ['"v=spf1" "mx:gcloud-node.com." "-all"'],
          ttl: 42,
          type: 'SPF',
        },
      ],
      spfExampleNestedTopLevelTTL: [
        {
          name: 'test.igloo.com.',
          endpoints: ['"v=spf1" "mx:gcloud-node.com." "-all"'],
          ttl: 42,
          type: 'SPF',
        },
        {
          name: 'test1.igloo.com.',
          endpoints: [
            '"v=spf2" "mx:gcloud-node.com." "-all"',
            '"v=spf3" "mx:gcloud-node.com." "-all    "',
            '"v=spf4" "mx:gcloud-node.com." "-all"',
          ],
          ttl: 42,
          type: 'SPF',
        },
      ],
      spfExampleNestedInlineTTL: [
        {
          name: 'test.igloo.com.',
          endpoints: ['"v=spf1" "mx:gcloud-node.com." "-all"'],
          ttl: 42,
          type: 'SPF',
        },
        {
          name: 'test1.igloo.com.',
          endpoints: [
            '"v=spf2" "mx:gcloud-node.com." "-all"',
            '"v=spf3" "mx:gcloud-node.com." "-all    "',
            '"v=spf4" "mx:gcloud-node.com." "-all"',
          ],
          ttl: 42,
          type: 'SPF',
        },
      ],
      spfExampleAtNameInlineTTL: [
        {
          name: 'igloo.com.',
          endpoints: ['"v=spf1 a mx ~all"'],
          ttl: 42,
          type: 'TXT',
        },
      ],
      spfExampleOfOneParam: [
        {
          name: 'eh.igloo.com.',
          endpoints: ['"v=spf1  ip:4:192.168.0.1/16"'],
          ttl: 42,
          type: 'SPF',
        },
      ],
    },
    SRV: {
      srvExampleWithNameAndTopLevelTTL: [
        {
          name: 'server.igloo.com.',
          endpoints: ['172.30.79.10'],
          ttl: 42,
          type: 'A',
        },
        {
          name: 'old-slow-box.igloo.com.',
          endpoints: ['172.30.79.11'],
          ttl: 42,
          type: 'A',
        },
        {
          name: 'sysadmins-box.igloo.com.',
          endpoints: ['172.30.79.12'],
          ttl: 42,
          type: 'A',
        },
        {
          name: 'new-fast-box.igloo.com.',
          endpoints: ['172.30.79.13'],
          ttl: 42,
          type: 'A',
        },
        {
          name: '_foobar._tcp.igloo.com.',
          endpoints: [
            '0 1 9 old-slow-box.igloo.com.',
            '0 3 9 new-fast-box.igloo.com.',
            '1 0 9 sysadmins-box.igloo.com.',
            '1 0 9 server.igloo.com.',
          ],
          ttl: 42,
          type: 'SRV',
        },
        {
          name: '*._tcp.igloo.com.',
          endpoints: ['0 0 0 .'],
          ttl: 42,
          type: 'SRV',
        },
        {
          name: '*._udp.igloo.com.',
          endpoints: ['0 0 0 .'],
          ttl: 42,
          type: 'SRV',
        },
      ],
      srvExampleWithNameAndInlineTTL: [
        {
          name: '_foobar._tcp.igloo.com.',
          endpoints: [
            '0 1 9 old-slow-box.igloo.com.',
            '0 3 9 new-fast-box.igloo.com.',
            '1 0 9 sysadmins-box.igloo.com.',
            '1 0 9 server.igloo.com.',
          ],
          ttl: 42,
          type: 'SRV',
        },
        {
          name: '*._tcp.igloo.com.',
          endpoints: ['0 0 0 .'],
          ttl: 42,
          type: 'SRV',
        },
        {
          name: '*._udp.igloo.com.',
          endpoints: ['0 0 0 .'],
          ttl: 42,
          type: 'SRV',
        },
      ],
    },
    SOA: {
      soaIsIgnored1: [],
      soaIsIgnored2: [
        {
          name: 'example.igloo.com.',
          endpoints: ['127.0.0.1'],
          /** @default 300 */
          ttl: 300,
          type: 'A',
        },
      ],
      soaIsIgnored3: [
        {
          name: 'mail1.igloo.com.',
          endpoints: ['mail'],
          /** @default 300 */
          ttl: 300,
          type: 'CNAME',
        },
      ],
      simpleSoa: [],
    },
  };

  describe.each([
    'GENERAL',
    ...SUPPORTED_RECORD_TYPES,
    'SOA',
  ] as MockSupportedRecordType[])(
    'Parsing type "%s" (valid)',
    resourceRecordType => {
      beforeEach(() => {
        spyOnCreateApi().mockImplementation(() => Promise.resolve(true));
      });

      const testCasesIndexed = validTestCases[resourceRecordType];

      it.each(Object.keys(testCasesIndexed) as IValidMockKeyType[])(
        'Should match expected - %s',
        key => {
          const input = validMock[key];
          const expected = testCasesIndexed[key];
          const { records } = testImportZoneFile(input);

          expect(records).toEqual(expected);
        }
      );
    }
  );

  describe('Warnings when there are ignored records in zone file', () => {
    it('Should return no warnings when there are not SOA and/or NS without name', () => {
      const input = validMock['nsExampleWithNamesAndTopLevelTTL'];
      const { warnings } = testImportZoneFile(input);

      expect(warnings).toEqual([]);
    });

    it('Should return warning when SOA is ignored', () => {
      const input = validMock['soaIsIgnored2'];
      const { warnings } = testImportZoneFile(input);

      expect(warnings).toEqual([WarningKeyKind.SoaIgnored]);
    });

    it('Should return warning when NS is ignored', () => {
      const input = validMock['nsExampleWithAtAndTopLevelTTL'];
      const { warnings } = testImportZoneFile(input);

      expect(warnings).toEqual([
        WarningKeyKind.NsIgnored,
        WarningKeyKind.NsIgnored,
      ]);
    });

    it('Should return warnings when SOA and NS are ignored', () => {
      const input = validMock['soaIsIgnored1'];

      const { warnings } = testImportZoneFile(input);

      expect(warnings).toEqual([
        WarningKeyKind.SoaIgnored,
        WarningKeyKind.NsIgnored,
      ]);
    });
  });

  describe('Edge cases', () => {
    const edgeTestCases: MockWithoutTypeForKey<keyof typeof edgeMock> = {
      atSymbol: [
        {
          name: 'www.igloo.com.',
          ttl: 300,
          type: 'CNAME',
          endpoints: ['igloo.com.'],
        },
      ],
      atSymbolEscaped: [
        {
          name: 'www.igloo.com.',
          ttl: 300,
          type: 'CNAME',
          endpoints: ['@'],
        },
      ],
    };

    it.each(Object.keys(edgeTestCases) as Array<keyof typeof edgeTestCases>)(
      'Should handle "%s"',
      key => {
        const input = edgeMock[key];
        const expected = edgeTestCases[key];
        const edgeResults = testImportZoneFile(input);
        expect(edgeResults.records).toEqual(expected);
      }
    );
  });

  describe('Complex (valid)', () => {
    const complexTestCases: MockWithoutTypeForKey<IComplexMockKeyType> = {
      /** @note the ttls here are defaulted to 300 */
      complicated1: [
        {
          name: 'server.igloo.com.',
          endpoints: ['172.30.79.10'],
          ttl: 300,
          type: 'A',
        },
        {
          name: 'old-slow-box.igloo.com.',
          endpoints: ['172.30.79.11'],
          ttl: 300,
          type: 'A',
        },
        {
          name: 'sysadmins-box.igloo.com.',
          endpoints: ['172.30.79.12'],
          ttl: 300,
          type: 'A',
        },
        {
          name: 'new-fast-box.igloo.com.',
          endpoints: ['172.30.79.13'],
          ttl: 300,
          type: 'A',
        },
        {
          name: '_foobar._tcp.igloo.com.',
          endpoints: [
            '0 1 9 old-slow-box.igloo.com.',
            '0 3 9 new-fast-box.igloo.com.',
            '1 0 9 sysadmins-box.igloo.com.',
            '1 0 9 server.igloo.com.',
          ],
          ttl: 300,
          type: 'SRV',
        },
        {
          name: '*._tcp.igloo.com.',
          endpoints: ['0 0 0 .'],
          ttl: 300,
          type: 'SRV',
        },
        {
          name: '*._udp.igloo.com.',
          endpoints: ['0 0 0 .'],
          ttl: 300,
          type: 'SRV',
        },
      ],
      complicated2: [
        {
          name: 'neither.igloo.com.',
          endpoints: ['1.2.3.4'],
          type: 'A',
          ttl: 604800,
        },
        {
          name: 'ns.igloo.com.',
          endpoints: ['2.3.4.5'],
          type: 'A',
          ttl: 100,
        },
        {
          name: 'mail.igloo.com.',
          endpoints: ['10.0.0.2'],
          type: 'A',
          ttl: 86400,
        },
        {
          name: 'mail2.igloo.com.',
          endpoints: ['10.0.0.3'],
          type: 'A',
          ttl: 86400,
        },
        {
          name: 'www.igloo.com.',
          endpoints: ['10.0.0.1'],
          type: 'A',
          ttl: 86400,
        },
        {
          name: '\\097.igloo.com.',
          endpoints: ['1.2.3.4'],
          type: 'A',
          ttl: 60,
        },
        {
          name: '\\304.igloo.com.',
          endpoints: ['1.2.3.4'],
          type: 'A',
          ttl: 60,
        },
        {
          name: '©.igloo.com.',
          endpoints: ['1.2.3.4'],
          type: 'A',
          ttl: 60,
        },
        {
          name: '¿.igloo.com.',
          endpoints: ['1.2.3.4'],
          type: 'A',
          ttl: 60,
        },
        {
          name: 'www.igloo.com.',
          endpoints: ['\\008\\99.igloo.com.'],
          type: 'CNAME',
          ttl: 60,
        },
        {
          endpoints: ['igloo.com.'],
          name: 'root.igloo.com.',
          ttl: 86400,
          type: 'CNAME',
        },
        {
          name: 'web.igloo.com.',
          endpoints: ['www'],
          type: 'CNAME',
          ttl: 86400,
        },
        {
          name: 'www.igloo.com.',
          endpoints: ['"\\101\\"'],
          type: 'TXT',
          ttl: 60,
        },
        {
          name: 'test.igloo.com.',
          endpoints: ['"v=spf1" "mx:gcloud-node.com." "-all"'],
          type: 'SPF',
          ttl: 604800,
        },
        {
          name: 'test1.igloo.com.',
          endpoints: [
            '"v=spf2" "mx:gcloud-node.com." "-all"',
            '"v=spf3" "mx:gcloud-node.com." "-all    "',
            '"v=spf4" "mx:gcloud-node.com." "-all"',
          ],
          type: 'SPF',
          ttl: 604800,
        },
      ],
      complicated3: [
        {
          name: 'igloo.com.',
          endpoints: ['192.0.2.1'],
          type: 'A',
          ttl: 3600,
        },
        {
          name: 'ns.igloo.com.',
          endpoints: ['192.0.2.2'],
          type: 'A',
          ttl: 3600,
        },
        {
          name: 'mail.igloo.com.',
          endpoints: ['192.0.2.3'],
          type: 'A',
          ttl: 3600,
        },
        {
          name: 'mail2.igloo.com.',
          endpoints: ['192.0.2.4'],
          type: 'A',
          ttl: 3600,
        },
        {
          name: 'mail3.igloo.com.',
          endpoints: ['192.0.2.5'],
          type: 'A',
          ttl: 3600,
        },
        {
          name: 'igloo.com.',
          endpoints: ['2001:db8:10::1'],
          type: 'AAAA',
          ttl: 3600,
        },
        {
          name: 'ns.igloo.com.',
          endpoints: ['2001:db8:10::2'],
          type: 'AAAA',
          ttl: 3600,
        },
        {
          name: 'www.igloo.com.',
          endpoints: ['igloo.com.'],
          type: 'CNAME',
          ttl: 3600,
        },
        {
          name: 'wwwtest.igloo.com.',
          endpoints: ['www'],
          type: 'CNAME',
          ttl: 3600,
        },
        {
          name: 'igloo.com.',
          endpoints: ['10 mail.igloo.com.', '20 mail2.igloo.com.', '50 mail3'],
          type: 'MX',
          ttl: 3600,
        },
      ],
      complicated4: [
        {
          name: 'mail.igloo.com.',
          endpoints: ['127.0.0.1', '1.2.3.4'],
          type: 'A',
          ttl: 1814400,
        },
        {
          name: 'mail.igloo.com.',
          endpoints: ['2001:db8::1'],
          type: 'AAAA',
          ttl: 1814400,
        },
        {
          name: 'mail1.igloo.com.',
          endpoints: ['mail'],
          type: 'CNAME',
          ttl: 1814400,
        },
        {
          name: 'mail2.igloo.com.',
          endpoints: ['mail'],
          type: 'CNAME',
          ttl: 1814400,
        },
        {
          name: 'igloo.com.',
          endpoints: ['0 mail1', '10 mail2'],
          type: 'MX',
          ttl: 1814400,
        },
      ],
    };

    /** @note !!! the order is changed (from the consumer) */
    it('Should handle - invalid nesting', () => {
      const { records } = testImportZoneFile(
        invalidClientMock.parsingErrorInvalidNesting
      );

      const expected = [
        {
          name: 'www.igloo.com.',
          endpoints: ['5.6.7.8'],
          type: 'A',
          ttl: 42,
        },
        {
          name: 'notme.igloo.com.',
          endpoints: ['1.2.3.4'],
          type: 'NS',
          ttl: 42,
        },
        {
          name: 'www.igloo.com.',
          endpoints: ['9.10.11.12'],
          type: 'NS',
          ttl: 42,
        },
      ];

      expect(records).toEqual(expect.arrayContaining(expected));
    });

    it.each(Object.keys(complexTestCases) as IComplexMockKeyType[])(
      '%s',
      key => {
        const expected = complexTestCases[key];
        const input = complexMock[key];
        const { records } = testImportZoneFile(input);

        // expect(records).toEqual(expected);
        expect(records).toEqual(expect.arrayContaining(expected!));
      }
    );
  });

  describe('Client side (invalid)', () => {
    it('Should handle - unsupported type (+ handle line number)', () => {
      const { error } = testImportZoneFile(invalidClientMock.unsupportedType);

      expect(error!.message).toEqual(
        getErrorText({
          errorType: ErrorKeyKind.BadDirective,
        } as any)
      );
      expect(error!.parsingLineContent).toEqual(
        'www 42s IN DNAME www.otherzone.com.'
      );
      expect(error!.parsingLineNumber).toEqual(2);
    });

    it.each`
      errorType                           | mock                                     | case
      ${ErrorKeyKind.EmptyInput}          | ${invalidClientMock.empty}               | ${'empty'}
      ${ErrorKeyKind.BadDirective}        | ${invalidClientMock.badDirectiveInclude} | ${'bad directive (import)'}
      ${ErrorKeyKind.BadDirective}        | ${invalidClientMock.badDirectiveUnknown} | ${'bad directive (unknown)'}
      ${ErrorKeyKind.BadDirective}        | ${invalidClientMock.incompleteRecords}   | ${'incomplete record'}
      ${ErrorKeyKind.NoRecords}           | ${invalidClientMock.onlyComment}         | ${'no records'}
      ${ErrorKeyKind.WrongNameArgsLength} | ${invalidClientMock.tooManyNameArgs}     | ${'too many args for name/ttl'}
      ${ErrorKeyKind.Duplicated}          | ${invalidClientMock.duplicateRecord}     | ${'duplicated'}
      ${ErrorKeyKind.EmptyInput}          | ${invalidClientMock.empty}               | ${'empty'}
      ${ErrorKeyKind.TtlInvalidFormat}    | ${invalidClientMock.badTtl}              | ${'invalid ttl'}
    `(
      'Should handle "$case"',
      ({ errorType, mock }: { errorType: ErrorKeyKind; mock: string }) => {
        const zone = testImportZoneFile(mock);
        const { error } = zone;
        expect(error!.message).toEqual(getErrorText({ errorType } as any));
      }
    );
  });

  describe('With wrong arg lengths (invalid)', () => {
    const argLengths = {
      txtWithNotEnoughArgs: {
        expectedArgCount: RecordMinLengthKind.TXT,
        actualArgCount: 0,
      },
      txtWithNotEnoughArgsOrAnything: {
        expectedArgCount: RecordMinLengthKind.TXT,
        actualArgCount: 0,
      },
      txtWithTooManyEntries: {
        expectedArgCount: RecordMinLengthKind.TXT,
        actualArgCount: RecordMinLengthKind.TXT + 1,
      },
      txtWithTooManyEntriesNoTtl: {
        expectedArgCount: RecordMinLengthKind.TXT,
        actualArgCount: RecordMinLengthKind.TXT + 1,
      },
      txtWithTooManyEntriesNoNameOrTtl: {
        expectedArgCount: RecordMinLengthKind.TXT,
        actualArgCount: RecordMinLengthKind.TXT + 1,
      },
      ptrWithTooManyEntries: {
        expectedArgCount: RecordMinLengthKind.PTR,
        actualArgCount: RecordMinLengthKind.PTR + 1,
      },
      ptrWithNotEnoughArgs1: {
        expectedArgCount: RecordMinLengthKind.PTR,
        actualArgCount: RecordMinLengthKind.PTR - 1,
      },
      aaaaWithTooManyEntries: {
        expectedArgCount: RecordMinLengthKind.AAAA,
        actualArgCount: RecordMinLengthKind.AAAA + 1,
      },
      aaaaWithNotEnoughArgs1: {
        expectedArgCount: RecordMinLengthKind.AAAA,
        actualArgCount: RecordMinLengthKind.AAAA - 1,
      },
      aWithTooManyEntries: {
        expectedArgCount: RecordMinLengthKind.A,
        actualArgCount: RecordMinLengthKind.A + 1,
      },
      aWithNotEnoughArgs1: {
        expectedArgCount: RecordMinLengthKind.A,
        actualArgCount: RecordMinLengthKind.A - 1,
      },
      spfWithNotEnoughArgs2: {
        expectedArgCount: RecordMinLengthKind.SPF,
        actualArgCount: RecordMinLengthKind.SPF - 1,
      },
      spfWithNotEnoughArgs3: {
        expectedArgCount: RecordMinLengthKind.SPF,
        actualArgCount: 0,
      },
      caaWithTooManyArgs: {
        expectedArgCount: RecordMinLengthKind.CAA,
        actualArgCount: RecordMinLengthKind.CAA + 1,
      },
      caaWithNotEnoughArgs1: {
        expectedArgCount: RecordMinLengthKind.CAA,
        actualArgCount: RecordMinLengthKind.CAA - 1,
      },
      caaWithNotEnoughArgs2: {
        expectedArgCount: RecordMinLengthKind.CAA,
        actualArgCount: RecordMinLengthKind.CAA - 2,
      },
      caaWithNotEnoughArgs3: {
        expectedArgCount: RecordMinLengthKind.CAA,
        actualArgCount: 0,
      },
      naptrWithTooManyArgs: {
        expectedArgCount: RecordMinLengthKind.NAPTR,
        actualArgCount: RecordMinLengthKind.NAPTR + 1,
      },
      naptrWithNotEnoughArgs1: {
        expectedArgCount: RecordMinLengthKind.NAPTR,
        actualArgCount: RecordMinLengthKind.NAPTR - 1,
      },
      naptrWithNotEnoughArgs2: {
        expectedArgCount: RecordMinLengthKind.NAPTR,
        actualArgCount: RecordMinLengthKind.NAPTR - 2,
      },
      naptrWithNotEnoughArgs3: {
        expectedArgCount: RecordMinLengthKind.NAPTR,
        actualArgCount: RecordMinLengthKind.NAPTR - 3,
      },
      naptrWithNotEnoughArgs4: {
        expectedArgCount: RecordMinLengthKind.NAPTR,
        actualArgCount: RecordMinLengthKind.NAPTR - 4,
      },
      naptrWithNotEnoughArgs5: {
        expectedArgCount: RecordMinLengthKind.NAPTR,
        actualArgCount: RecordMinLengthKind.NAPTR - 5,
      },
      naptrWithNotEnoughArgs6: {
        expectedArgCount: RecordMinLengthKind.NAPTR,
        actualArgCount: 0,
      },
      naptrWithNotEnoughArgs7: {
        expectedArgCount: RecordMinLengthKind.NAPTR,
        actualArgCount: 0,
      },
      srvWithTooManyArgs: {
        expectedArgCount: RecordMinLengthKind.SRV,
        actualArgCount: RecordMinLengthKind.SRV + 1,
      },
      srvWithNotEnoughArgs1: {
        expectedArgCount: RecordMinLengthKind.SRV,
        actualArgCount: RecordMinLengthKind.SRV - 1,
      },
      srvWithNotEnoughArgs2: {
        expectedArgCount: RecordMinLengthKind.SRV,
        actualArgCount: RecordMinLengthKind.SRV - 2,
      },
      srvWithNotEnoughArgs3: {
        expectedArgCount: RecordMinLengthKind.SRV,
        actualArgCount: RecordMinLengthKind.SRV - 3,
      },
      srvWithNotEnoughArgs4: {
        expectedArgCount: RecordMinLengthKind.SRV,
        actualArgCount: RecordMinLengthKind.SRV - 4,
      },
      srvWithNotEnoughArgs5: {
        expectedArgCount: RecordMinLengthKind.SRV,
        actualArgCount: 0,
      },
      nsWithTooManyArgs: {
        expectedArgCount: RecordMinLengthKind.NS,
        actualArgCount: RecordMinLengthKind.NS + 1,
      },
      nsWithNotEnoughArgs1: {
        expectedArgCount: RecordMinLengthKind.NS,
        actualArgCount: 0,
      },
      nsWithNotEnoughArgs2: {
        expectedArgCount: RecordMinLengthKind.NS,
        actualArgCount: 0,
      },
      nsWithNotEnoughArgs3: {
        expectedArgCount: RecordMinLengthKind.NS,
        actualArgCount: 0,
      },
      nsWithNotEnoughArgs4: {
        expectedArgCount: RecordMinLengthKind.NS,
        actualArgCount: 0,
      },
      mxWithTooManyArgs: {
        expectedArgCount: RecordMinLengthKind.MX,
        actualArgCount: RecordMinLengthKind.MX + 1,
      },
      mxWithNotEnoughArgs1: {
        expectedArgCount: RecordMinLengthKind.MX,
        actualArgCount: RecordMinLengthKind.MX - 1,
      },
      mxWithNotEnoughArgs2: {
        expectedArgCount: RecordMinLengthKind.MX,
        actualArgCount: 0,
      },
      cnameWithTooManyArgs: {
        expectedArgCount: RecordMinLengthKind.CNAME,
        actualArgCount: RecordMinLengthKind.CNAME + 1,
      },
      cnameWithNotEnoughArgs1: {
        expectedArgCount: RecordMinLengthKind.CNAME,
        actualArgCount: 0,
      },
      cnameWithNotEnoughArgs2: {
        expectedArgCount: RecordMinLengthKind.CNAME,
        actualArgCount: 0,
      },
    };

    it.each(Object.keys(argLengths) as Array<keyof typeof argLengths>)(
      'Should throw - `%s`',
      key => {
        const input = invalidClientMock[key];
        const error: ParserInvalidArgumentsError &
          ParsingError = testImportZoneFile(input).error as any;
        const { expectedArgCount, actualArgCount } = argLengths[key];

        expect(error).toMatchSnapshot();

        /**
         * @note this is not here unless i18n was added
         * @example /Too many args\! Expected (\d) for type [a-z]\. Received (\d)/gmi)
         *
         * @example
         *    const matches = match(/(\d+)/gim, error!.message).map(Number);
         *    expect(matches).toHaveLength(2)
         *    const [matchedExpected, matchedActual] = matches;
         */
        const { expected: matchedExpected, actual: matchedActual } = JSON.parse(
          error?.message
        );
        expect(matchedExpected).toEqual(expectedArgCount);
        expect(matchedActual).toEqual(actualArgCount);
      }
    );
  });

  describe('API side (invalid)', () => {
    let spy: jest.SpyInstance;

    beforeEach(() => {
      spy = spyOnCreateApi().mockImplementation(() =>
        Promise.reject(new Error('Invalid!'))
      );
    });

    // these will have other types as well
    const invalidApiTestCases: {
      [K in IInvalidApiMockKeyType]: { message: string };
    } = {
      duplicateDistanceRecord: {
        message: 'Invalid!',
      },
      labelTooLong: {
        message: 'Invalid!',
      },
      nameTooLong: {
        message: 'Invalid!',
      },
      negativeTtl: {
        message: 'Invalid!',
      },
      escapedCharacters: {
        message: 'Invalid!',
      },
      cnameLoop1: {
        message: 'Invalid!',
      },
      cnameLoop2: {
        message: 'Invalid!',
      },
      cnameLoop3: {
        message: 'Invalid!',
      },
      cnameForExistingRecord: {
        message: 'Invalid!',
      },
      mxExample1: {
        message: 'Invalid!',
      },
      invalidIpForAAAA: {
        message: 'Invalid!',
      },
      invalidIpv6ForA: {
        message: 'Invalid!',
      },
      invalidIpv4ForAAAA: {
        message: 'Invalid!',
      },
      invalidIpForA: {
        message: 'Invalid!',
      },
      txtExampleInvalidSubdomain: {
        message: 'Invalid!',
      },
      caaExampleBadParams: {
        message: 'Invalid!',
      },
      caaExampleNotEmailForIoDef: {
        message: 'Invalid!',
      },
      naptrExampleInvalid: {
        message: 'Invalid!',
      },
    };

    it.each(
      Object.keys(invalidApiTestCases) as Array<
        keyof typeof invalidApiTestCases
      >
    )('Should throw - `%s`', async key => {
      const input = invalidApiMock[key];
      const expected = invalidApiTestCases[key];

      expect.assertions(2);

      try {
        await testImportZoneFileAndCallApi(input);
      } catch (error) {
        expect(error!.message).toEqual(expected.message);
      }

      expect(spy).toHaveBeenCalled();
    });
  });
});
