import { getTargetMintVersion, downloadTargetMint } from '@mintlify/previewing';
import { execSync } from 'node:child_process';
import { mockProcessExit } from 'vitest-mock-process';

import { getLatestCliVersion, getVersions } from '../src/helpers.js';
import { update } from '../src/update.js';

vi.mock('node:child_process', () => ({
  execSync: vi.fn(),
}));

vi.mock('@mintlify/previewing', () => ({
  getClientVersion: vi.fn(),
  getTargetMintVersion: vi.fn(),
  downloadTargetMint: vi.fn(),
}));

vi.mock('../src/helpers.js', async (importOriginal) => {
  const original = await importOriginal();
  return {
    // @ts-expect-error - this is a mock
    ...original,
    getLatestCliVersion: vi.fn(),
    getVersions: vi.fn(),
  };
});

const processExitMock = mockProcessExit();

describe('update', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  afterEach(() => {
    vi.resetAllMocks();
  });

  it('should update the cli and client successfully', async () => {
    vi.mocked(getVersions).mockReturnValue({
      cli: '1.0.0',
      client: '1.0.0',
    });
    vi.mocked(getTargetMintVersion).mockResolvedValue('2.0.0');
    vi.mocked(getLatestCliVersion).mockReturnValue('2.0.0');
    vi.mocked(execSync).mockReturnValue(Buffer.from(''));
    vi.mocked(downloadTargetMint).mockResolvedValue();

    await update({ packageName: 'mintlify' });

    expect(execSync).toHaveBeenCalledWith('npm install -g mintlify@latest --silent');
    expect(downloadTargetMint).toHaveBeenCalledWith({
      logger: expect.any(Object),
      targetVersion: '2.0.0',
      existingVersion: '1.0.0',
    });
  });

  it('should return when already up to date', async () => {
    vi.mocked(getVersions).mockReturnValue({
      cli: '1.0.0',
      client: '1.0.0',
    });
    vi.mocked(getLatestCliVersion).mockReturnValue('1.0.0');
    vi.mocked(getTargetMintVersion).mockResolvedValue('1.0.0');
    vi.mocked(execSync).mockReturnValue(Buffer.from(''));
    vi.mocked(downloadTargetMint).mockResolvedValue();

    await update({ packageName: 'mintlify' });

    expect(execSync).not.toHaveBeenCalled();
    expect(downloadTargetMint).not.toHaveBeenCalled();
  });

  it('should only update cli if client version is up to date', async () => {
    vi.mocked(getVersions).mockReturnValue({
      cli: '1.0.0',
      client: '2.0.0',
    });
    vi.mocked(getLatestCliVersion).mockReturnValue('2.0.0');
    vi.mocked(getTargetMintVersion).mockResolvedValue('2.0.0');
    vi.mocked(execSync).mockReturnValue(Buffer.from(''));

    await update({ packageName: 'mintlify' });

    expect(execSync).toHaveBeenCalledWith('npm install -g mintlify@latest --silent');
    expect(downloadTargetMint).not.toHaveBeenCalled();
  });

  it('should only update client if cli version is up to date', async () => {
    vi.mocked(getVersions).mockReturnValue({
      cli: '2.0.0',
      client: '1.0.0',
    });
    vi.mocked(getLatestCliVersion).mockReturnValue('2.0.0');
    vi.mocked(getTargetMintVersion).mockResolvedValue('2.0.0');
    vi.mocked(execSync).mockReturnValue(Buffer.from(''));

    await update({ packageName: 'mintlify' });

    expect(execSync).not.toHaveBeenCalled();
    expect(downloadTargetMint).toHaveBeenCalledWith({
      logger: expect.any(Object),
      targetVersion: '2.0.0',
      existingVersion: '1.0.0',
    });
  });

  it('should handle cli update failure', async () => {
    vi.mocked(getVersions).mockReturnValue({
      cli: '1.0.0',
      client: '1.0.0',
    });
    vi.mocked(getLatestCliVersion).mockReturnValue('2.0.0');
    vi.mocked(getTargetMintVersion).mockResolvedValue('2.0.0');
    vi.mocked(execSync).mockImplementation(() => {
      throw new Error('Update failed');
    });

    await update({ packageName: 'mintlify' });
    expect(processExitMock).toHaveBeenCalledWith(1);
  });

  it('should handle client update failure', async () => {
    vi.mocked(getVersions).mockReturnValue({
      cli: '1.0.0',
      client: '1.0.0',
    });
    vi.mocked(getLatestCliVersion).mockReturnValue('2.0.0');
    vi.mocked(getTargetMintVersion).mockResolvedValue('2.0.0');
    vi.mocked(execSync).mockReturnValue(Buffer.from(''));
    vi.mocked(downloadTargetMint).mockRejectedValue(new Error('Download failed'));

    await update({ packageName: 'mintlify' });
    expect(processExitMock).toHaveBeenCalledWith(1);
  });
});
