import { describe, it, expect } from 'vitest';
import { fetchRepoStats } from './github';
import type { FetchedStats } from './github';

function makeMockFetch(body: unknown, status = 200) {
  return (async (url: string) => {
    return {
      ok: status >= 200 && status < 300,
      status,
      statusText: status === 200 ? 'OK' : 'Error',
      url,
      json: async () => body,
    } as unknown as Response;
  }) as typeof fetch;
}

describe('fetchRepoStats', () => {
  const token = 'ghp_test';

  it('parses repo, issues, pulls, releases, and contributors', async () => {
    const repo = {
      full_name: 'x/y', description: 'd', language: 'TypeScript',
      license: { spdx_id: 'MIT' }, stargazers_count: 10, forks_count: 2,
      subscribers_count: 1, open_issues_count: 0, default_branch: 'main',
      topics: [], created_at: '2025-01-01T00:00:00Z',
      updated_at: '2026-05-01T00:00:00Z', pushed_at: '2026-05-02T00:00:00Z',
      size: 1, has_wiki: false, has_pages: false, archived: false, homepage: null,
    };
    const issues: any[] = [];
    const pulls: any[] = [];
    const releases: any[] = [];
    const contributors = [{ login: 'octocat', contributions: 7 }];

    const fetchImpl = ((url: string) => {
      const u = new URL(url);
      const path = u.pathname;
      let body: unknown;
      if (path === '/repos/x/y') body = repo;
      else if (path.endsWith('/issues')) body = issues;
      else if (path.endsWith('/pulls')) body = pulls;
      else if (path.endsWith('/releases')) body = releases;
      else if (path.endsWith('/contributors')) body = contributors;
      else throw new Error('unexpected path: ' + path);
      return Promise.resolve({
        ok: true, status: 200, statusText: 'OK', url,
        json: async () => body,
      } as unknown as Response);
    }) as typeof fetch;

    const result = await fetchRepoStats(
      { owner: 'x', name: 'y' },
      { fetchImpl, token, issueLimit: 5, pullLimit: 5, releaseLimit: 5, contributorLimit: 5 },
    );
    expect(result.repo.stars).toBe(10);
    expect(result.contributors[0]!.login).toBe('octocat');
  });

  it('drops PRs from the issues list (they come from /pulls)', async () => {
    const issues: any[] = [
      { number: 1, title: 'real issue', state: 'open', user: { login: 'a' },
        created_at: '2026-01-01T00:00:00Z', updated_at: '2026-01-01T00:00:00Z',
        comments: 0, labels: [], pull_request: { url: 'x' } },
      { number: 2, title: 'actual issue', state: 'open', user: { login: 'b' },
        created_at: '2026-01-01T00:00:00Z', updated_at: '2026-01-01T00:00:00Z',
        comments: 0, labels: [], pull_request: undefined },
    ];
    const pulls: any[] = [];
    const releases: any[] = [];
    const contributors: any[] = [];

    const fetchImpl = ((url: string) => {
      const u = new URL(url);
      const body =
        u.pathname.endsWith('/issues') ? issues
        : u.pathname.endsWith('/pulls') ? pulls
        : u.pathname.endsWith('/releases') ? releases
        : u.pathname.endsWith('/contributors') ? contributors
        : { full_name: 'x/y' };
      return Promise.resolve({
        ok: true, status: 200, statusText: 'OK', url,
        json: async () => body,
      } as unknown as Response);
    }) as typeof fetch;

    const result = await fetchRepoStats(
      { owner: 'x', name: 'y' },
      { fetchImpl, token, issueLimit: 5, pullLimit: 5, releaseLimit: 5, contributorLimit: 5 },
    );
    expect(result.issues).toHaveLength(1);
    expect(result.issues[0]!.title).toBe('actual issue');
  });

  it('throws on a non-2xx response with a useful message', async () => {
    const fetchImpl = makeMockFetch({ message: 'Not Found' }, 404);
    await expect(
      fetchRepoStats({ owner: 'x', name: 'y' }, { fetchImpl, token }),
    ).rejects.toThrow(/404/);
  });
});
