import { describe, it, expect, beforeAll } from 'vitest';
import http from 'node:http';
import { isMcplocalRunning, mcpctl } from './mcp-client.js';

const MCPLOCAL_URL = process.env['MCPLOCAL_URL'] ?? 'http://localhost:3200';

let available = false;

function fetchJson<T>(urlPath: string, method = 'GET'): Promise<T | null> {
  return new Promise((resolve) => {
    const req = http.request(`${MCPLOCAL_URL}${urlPath}`, { method, timeout: 5000 }, (res) => {
      const chunks: Buffer[] = [];
      res.on('data', (chunk: Buffer) => chunks.push(chunk));
      res.on('end', () => {
        try {
          resolve(JSON.parse(Buffer.concat(chunks).toString()) as T);
        } catch {
          resolve(null);
        }
      });
    });
    req.on('error', () => resolve(null));
    req.on('timeout', () => { req.destroy(); resolve(null); });
    req.end();
  });
}

beforeAll(async () => {
  available = await isMcplocalRunning();
});

interface CacheStats {
  rootDir: string;
  totalSize: number;
  totalEntries: number;
  namespaces: Array<{ name: string; entries: number; size: number }>;
}

interface ClearResult {
  removed: number;
  freedBytes: number;
}

describe('Cache smoke tests', () => {
  describe('mcplocal /cache endpoints', () => {
    it('GET /cache/stats returns valid stats structure', async () => {
      if (!available) return;

      const stats = await fetchJson<CacheStats>('/cache/stats');
      expect(stats).not.toBeNull();
      expect(stats).toHaveProperty('rootDir');
      expect(stats).toHaveProperty('totalSize');
      expect(stats).toHaveProperty('totalEntries');
      expect(stats).toHaveProperty('namespaces');
      expect(Array.isArray(stats!.namespaces)).toBe(true);
      expect(typeof stats!.totalSize).toBe('number');
      expect(typeof stats!.totalEntries).toBe('number');
    });

    it('namespaces use provider--model--proxymodel format', async () => {
      if (!available) return;

      const stats = await fetchJson<CacheStats>('/cache/stats');
      if (!stats || stats.namespaces.length === 0) return;

      // Each namespace should contain -- separators
      for (const ns of stats.namespaces) {
        expect(ns.name).toBeTruthy();
        expect(typeof ns.entries).toBe('number');
        expect(typeof ns.size).toBe('number');
      }
    });

    it('DELETE /cache returns clear result', async () => {
      if (!available) return;

      // This clears the cache, but it's non-destructive for a smoke test
      const result = await fetchJson<ClearResult>('/cache', 'DELETE');
      expect(result).not.toBeNull();
      expect(result).toHaveProperty('removed');
      expect(result).toHaveProperty('freedBytes');
      expect(typeof result!.removed).toBe('number');
      expect(typeof result!.freedBytes).toBe('number');
    });
  });

  describe('mcpctl cache CLI', () => {
    it('mcpctl cache stats shows cache statistics', async () => {
      if (!available) return;

      const output = await mcpctl('cache stats');
      // Should either show table or "Cache is empty."
      expect(output.length).toBeGreaterThan(0);
      const hasTable = output.includes('NAMESPACE');
      const isEmpty = output.includes('Cache is empty');
      expect(hasTable || isEmpty).toBe(true);
    });

    it('mcpctl cache clear runs without error', async () => {
      if (!available) return;

      const output = await mcpctl('cache clear');
      // Should report what was cleared, or that cache is empty
      expect(output).toMatch(/[Cc]lear|empty/i);
    });
  });

  describe('cache namespace isolation', () => {
    it('stats show separate namespaces per llm provider/model/proxymodel combo', async () => {
      if (!available) return;

      // After any project MCP sessions have run, check that namespaces
      // follow the provider--model--proxymodel convention
      const stats = await fetchJson<CacheStats>('/cache/stats');
      if (!stats || stats.namespaces.length === 0) return;

      // Namespaces with -- separators indicate proper isolation
      const separated = stats.namespaces.filter((ns) => ns.name.includes('--'));
      // If there are namespaces, at least some should have the separator format
      // (the 'dynamic' namespace from hot-swap is an exception)
      if (stats.namespaces.length > 1) {
        expect(separated.length).toBeGreaterThan(0);
      }
    });
  });
});
