import { describe, it, expect, vi, beforeEach } from 'vitest';
import { RegistryClient } from '../../src/registry/client.js';
import type { RegistryServer } from '../../src/registry/types.js';

function makeServer(name: string, source: 'official' | 'glama' | 'smithery'): RegistryServer {
  return {
    name,
    description: `${name} description`,
    packages: { npm: `@test/${name}` },
    envTemplate: [],
    transport: 'stdio',
    popularityScore: 50,
    verified: source === 'smithery',
    sourceRegistry: source,
  };
}

// Mock fetch globally
const mockFetch = vi.fn();

beforeEach(() => {
  vi.stubGlobal('fetch', mockFetch);
  mockFetch.mockReset();
});

function mockRegistryResponse(source: string, servers: RegistryServer[]): void {
  mockFetch.mockImplementation((url: string) => {
    if (url.includes('registry.modelcontextprotocol.io')) {
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve({
          servers: servers
            .filter((s) => s.sourceRegistry === 'official')
            .map((s) => ({
              server: {
                name: s.name,
                description: s.description,
                packages: s.packages.npm !== undefined ? [{
                  registryType: 'npm',
                  identifier: s.packages.npm,
                  transport: { type: 'stdio' },
                  environmentVariables: [],
                }] : [],
                remotes: [],
              },
            })),
          metadata: { nextCursor: null, count: 1 },
        }),
      });
    }
    if (url.includes('glama.ai')) {
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve({
          servers: servers
            .filter((s) => s.sourceRegistry === 'glama')
            .map((s) => ({
              id: s.name,
              name: s.name,
              description: s.description,
              attributes: [],
              slug: s.packages.npm ?? '',
            })),
          pageInfo: { hasNextPage: false, hasPreviousPage: false },
        }),
      });
    }
    if (url.includes('registry.smithery.ai')) {
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve({
          servers: servers
            .filter((s) => s.sourceRegistry === 'smithery')
            .map((s) => ({
              qualifiedName: s.name,
              displayName: s.name,
              description: s.description,
              verified: s.verified,
              useCount: s.popularityScore,
              remote: false,
            })),
          pagination: { currentPage: 1, pageSize: 20, totalPages: 1, totalCount: 1 },
        }),
      });
    }
    return Promise.reject(new Error(`Unexpected URL: ${url}`));
  });
}

