import type { GithubDigestFile } from '../../../util/github/types';
import { toSha256 } from '../../../util/hash';
import { GitHubReleaseAttachmentMocker } from './test';
import { GithubReleaseAttachmentsDatasource } from '.';
import * as httpMock from '~test/http-mock';

describe('modules/datasource/github-release-attachments/digest', () => {
  const packageName = 'some/dep';
  const releaseMock = new GitHubReleaseAttachmentMocker(
    'https://api.github.com',
    packageName,
  );
  const githubReleaseAttachments = new GithubReleaseAttachmentsDatasource();

  describe('findDigestAsset', () => {
    it('finds SHASUMS.txt file containing digest', async () => {
      const release = releaseMock.withDigestFileAsset(
        'v1.0.0',
        'test-digest    linux-amd64.tar.gz',
        'another-digest linux-arm64.tar.gz',
      );

      const digestAsset = await githubReleaseAttachments.findDigestAsset(
        release,
        'test-digest',
      );
      expect(digestAsset?.assetName).toBe('SHASUMS.txt');
      expect(digestAsset?.digestedFileName).toBe('linux-amd64.tar.gz');
    });

    it('returns null when not found in digest file asset', async () => {
      const release = releaseMock.withDigestFileAsset(
        'v1.0.0',
        'another-digest linux-arm64.tar.gz',
      );
      // Small assets like this digest file may be downloaded twice
      httpMock
        .scope('https://api.github.com')
        .get(`/repos/${packageName}/releases/download/v1.0.0/SHASUMS.txt`)
        .reply(200, '');

      const digestAsset = await githubReleaseAttachments.findDigestAsset(
        release,
        'test-digest',
      );
      expect(digestAsset).toBeNull();
    });

    it('finds asset by digest', async () => {
      const content = '1'.repeat(10 * 1024);
      const release = releaseMock.withAssets('v1.0.0', {
        'smaller.zip': '1'.repeat(9 * 1024),
        'same-size.zip': '2'.repeat(10 * 1024),
        'asset.zip': content,
        'smallest.zip': '1'.repeat(8 * 1024),
      });
      const contentDigest = toSha256(content);

      const digestAsset = await githubReleaseAttachments.findDigestAsset(
        release,
        contentDigest,
      );
      expect(digestAsset?.assetName).toBe('asset.zip');
      expect(digestAsset?.digestedFileName).toBeUndefined();
    });

    it('returns null when no assets available', async () => {
      const release = releaseMock.release('v1.0.0');
      const digestAsset = await githubReleaseAttachments.findDigestAsset(
        release,
        'test-digest',
      );
      expect(digestAsset).toBeNull();
    });
  });

  describe('mapDigestAssetToRelease', () => {
    describe('with digest file', () => {
      const digestAsset: GithubDigestFile = {
        assetName: 'SHASUMS.txt',
        currentVersion: 'v1.0.0',
        currentDigest: 'old-digest',
        digestedFileName: 'asset.zip',
      };

      it('downloads updated digest file', async () => {
        const release = releaseMock.withDigestFileAsset(
          'v1.0.1',
          'updated-digest  asset.zip',
        );
        const digest = await githubReleaseAttachments.mapDigestAssetToRelease(
          digestAsset,
          release,
        );
        expect(digest).toBe('updated-digest');
      });

      it('maps digested file name to new version', async () => {
        const digestAssetWithVersion = {
          ...digestAsset,
          digestedFileName: 'asset-1.0.0.zip',
        };

        const release = releaseMock.withDigestFileAsset(
          'v1.0.1',
          'updated-digest  asset-1.0.1.zip',
        );
        const digest = await githubReleaseAttachments.mapDigestAssetToRelease(
          digestAssetWithVersion,
          release,
        );
        expect(digest).toBe('updated-digest');
      });

      it('returns null when not found in digest file', async () => {
        const release = releaseMock.withDigestFileAsset(
          'v1.0.1',
          'moot-digest asset.tar.gz',
        );
        const digest = await githubReleaseAttachments.mapDigestAssetToRelease(
          digestAsset,
          release,
        );
        expect(digest).toBeNull();
      });

      it('returns null when digest file not found', async () => {
        const release = releaseMock.release('v1.0.1');
        const digest = await githubReleaseAttachments.mapDigestAssetToRelease(
          digestAsset,
          release,
        );
        expect(digest).toBeNull();
      });

      it('falls back to digesting file when checksum file is removed', async () => {
        const checksumAssetWithVersion: GithubDigestFile = {
          assetName: 'SHASUMS.txt',
          currentVersion: 'v1.0.0',
          currentDigest: '0'.repeat(64),
          digestedFileName: 'asset-v1.0.0.zip',
        };
        const updatedContent = 'new content';
        const release = releaseMock.withAssets('v1.0.1', {
          'asset-v1.0.1.zip': updatedContent,
        });
        const contentDigest = toSha256(updatedContent);

        const digest = await githubReleaseAttachments.mapDigestAssetToRelease(
          checksumAssetWithVersion,
          release,
        );
        expect(digest).toEqual(contentDigest);
      });
    });

    describe('with digested file', () => {
      const digestAsset: GithubDigestFile = {
        assetName: 'asset.zip',
        currentVersion: 'v1.0.0',
        currentDigest: '0'.repeat(64),
      };

      it('digests updated file', async () => {
        const updatedContent = 'new content';
        const release = releaseMock.withAssets('v1.0.1', {
          'asset.zip': updatedContent,
        });
        const contentDigest = toSha256(updatedContent);

        const digest = await githubReleaseAttachments.mapDigestAssetToRelease(
          digestAsset,
          release,
        );
        expect(digest).toEqual(contentDigest);
      });

      it('returns null when not found', async () => {
        const release = releaseMock.release('v1.0.1');
        const digest = await githubReleaseAttachments.mapDigestAssetToRelease(
          digestAsset,
          release,
        );
        expect(digest).toBeNull();
      });
    });
  });
});
