import { beforeEach, describe, expect, it, vi } from 'vitest';
import { GiteaHost } from '../gitea';
import { GitHubHost } from '../github';

describe('lastCommit (Gitea)', () => {
  let fetchSpy: ReturnType<typeof vi.spyOn>;

  beforeEach(() => {
    fetchSpy = vi.spyOn(globalThis, 'fetch');
  });

  it('returns commit metadata when API succeeds', async () => {
    fetchSpy.mockResolvedValueOnce(new Response(JSON.stringify([
      { sha: 'abc1234567', commit: { author: { name: 'Alice', date: '2026-04-20T12:00:00Z' } } },
    ]), { status: 200 }));
    const host = new GiteaHost('https://git.example.com', 'user/vault', 'tok');
    const result = await host.lastCommit('manifest.enc');
    expect(result).toEqual({ sha: 'abc1234', author: 'Alice', date: '2026-04-20T12:00:00Z' });
    expect(fetchSpy.mock.calls[0][0]).toContain('/repos/user/vault/commits');
    expect(fetchSpy.mock.calls[0][0]).toContain('path=manifest.enc');
    expect(fetchSpy.mock.calls[0][0]).toContain('limit=1');
  });

  it('returns null on 404', async () => {
    fetchSpy.mockResolvedValueOnce(new Response('', { status: 404 }));
    const host = new GiteaHost('https://git.example.com', 'user/vault', 'tok');
    expect(await host.lastCommit('manifest.enc')).toBeNull();
  });

  it('returns null on network error', async () => {
    fetchSpy.mockRejectedValueOnce(new Error('network'));
    const host = new GiteaHost('https://git.example.com', 'user/vault', 'tok');
    expect(await host.lastCommit('manifest.enc')).toBeNull();
  });
});

describe('lastCommit (GitHub)', () => {
  let fetchSpy: ReturnType<typeof vi.spyOn>;

  beforeEach(() => {
    fetchSpy = vi.spyOn(globalThis, 'fetch');
  });

  it('returns commit metadata when API succeeds', async () => {
    fetchSpy.mockResolvedValueOnce(new Response(JSON.stringify([
      { sha: 'def4567890', commit: { author: { name: 'Bob', date: '2026-04-22T15:00:00Z' } } },
    ]), { status: 200 }));
    const host = new GitHubHost('user/vault', 'tok');
    const result = await host.lastCommit('manifest.enc');
    expect(result).toEqual({ sha: 'def4567', author: 'Bob', date: '2026-04-22T15:00:00Z' });
    expect(fetchSpy.mock.calls[0][0]).toContain('/repos/user/vault/commits');
    expect(fetchSpy.mock.calls[0][0]).toContain('path=manifest.enc');
    expect(fetchSpy.mock.calls[0][0]).toContain('per_page=1');
  });

  it('returns null when commits list is empty', async () => {
    fetchSpy.mockResolvedValueOnce(new Response(JSON.stringify([]), { status: 200 }));
    const host = new GitHubHost('user/vault', 'tok');
    expect(await host.lastCommit('manifest.enc')).toBeNull();
  });
});

describe('writeFileCreateOnly (Gitea)', () => {
  let fetchSpy: ReturnType<typeof vi.spyOn>;
  beforeEach(() => { fetchSpy = vi.spyOn(globalThis, 'fetch'); });

  it('creates when file does not exist', async () => {
    fetchSpy
      .mockResolvedValueOnce(new Response('', { status: 404 }))     // pre-check
      .mockResolvedValueOnce(new Response('{}', { status: 201 })); // POST
    const host = new GiteaHost('https://git.example.com', 'user/vault', 'tok');
    await host.writeFileCreateOnly('manifest.enc', new Uint8Array([1, 2, 3]), 'init');
    expect(fetchSpy).toHaveBeenCalledTimes(2);
    expect((fetchSpy.mock.calls[1][1] as RequestInit).method).toBe('POST');
  });

  it('throws when file already exists', async () => {
    fetchSpy.mockResolvedValueOnce(new Response(
      JSON.stringify({ sha: 'abc' }), { status: 200 },
    ));
    const host = new GiteaHost('https://git.example.com', 'user/vault', 'tok');
    await expect(
      host.writeFileCreateOnly('manifest.enc', new Uint8Array([1]), 'init'),
    ).rejects.toThrow(/already exists/);
    expect(fetchSpy).toHaveBeenCalledTimes(1);  // pre-check only, no write
  });
});

describe('writeFileCreateOnly (GitHub)', () => {
  let fetchSpy: ReturnType<typeof vi.spyOn>;
  beforeEach(() => { fetchSpy = vi.spyOn(globalThis, 'fetch'); });

  it('throws when file already exists', async () => {
    fetchSpy.mockResolvedValueOnce(new Response(
      JSON.stringify({ sha: 'abc' }), { status: 200 },
    ));
    const host = new GitHubHost('user/vault', 'tok');
    await expect(
      host.writeFileCreateOnly('manifest.enc', new Uint8Array([1]), 'init'),
    ).rejects.toThrow(/already exists/);
  });

  it('creates when file does not exist', async () => {
    fetchSpy
      .mockResolvedValueOnce(new Response('', { status: 404 }))
      .mockResolvedValueOnce(new Response('{}', { status: 201 }));
    const host = new GitHubHost('user/vault', 'tok');
    await host.writeFileCreateOnly('manifest.enc', new Uint8Array([1]), 'init');
    expect(fetchSpy).toHaveBeenCalledTimes(2);
    expect((fetchSpy.mock.calls[1][1] as RequestInit).method).toBe('PUT');
  });
});