describe('RegistryClient', () => {
  it('queries all enabled registries', async () => {
    const testServers = [
      makeServer('slack-official', 'official'),
      makeServer('slack-glama', 'glama'),
      makeServer('slack-smithery', 'smithery'),
    ];
    mockRegistryResponse('all', testServers);

    const client = new RegistryClient();
    const results = await client.search({ query: 'slack' });

    expect(results.length).toBeGreaterThan(0);
    expect(mockFetch).toHaveBeenCalledTimes(3);
  });

  it('uses cached results on second call', async () => {
    mockRegistryResponse('all', [makeServer('slack', 'official')]);

    const client = new RegistryClient();
    await client.search({ query: 'slack' });
    mockFetch.mockClear();
    await client.search({ query: 'slack' });

    expect(mockFetch).not.toHaveBeenCalled();
  });

  it('filters by registry when specified', async () => {
    mockRegistryResponse('all', [makeServer('test', 'official')]);

    const client = new RegistryClient();
    await client.search({ query: 'test', registries: ['official'] });

    expect(mockFetch).toHaveBeenCalledTimes(1);
    const calledUrl = mockFetch.mock.calls[0]?.[0] as string;
    expect(calledUrl).toContain('modelcontextprotocol.io');
  });

  it('handles partial failures gracefully', async () => {
    mockFetch.mockImplementation((url: string) => {
      if (url.includes('glama.ai')) {
        return Promise.reject(new Error('Network error'));
      }
      if (url.includes('registry.smithery.ai')) {
        return Promise.resolve({
          ok: true,
          json: () => Promise.resolve({
            servers: [{
              qualifiedName: 'slack',
              displayName: 'Slack',
              description: 'Slack',
              verified: true,
              useCount: 100,
              remote: false,
            }],
            pagination: { currentPage: 1, pageSize: 20, totalPages: 1, totalCount: 1 },
          }),
        });
      }
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve({
          servers: [],
          metadata: { nextCursor: null },
        }),
      });
    });

    const client = new RegistryClient();
    const results = await client.search({ query: 'slack' });

    // Should still return results from successful sources
    expect(results.length).toBeGreaterThan(0);
  });

  it('records error counts on failures', async () => {
    mockFetch.mockImplementation((url: string) => {
      if (url.includes('glama.ai')) {
        return Promise.reject(new Error('fail'));
      }
      // Return empty for others
      if (url.includes('modelcontextprotocol')) {
        return Promise.resolve({
          ok: true,
          json: () => Promise.resolve({ servers: [], metadata: { nextCursor: null } }),
        });
      }
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve({
          servers: [],
          pagination: { currentPage: 1, pageSize: 20, totalPages: 1, totalCount: 0 },
        }),
      });
    });

    const client = new RegistryClient();
    await client.search({ query: 'test' });

    const errors = client.getErrorCounts();
    expect(errors.get('glama')).toBe(1);
  });

  it('filters by verified when specified', async () => {
    mockFetch.mockImplementation((url: string) => {
      if (url.includes('registry.smithery.ai')) {
        return Promise.resolve({
          ok: true,
          json: () => Promise.resolve({
            servers: [
              { qualifiedName: 'verified', displayName: 'Verified', description: '', verified: true, useCount: 100, remote: false },
              { qualifiedName: 'unverified', displayName: 'Unverified', description: '', verified: false, useCount: 50, remote: false },
            ],
            pagination: { currentPage: 1, pageSize: 20, totalPages: 1, totalCount: 2 },
          }),
        });
      }
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve({ servers: [], metadata: { nextCursor: null } }),
      });
    });

    // Mock glama too
    mockFetch.mockImplementation((url: string) => {
      if (url.includes('registry.smithery.ai')) {
        return Promise.resolve({
          ok: true,
          json: () => Promise.resolve({
            servers: [
              { qualifiedName: 'verified', displayName: 'Verified', description: '', verified: true, useCount: 100, remote: false },
              { qualifiedName: 'unverified', displayName: 'Unverified', description: '', verified: false, useCount: 50, remote: false },
            ],
            pagination: { currentPage: 1, pageSize: 20, totalPages: 1, totalCount: 2 },
          }),
        });
      }
      if (url.includes('glama.ai')) {
        return Promise.resolve({
          ok: true,
          json: () => Promise.resolve({ servers: [], pageInfo: { hasNextPage: false, hasPreviousPage: false } }),
        });
      }
      return Promise.resolve({
        ok: true,
        json: () => Promise.resolve({ servers: [], metadata: { nextCursor: null } }),
      });
    });

    const client = new RegistryClient();
    const results = await client.search({ query: 'test', verified: true });

    for (const r of results) {
      expect(r.verified).toBe(true);
    }
  });

  it('respects limit option', async () => {
    mockRegistryResponse('all', [
      makeServer('a', 'official'),
      makeServer('b', 'glama'),
      makeServer('c', 'smithery'),
    ]);

    const client = new RegistryClient();
    const results = await client.search({ query: 'test', limit: 1 });
    expect(results.length).toBeLessThanOrEqual(1);
  });

  it('records latency metrics', async () => {
    mockRegistryResponse('all', [makeServer('test', 'official')]);

    const client = new RegistryClient();
    await client.search({ query: 'test' });

    const latencies = client.getQueryLatencies();
    expect(latencies.size).toBeGreaterThan(0);
  });

  it('clearCache empties cache', async () => {
    mockRegistryResponse('all', [makeServer('test', 'official')]);

    const client = new RegistryClient();
    await client.search({ query: 'test' });
    client.clearCache();
    mockFetch.mockClear();
    mockRegistryResponse('all', [makeServer('test', 'official')]);
    await client.search({ query: 'test' });

    // Should have fetched again after cache clear
    expect(mockFetch).toHaveBeenCalled();
  });
});
