import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { RegistryCache } from '../../src/registry/cache.js';
import type { RegistryServer, SearchOptions } from '../../src/registry/types.js';

function makeServer(name: string): RegistryServer {
  return {
    name,
    description: `${name} server`,
    packages: {},
    envTemplate: [],
    transport: 'stdio',
    popularityScore: 0,
    verified: false,
    sourceRegistry: 'official',
  };
}

const defaultOptions: SearchOptions = { query: 'test' };

describe('RegistryCache', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });
  afterEach(() => {
    vi.useRealTimers();
  });

  it('returns null for cache miss', () => {
    const cache = new RegistryCache();
    expect(cache.get('unknown', defaultOptions)).toBeNull();
  });

  it('returns data for cache hit within TTL', () => {
    const cache = new RegistryCache();
    const data = [makeServer('test')];
    cache.set('test', defaultOptions, data);
    expect(cache.get('test', defaultOptions)).toEqual(data);
  });

  it('returns null after TTL expires', () => {
    const cache = new RegistryCache(1000); // 1 second TTL
    cache.set('test', defaultOptions, [makeServer('test')]);

    vi.advanceTimersByTime(1001);
    expect(cache.get('test', defaultOptions)).toBeNull();
  });

  it('generates deterministic cache keys', () => {
    const cache = new RegistryCache();
    const data = [makeServer('test')];
    cache.set('query', { query: 'query', limit: 10 }, data);
    expect(cache.get('query', { query: 'query', limit: 10 })).toEqual(data);
  });

  it('generates different keys for different queries', () => {
    const cache = new RegistryCache();
    cache.set('a', { query: 'a' }, [makeServer('a')]);
    expect(cache.get('b', { query: 'b' })).toBeNull();
  });

  it('tracks hits and misses correctly', () => {
    const cache = new RegistryCache();
    cache.set('test', defaultOptions, [makeServer('test')]);

    cache.get('test', defaultOptions); // hit
    cache.get('test', defaultOptions); // hit
    cache.get('miss', { query: 'miss' }); // miss

    const ratio = cache.getHitRatio();
    expect(ratio.hits).toBe(2);
    expect(ratio.misses).toBe(1);
    expect(ratio.ratio).toBeCloseTo(2 / 3);
  });

  it('returns 0 ratio when no accesses', () => {
    const cache = new RegistryCache();
    expect(cache.getHitRatio().ratio).toBe(0);
  });

  it('clears all entries and resets metrics', () => {
    const cache = new RegistryCache();
    cache.set('a', { query: 'a' }, [makeServer('a')]);
    cache.get('a', { query: 'a' }); // hit
    cache.clear();

    expect(cache.get('a', { query: 'a' })).toBeNull();
    expect(cache.size).toBe(0);
    expect(cache.getHitRatio().hits).toBe(0);
  });
});
