import { getDigest, getPkgReleases } from '..';
import * as conan from '../../versioning/conan';
import type { GetDigestInputConfig, GetPkgReleasesConfig } from '../types';
import { defaultRegistryUrl } from './common';
import { ConanDatasource } from '.';
import { Fixtures } from '~test/fixtures';
import * as httpMock from '~test/http-mock';

const mixedCaseJson = Fixtures.get('mixed_case.json');
const pocoJson = Fixtures.get('poco.json');
const pocoRevisions = Fixtures.getJson('poco_revisions.json');
const pocoYamlGitHubContent = Fixtures.get('poco.yaml');
const malformedJson = Fixtures.get('malformed.json');
const fakeJson = Fixtures.get('fake.json');
const datasource = ConanDatasource.id;

const nonDefaultRegistryUrl = 'https://not.conan.io/';

const config: GetPkgReleasesConfig = {
  packageName: '',
  datasource,
  versioning: conan.id,
  registryUrls: [nonDefaultRegistryUrl],
};

const digestConfig: GetDigestInputConfig = {
  packageName: 'fake',
  datasource,
  registryUrls: [nonDefaultRegistryUrl],
};

describe('modules/datasource/conan/index', () => {
  beforeEach(() => {
    config.registryUrls = [nonDefaultRegistryUrl];
  });

  describe('getDigest', () => {
    it('handles package without digest', async () => {
      digestConfig.packageName = 'fakepackage/1.2@_/_';
      expect(await getDigest(digestConfig)).toBeNull();
    });

    it('handles digest', async () => {
      const version = '1.8.1';
      httpMock
        .scope(nonDefaultRegistryUrl)
        .get(`/v2/conans/poco/${version}/_/_/revisions`)
        .reply(200, pocoRevisions[version]);
      digestConfig.packageName = `poco/${version}@_/_`;
      digestConfig.currentDigest = '4fc13d60fd91ba44fefe808ad719a5af';
      expect(await getDigest(digestConfig, version)).toBe(
        '3a9b47caee2e2c1d3fb7d97788339aa8',
      );
    });

    it('returns null for missing revision', async () => {
      const version = '1.8.1';
      httpMock
        .scope(nonDefaultRegistryUrl)
        .get(`/v2/conans/poco/${version}/_/_/revisions`)
        .reply(200, []);
      digestConfig.packageName = `poco/${version}@_/_`;
      digestConfig.currentDigest = '4fc13d60fd91ba44fefe808ad719a5af';
      expect(await getDigest(digestConfig, version)).toBeNull();
    });
  });

  describe('getReleases', () => {
    it('handles bad return', async () => {
      httpMock
        .scope(nonDefaultRegistryUrl)
        .get('/v2/conans/search?q=fakepackage')
        .reply(200);
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'fakepackage/1.2@_/_',
        }),
      ).toBeNull();
    });

    it('handles empty return', async () => {
      httpMock
        .scope(nonDefaultRegistryUrl)
        .get('/v2/conans/search?q=fakepackage')
        .reply(200, {});
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'fakepackage/1.2@_/_',
        }),
      ).toBeNull();
    });

    it('handles bad registries', async () => {
      httpMock
        .scope('https://fake.bintray.com/')
        .get('/v2/conans/search?q=poco')
        .reply(404);
      config.registryUrls = ['https://fake.bintray.com/'];
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'poco/1.2@_/_',
        }),
      ).toBeNull();
    });

    it('handles missing packages', async () => {
      httpMock
        .scope(nonDefaultRegistryUrl)
        .get('/v2/conans/search?q=fakepackage')
        .reply(200, fakeJson);
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'fakepackage/1.2@_/_',
        }),
      ).toBeNull();
    });

    it('processes real versioned data', async () => {
      httpMock
        .scope(nonDefaultRegistryUrl)
        .get('/v2/conans/search?q=poco')
        .reply(200, pocoJson);
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'poco/1.2@_/_',
        }),
      ).toEqual({
        registryUrl: 'https://not.conan.io',
        releases: [
          {
            version: '1.8.1',
          },
          {
            version: '1.9.3',
          },
          {
            version: '1.9.4',
          },
          {
            version: '1.10.0',
          },
          {
            version: '1.10.1',
          },
        ],
      });
    });

    it('processes mixed case names', async () => {
      httpMock
        .scope(nonDefaultRegistryUrl)
        .get('/v2/conans/search?q=FooBar')
        .reply(200, mixedCaseJson);
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'FooBar/1.0.0@_/_',
        }),
      ).toEqual({
        registryUrl: 'https://not.conan.io',
        releases: [
          {
            version: '1.0.0',
          },
          {
            version: '1.1.0',
          },
          {
            version: '2.2.0',
          },
        ],
      });
    });

    it('uses github instead of conan center', async () => {
      httpMock
        .scope('https://api.github.com')
        .get(
          '/repos/conan-io/conan-center-index/contents/recipes/poco/config.yml',
        )
        .reply(200, pocoYamlGitHubContent);
      expect(
        await getPkgReleases({
          ...config,
          registryUrls: [defaultRegistryUrl],
          packageName: 'poco/1.2@_/_',
        }),
      ).toEqual({
        registryUrl: 'https://center2.conan.io',
        releases: [
          {
            version: '1.8.1',
          },
          {
            version: '1.9.3',
          },
          {
            version: '1.9.4',
          },
          {
            version: '1.10.0',
          },
          {
            version: '1.10.1',
          },
          {
            version: '1.11.0',
          },
          {
            version: '1.11.1',
          },
        ],
      });
    });

    it('works with empty releases', async () => {
      httpMock
        .scope('https://api.github.com')
        .get(
          '/repos/conan-io/conan-center-index/contents/recipes/poco/config.yml',
        )
        .reply(200, '');
      expect(
        await getPkgReleases({
          ...config,
          registryUrls: [defaultRegistryUrl],
          packageName: 'poco/1.2@_/_',
        }),
      ).toBeNull();
    });

    it('rejects userAndChannel for Conan Center', async () => {
      expect(
        await getPkgReleases({
          ...config,
          registryUrls: [defaultRegistryUrl],
          packageName: 'poco/1.2@foo/bar',
        }),
      ).toBeNull();
    });

    it('handles mismatched userAndChannel versioned data', async () => {
      httpMock
        .scope(nonDefaultRegistryUrl)
        .get('/v2/conans/search?q=poco')
        .reply(200, pocoJson);
      config.packageName = 'poco';
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'poco/1.2@un/matched',
        }),
      ).toBeNull();
    });

    it('handles malformed packages', async () => {
      httpMock
        .scope(nonDefaultRegistryUrl)
        .get('/v2/conans/search?q=bad')
        .reply(200, malformedJson);
      config.packageName = 'bad';
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'bad/1.2@_/_',
        }),
      ).toEqual({
        registryUrl: 'https://not.conan.io',
        releases: [
          {
            version: '1.9.3',
          },
        ],
      });
    });

    it('handles non 404 errors', async () => {
      httpMock
        .scope('https://fake.bintray.com/')
        .get('/v2/conans/search?q=poco')
        .replyWithError('error');
      config.registryUrls = ['https://fake.bintray.com/'];
      config.packageName = 'poco';
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'poco/1.2@_/_',
        }),
      ).toBeNull();
    });

    it('handles missing slash on registries', async () => {
      httpMock
        .scope('https://fake.bintray.com/')
        .get('/v2/conans/search?q=poco')
        .reply(200, fakeJson);
      config.registryUrls = ['https://fake.bintray.com'];
      config.packageName = 'poco';
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'poco/1.2@_/_',
        }),
      ).toBeNull();
    });

    it('artifactory sourceurl', async () => {
      httpMock
        .scope('https://fake.artifactory.com/artifactory/api/conan/test-repo/')
        .get('/v2/conans/search?q=arti')
        .reply(
          200,
          { results: ['arti/1.0.0@_/_', 'arti/1.1.1@_/_'] },
          {
            'x-jfrog-version': 'latest',
          },
        );
      httpMock
        .scope('https://fake.artifactory.com/artifactory/api/conan/test-repo/')
        .get('/v2/conans/arti/1.1.1/_/_/latest')
        .reply(200, {
          revision: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
          time: '2032-06-23T00:00:00.000+0000',
        });
      httpMock
        .scope(
          'https://fake.artifactory.com/artifactory/api/storage/test-repo/',
        )
        .get(
          '/_/arti/1.1.1/_/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/export/conanfile.py?properties=conan.package.url',
        )
        .reply(200, {
          properties: {
            'conan.package.url': 'https://fake.conan.url.com',
          },
        });

      config.registryUrls = [
        'https://fake.artifactory.com/artifactory/api/conan/test-repo',
      ];
      config.packageName = 'arti';
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'arti/1.1@_/_',
        }),
      ).toEqual({
        registryUrl:
          'https://fake.artifactory.com/artifactory/api/conan/test-repo',
        releases: [
          {
            version: '1.0.0',
          },
          {
            version: '1.1.1',
          },
        ],
        sourceUrl: 'https://fake.conan.url.com',
      });
    });

    it('artifactory header without api', async () => {
      httpMock
        .scope('https://fake.artifactory.com')
        .get('/v2/conans/search?q=arti')
        .reply(
          200,
          { results: ['arti/1.0.0@_/_', 'arti/1.1.1@_/_'] },
          {
            'x-jfrog-version': 'latest',
          },
        );
      config.registryUrls = ['https://fake.artifactory.com'];
      config.packageName = 'arti';
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'arti/1.1@_/_',
        }),
      ).toEqual({
        registryUrl: 'https://fake.artifactory.com',
        releases: [
          {
            version: '1.0.0',
          },
          {
            version: '1.1.1',
          },
        ],
      });
    });

    it('artifactory invalid version', async () => {
      httpMock
        .scope('https://fake.artifactory.com/artifactory/api/conan/test-repo')
        .get('/v2/conans/search?q=arti')
        .reply(
          200,
          {
            results: ['arti/invalid_version@_/_'],
          },
          { 'x-jfrog-version': 'latest' },
        );
      config.registryUrls = [
        'https://fake.artifactory.com/artifactory/api/conan/test-repo',
      ];
      config.packageName = 'arti';
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'arti/1.1@_/_',
        }),
      ).toEqual({
        registryUrl:
          'https://fake.artifactory.com/artifactory/api/conan/test-repo',
        releases: [],
      });
    });

    it('non artifactory header', async () => {
      httpMock
        .scope('https://fake.artifactory.com/artifactory/api/conan/test-repo')
        .get('/v2/conans/search?q=arti')
        .reply(200, {}, { 'x-jfrog-invalid-header': 'latest' });
      config.registryUrls = [
        'https://fake.artifactory.com/artifactory/api/conan/test-repo',
      ];
      config.packageName = 'arti';
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'arti/1.1@_/_',
        }),
      ).toBeNull();
    });

    it('artifactory no package url', async () => {
      httpMock
        .scope('https://fake.artifactory.com/artifactory/api/conan/test-repo/')
        .get('/v2/conans/search?q=arti')
        .reply(
          200,
          { results: ['arti/1.0.0@_/_', 'arti/1.1.1@_/_'] },
          {
            'x-jfrog-version': 'latest',
          },
        );
      httpMock
        .scope('https://fake.artifactory.com/artifactory/api/conan/test-repo/')
        .get('/v2/conans/arti/1.1.1/_/_/latest')
        .reply(200, {
          revision: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
          time: '2032-06-23T00:00:00.000+0000',
        });
      httpMock
        .scope(
          'https://fake.artifactory.com/artifactory/api/storage/test-repo/',
        )
        .get(
          '/_/arti/1.1.1/_/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/export/conanfile.py?properties=conan.package.url',
        )
        .reply(200);

      config.registryUrls = [
        'https://fake.artifactory.com/artifactory/api/conan/test-repo',
      ];
      config.packageName = 'arti';
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'arti/1.1@_/_',
        }),
      ).toEqual({
        registryUrl:
          'https://fake.artifactory.com/artifactory/api/conan/test-repo',
        releases: [
          {
            version: '1.0.0',
          },
          {
            version: '1.1.1',
          },
        ],
      });
    });

    it('artifactory http error', async () => {
      httpMock
        .scope('https://fake.artifactory.com/artifactory/api/conan/test-repo/')
        .get('/v2/conans/search?q=arti')
        .reply(
          200,
          { results: ['arti/1.0.0@_/_', 'arti/1.1.1@_/_'] },
          {
            'x-jfrog-version': 'latest',
          },
        );
      httpMock
        .scope('https://fake.artifactory.com/artifactory/api/conan/test-repo/')
        .get('/v2/conans/arti/1.1.1/_/_/latest')
        .reply(404);

      config.registryUrls = [
        'https://fake.artifactory.com/artifactory/api/conan/test-repo',
      ];
      config.packageName = 'arti';
      expect(
        await getPkgReleases({
          ...config,
          packageName: 'arti/1.1@_/_',
        }),
      ).toEqual({
        registryUrl:
          'https://fake.artifactory.com/artifactory/api/conan/test-repo',
        releases: [
          {
            version: '1.0.0',
          },
          {
            version: '1.1.1',
          },
        ],
      });
    });
  });
});
