import is from '@sindresorhus/is';
import type { Response, SimpleGit } from 'simple-git';
import Git from 'simple-git';
import { mock } from 'vitest-mock-extended';
import { GlobalConfig } from '../../../config/global';
import * as hostRules from '../../../util/host-rules';
import { extractPackageFile } from '.';

vi.mock('simple-git', () => ({ default: vi.fn() }));
const simpleGitFactoryMock = vi.mocked(Git);

const gitMock = mock<SimpleGit>();

describe('modules/manager/git-submodules/extract', () => {
  beforeEach(async () => {
    const { simpleGit: Git } =
      await vi.importActual<typeof import('simple-git')>('simple-git');
    GlobalConfig.set({ localDir: `${__dirname}/__fixtures__` });
    // clear host rules
    hostRules.clear();
    // clear environment variables
    process.env = {};

    simpleGitFactoryMock.mockImplementation((...args: any[]) => {
      const git = Git(...args);

      gitMock.env.mockImplementation(() => gitMock);
      gitMock.subModule.mockResolvedValue(
        '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
      );

      gitMock.raw.mockImplementation((options) => {
        if (
          (is.string(options) || is.array(options, is.string)) &&
          options.includes('remote.origin.url')
        ) {
          return Promise.resolve(
            'https://github.com/renovatebot/renovate.git',
          ) as Response<string>;
        }
        return git.raw(options);
      });
      return gitMock;
    });
  });

  describe('extractPackageFile()', () => {
    it('empty submodule returns null', async () => {
      expect(await extractPackageFile('', '.gitmodules.1', {})).toBeNull();
    });

    it('default branch is detected when no branch is specified', async () => {
      gitMock.listRemote.mockResolvedValueOnce(
        'ref: refs/heads/main  HEAD\n5701164b9f5edba1f6ca114c491a564ffb55a964        HEAD',
      );
      const res = await extractPackageFile('', '.gitmodules.2', {});
      expect(res?.deps).toHaveLength(1);
      expect(res?.deps[0].currentValue).toBe('main');
    });

    it('default branch is detected with using git environment variables when no branch is specified', async () => {
      gitMock.listRemote.mockResolvedValueOnce(
        'ref: refs/heads/main  HEAD\n5701164b9f5edba1f6ca114c491a564ffb55a964        HEAD',
      );
      hostRules.add({
        hostType: 'github',
        matchHost: 'github.com',
        token: 'abc123',
      });
      const res = await extractPackageFile('', '.gitmodules.2', {});
      expect(res?.deps).toHaveLength(1);
      expect(res?.deps[0].currentValue).toBe('main');
      expect(gitMock.env).toHaveBeenCalledWith({
        GIT_CONFIG_COUNT: '3',
        GIT_CONFIG_KEY_0: 'url.https://ssh:abc123@github.com/.insteadOf',
        GIT_CONFIG_KEY_1: 'url.https://git:abc123@github.com/.insteadOf',
        GIT_CONFIG_KEY_2: 'url.https://abc123@github.com/.insteadOf',
        GIT_CONFIG_VALUE_0: 'ssh://git@github.com/',
        GIT_CONFIG_VALUE_1: 'git@github.com:',
        GIT_CONFIG_VALUE_2: 'https://github.com/',
      });
      expect(gitMock.listRemote).toHaveBeenCalledWith([
        '--symref',
        'https://github.com/PowerShell/PowerShell-Docs',
        'HEAD',
      ]);
    });

    it('combined token from host rule is used to detect branch', async () => {
      gitMock.listRemote.mockResolvedValueOnce(
        'ref: refs/heads/main HEAD\n5701164b9f5edba1f6ca114c491a564ffb55a964        HEAD',
      );
      hostRules.add({
        hostType: 'github',
        matchHost: 'github.com',
        token: 'x-access-token:ghs_abc123',
      });
      const res = await extractPackageFile('', '.gitmodules.2', {});
      expect(res?.deps).toHaveLength(1);
      expect(res?.deps[0].currentValue).toBe('main');
      expect(gitMock.env).toHaveBeenCalledWith({
        GIT_CONFIG_COUNT: '3',
        GIT_CONFIG_KEY_0:
          'url.https://x-access-token:ghs_abc123@github.com/.insteadOf',
        GIT_CONFIG_KEY_1:
          'url.https://x-access-token:ghs_abc123@github.com/.insteadOf',
        GIT_CONFIG_KEY_2:
          'url.https://x-access-token:ghs_abc123@github.com/.insteadOf',
        GIT_CONFIG_VALUE_0: 'ssh://git@github.com/',
        GIT_CONFIG_VALUE_1: 'git@github.com:',
        GIT_CONFIG_VALUE_2: 'https://github.com/',
      });
      expect(gitMock.listRemote).toHaveBeenCalledWith([
        '--symref',
        'https://github.com/PowerShell/PowerShell-Docs',
        'HEAD',
      ]);
    });

    it('default to master if no branch can be detected', async () => {
      const res = await extractPackageFile('', '.gitmodules.2', {});
      expect(res?.deps).toHaveLength(1);
      expect(res?.deps[0].versioning).toBeUndefined();
      expect(res?.deps[0].currentValue).toBe('master');
    });

    it('given branch is used when branch is specified', async () => {
      const res = await extractPackageFile('', '.gitmodules.3', {});
      expect(res?.deps).toHaveLength(1);
      expect(res?.deps[0].currentValue).toBe('staging');
    });

    it('submodule packageName is constructed from relative path', async () => {
      const res = await extractPackageFile('', '.gitmodules.4', {});
      expect(res?.deps).toHaveLength(1);
      expect(res?.deps[0].packageName).toBe(
        'https://github.com/PowerShell/PowerShell-Docs',
      );
    });

    it('combined username+pwd from host rule is used to detect branch for gitlab', async () => {
      gitMock.listRemote.mockResolvedValueOnce(
        'ref: refs/heads/main HEAD\n5701164b9f5edba1f6ca114c491a564ffb55a964        HEAD',
      );
      hostRules.add({
        hostType: 'gitlab',
        matchHost: 'gitlab.com',
        username: 'username',
        password: 'password',
      });
      const res = await extractPackageFile('', '.gitmodules.2', {});
      expect(res?.deps).toHaveLength(1);
      expect(res?.deps[0].currentValue).toBe('main');
      expect(gitMock.env).toHaveBeenCalledWith({
        GIT_CONFIG_COUNT: '3',
        GIT_CONFIG_KEY_0: 'url.https://username:password@gitlab.com/.insteadOf',
        GIT_CONFIG_KEY_1: 'url.https://username:password@gitlab.com/.insteadOf',
        GIT_CONFIG_KEY_2: 'url.https://username:password@gitlab.com/.insteadOf',
        GIT_CONFIG_VALUE_0: 'ssh://git@gitlab.com/',
        GIT_CONFIG_VALUE_1: 'git@gitlab.com:',
        GIT_CONFIG_VALUE_2: 'https://gitlab.com/',
      });
      expect(gitMock.listRemote).toHaveBeenCalledWith([
        '--symref',
        'https://github.com/PowerShell/PowerShell-Docs',
        'HEAD',
      ]);
    });

    it('combined username+pwd from host rule is used to detect branch for git-refs and git-tags', async () => {
      gitMock.listRemote.mockResolvedValueOnce(
        'ref: refs/heads/main HEAD\n5701164b9f5edba1f6ca114c491a564ffb55a964        HEAD',
      );
      hostRules.add({
        hostType: 'git-refs',
        matchHost: 'gitrefs.com',
        username: 'git-refs-user',
        password: 'git-refs-password',
      });
      hostRules.add({
        hostType: 'git-tags',
        matchHost: 'gittags.com',
        username: 'git-tags-user',
        password: 'git-tags-password',
      });
      const res = await extractPackageFile('', '.gitmodules.2', {});
      expect(res?.deps).toHaveLength(1);
      expect(res?.deps[0].currentValue).toBe('main');
      expect(gitMock.env).toHaveBeenCalledWith({
        GIT_CONFIG_COUNT: '6',
        GIT_CONFIG_KEY_0:
          'url.https://git-refs-user:git-refs-password@gitrefs.com/.insteadOf',
        GIT_CONFIG_KEY_1:
          'url.https://git-refs-user:git-refs-password@gitrefs.com/.insteadOf',
        GIT_CONFIG_KEY_2:
          'url.https://git-refs-user:git-refs-password@gitrefs.com/.insteadOf',
        GIT_CONFIG_KEY_3:
          'url.https://git-tags-user:git-tags-password@gittags.com/.insteadOf',
        GIT_CONFIG_KEY_4:
          'url.https://git-tags-user:git-tags-password@gittags.com/.insteadOf',
        GIT_CONFIG_KEY_5:
          'url.https://git-tags-user:git-tags-password@gittags.com/.insteadOf',
        GIT_CONFIG_VALUE_0: 'ssh://git@gitrefs.com/',
        GIT_CONFIG_VALUE_1: 'git@gitrefs.com:',
        GIT_CONFIG_VALUE_2: 'https://gitrefs.com/',
        GIT_CONFIG_VALUE_3: 'ssh://git@gittags.com/',
        GIT_CONFIG_VALUE_4: 'git@gittags.com:',
        GIT_CONFIG_VALUE_5: 'https://gittags.com/',
      });
      expect(gitMock.listRemote).toHaveBeenCalledWith([
        '--symref',
        'https://github.com/PowerShell/PowerShell-Docs',
        'HEAD',
      ]);
    });

    it('extracts multiple submodules', async () => {
      hostRules.add({ matchHost: 'github.com', token: '123test' });
      hostRules.add({
        matchHost: 'domain.test',
        token: 'abc',
        hostType: 'git-refs',
      });
      hostRules.add({
        matchHost: 'gitlab.com',
        token: 'xyz',
        hostType: 'gitlab',
      });
      gitMock.listRemote.mockResolvedValueOnce(
        'ref: refs/heads/main  HEAD\n5701164b9f5edba1f6ca114c491a564ffb55a964        HEAD',
      );
      gitMock.listRemote.mockResolvedValueOnce(
        'ref: refs/heads/main  HEAD\n5701164b9f5edba1f6ca114c491a564ffb55a964        HEAD',
      );
      gitMock.listRemote.mockResolvedValueOnce(
        'ref: refs/heads/main  HEAD\n5701164b9f5edba1f6ca114c491a564ffb55a964        HEAD',
      );
      gitMock.listRemote.mockResolvedValueOnce(
        'ref: refs/heads/master  HEAD\n5701164b9f5edba1f6ca114c491a564ffb55a964        HEAD',
      );
      gitMock.listRemote.mockResolvedValueOnce(
        'ref: refs/heads/dev  HEAD\n5701164b9f5edba1f6ca114c491a564ffb55a964        HEAD',
      );
      const res = await extractPackageFile('', '.gitmodules.5', {});
      expect(res).toEqual({
        datasource: 'git-refs',
        deps: [
          {
            currentDigest: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
            currentValue: 'main',
            depName: 'deps/renovate',
            packageName: 'https://github.com/renovatebot/renovate.git',
          },
          {
            currentDigest: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
            currentValue: 'main',
            depName: 'deps/renovate-pro',
            packageName: 'https://github.com/renovatebot/pro.git',
          },
          {
            currentDigest: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
            currentValue: 'main',
            depName: 'deps/renovate-config',
            packageName: 'https://github.com/renovatebot/renovate-config.git',
          },
          {
            currentDigest: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
            currentValue: 'master',
            depName: 'some-other',
            packageName: 'https://domain.test/some/other.git',
          },
          {
            currentDigest: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
            currentValue: 'dev',
            depName: 'some-gitlab',
            packageName: 'https://gitlab.com/some/repo.git',
          },
        ],
      });
    });

    it('whitespaces in submodule URL are encoded properly', async () => {
      hostRules.add({
        matchHost: 'organization@dev.azure.com/organization',
        token: 'pat',
        hostType: 'azure',
      });
      gitMock.listRemote.mockResolvedValueOnce(
        'ref: refs/heads/main  HEAD\n5701164b9f5edba1f6ca114c491a564ffb55a964        HEAD',
      );
      const res = await extractPackageFile('', '.gitmodules.6', {});
      expect(res).toEqual({
        datasource: 'git-refs',
        deps: [
          {
            currentDigest: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
            currentValue: 'main',
            depName: 'some-azure',
            packageName:
              'https://dev.azure.com/organization/whitespace%20project/_git/repo',
          },
        ],
      });
    });

    it('fallback to current branch if special value is detected', async () => {
      gitMock.branch.mockResolvedValueOnce({
        all: ['staging', 'main'],
        branches: {
          staging: {
            current: true,
            name: 'staging',
            commit: '9eeb873',
            label: 'staging branch',
            linkedWorkTree: false,
          },
          main: {
            current: false,
            name: 'main',
            commit: 'e14c7e1',
            label: 'main branch',
            linkedWorkTree: false,
          },
        },
        current: 'staging',
        detached: false,
      });

      const res = await extractPackageFile('', '.gitmodules.7', {});
      expect(res).toEqual({
        datasource: 'git-refs',
        deps: [
          {
            currentDigest: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
            currentValue: 'staging',
            depName: 'PowerShell-Docs',
            packageName: 'https://github.com/PowerShell/PowerShell-Docs',
          },
        ],
      });
    });

    it('given semver version is extracted from branch and versioning is set to semver', async () => {
      const res = await extractPackageFile('', '.gitmodules.8', {});
      expect(res).toEqual({
        datasource: 'git-refs',
        deps: [
          {
            currentDigest: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
            currentValue: 'v0.0.1',
            depName: 'deps/renovate1',
            packageName: 'https://github.com/renovatebot/renovate.git',
            versioning: 'semver',
          },
          {
            currentDigest: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
            currentValue: '0.0.1',
            depName: 'deps/renovate2',
            packageName: 'https://github.com/renovatebot/renovate.git',
            versioning: 'semver',
          },
          {
            currentDigest: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
            currentValue: 'not-a-semver',
            packageName: 'https://github.com/renovatebot/renovate.git',
            depName: 'deps/renovate3',
          },
        ],
      });
    });
  });
});
