import { describe, it, expect, vi, beforeEach } from 'vitest';
import Fastify from 'fastify';
import { MetricsCollector } from '../src/services/metrics-collector.js';
import { HealthAggregator } from '../src/services/health-aggregator.js';
import type { McpOrchestrator } from '../src/services/orchestrator.js';
import { registerHealthMonitoringRoutes } from '../src/routes/health-monitoring.js';

function mockOrchestrator(available = true): McpOrchestrator {
  return {
    ping: vi.fn(async () => available),
    pullImage: vi.fn(async () => {}),
    createContainer: vi.fn(async () => ({
      containerId: 'c1', name: 'test', state: 'running' as const, createdAt: new Date(),
    })),
    stopContainer: vi.fn(async () => {}),
    removeContainer: vi.fn(async () => {}),
    inspectContainer: vi.fn(async () => ({
      containerId: 'c1', name: 'test', state: 'running' as const, createdAt: new Date(),
    })),
    getContainerLogs: vi.fn(async () => ({ stdout: '', stderr: '' })),
  };
}

describe('MetricsCollector', () => {
  let collector: MetricsCollector;

  beforeEach(() => {
    collector = new MetricsCollector();
  });

  it('registers and retrieves instance metrics', () => {
    collector.register('inst-1');
    const m = collector.getMetrics('inst-1');
    expect(m).toBeDefined();
    expect(m!.instanceId).toBe('inst-1');
    expect(m!.requestCount).toBe(0);
    expect(m!.errorCount).toBe(0);
    expect(m!.status).toBe('unknown');
  });

  it('returns undefined for unregistered instance', () => {
    expect(collector.getMetrics('nonexistent')).toBeUndefined();
  });

  it('records requests and increments count', () => {
    collector.register('inst-1');
    collector.recordRequest('inst-1', 50);
    collector.recordRequest('inst-1', 100);
    const m = collector.getMetrics('inst-1')!;
    expect(m.requestCount).toBe(2);
    expect(m.lastRequestAt).not.toBeNull();
    expect(m.latencyMs).toEqual([50, 100]);
  });

  it('records errors', () => {
    collector.register('inst-1');
    collector.recordError('inst-1');
    collector.recordError('inst-1');
    expect(collector.getMetrics('inst-1')!.errorCount).toBe(2);
  });

  it('auto-registers on recordRequest for unknown instance', () => {
    collector.recordRequest('new-inst');
    expect(collector.getMetrics('new-inst')).toBeDefined();
    expect(collector.getMetrics('new-inst')!.requestCount).toBe(1);
  });

  it('calculates error rate', () => {
    collector.register('inst-1');
    collector.recordRequest('inst-1');
    collector.recordRequest('inst-1');
    collector.recordError('inst-1');
    expect(collector.getErrorRate('inst-1')).toBe(0.5);
  });

  it('returns 0 error rate when no requests', () => {
    collector.register('inst-1');
    expect(collector.getErrorRate('inst-1')).toBe(0);
  });

  it('calculates average latency', () => {
    collector.register('inst-1');
    collector.recordRequest('inst-1', 100);
    collector.recordRequest('inst-1', 200);
    collector.recordRequest('inst-1', 300);
    expect(collector.getAverageLatency('inst-1')).toBe(200);
  });

  it('returns 0 average latency when no samples', () => {
    collector.register('inst-1');
    expect(collector.getAverageLatency('inst-1')).toBe(0);
  });

  it('updates status', () => {
    collector.register('inst-1');
    collector.updateStatus('inst-1', 'healthy');
    expect(collector.getMetrics('inst-1')!.status).toBe('healthy');
  });

  it('getAllMetrics returns all registered instances', () => {
    collector.register('a');
    collector.register('b');
    collector.register('c');
    const all = collector.getAllMetrics();
    expect(all).toHaveLength(3);
    expect(all.map((m) => m.instanceId).sort()).toEqual(['a', 'b', 'c']);
  });

  it('unregister removes instance', () => {
    collector.register('inst-1');
    collector.unregister('inst-1');
    expect(collector.getMetrics('inst-1')).toBeUndefined();
    expect(collector.getAllMetrics()).toHaveLength(0);
  });

  it('reset clears all metrics', () => {
    collector.register('a');
    collector.register('b');
    collector.reset();
    expect(collector.getAllMetrics()).toHaveLength(0);
  });

  it('computes uptime from registration time', () => {
    collector.register('inst-1');
    const m = collector.getMetrics('inst-1')!;
    expect(m.uptime).toBeGreaterThanOrEqual(0);
  });
});

describe('HealthAggregator', () => {
  let collector: MetricsCollector;
  let orchestrator: McpOrchestrator;
  let aggregator: HealthAggregator;

  beforeEach(() => {
    collector = new MetricsCollector();
    orchestrator = mockOrchestrator(true);
    aggregator = new HealthAggregator(collector, orchestrator);
  });

  it('reports healthy when all instances healthy', async () => {
    collector.register('a');
    collector.register('b');
    collector.updateStatus('a', 'healthy');
    collector.updateStatus('b', 'healthy');

    const overview = await aggregator.getOverview();
    expect(overview.overallStatus).toBe('healthy');
    expect(overview.totalInstances).toBe(2);
    expect(overview.healthyCount).toBe(2);
    expect(overview.unhealthyCount).toBe(0);
    expect(overview.runtimeAvailable).toBe(true);
  });

  it('reports degraded when some instances unhealthy', async () => {
    collector.register('a');
    collector.register('b');
    collector.updateStatus('a', 'healthy');
    collector.updateStatus('b', 'unhealthy');

    const overview = await aggregator.getOverview();
    expect(overview.overallStatus).toBe('degraded');
    expect(overview.unhealthyCount).toBe(1);
  });

  it('reports degraded when some instances unknown', async () => {
    collector.register('a');
    collector.updateStatus('a', 'healthy');
    collector.register('b'); // status remains 'unknown'

    const overview = await aggregator.getOverview();
    expect(overview.overallStatus).toBe('degraded');
    expect(overview.unknownCount).toBe(1);
  });

  it('reports unhealthy when all instances unhealthy', async () => {
    collector.register('a');
    collector.updateStatus('a', 'unhealthy');

    const overview = await aggregator.getOverview();
    expect(overview.overallStatus).toBe('unhealthy');
  });

  it('reports unhealthy when runtime unavailable', async () => {
    orchestrator = mockOrchestrator(false);
    aggregator = new HealthAggregator(collector, orchestrator);

    const overview = await aggregator.getOverview();
    expect(overview.overallStatus).toBe('unhealthy');
    expect(overview.runtimeAvailable).toBe(false);
  });

  it('reports healthy with no instances and runtime available', async () => {
    const overview = await aggregator.getOverview();
    expect(overview.overallStatus).toBe('healthy');
    expect(overview.totalInstances).toBe(0);
  });

  it('computes aggregate error rate', async () => {
    collector.register('a');
    collector.recordRequest('a');
    collector.recordRequest('a');
    collector.recordError('a');

    const overview = await aggregator.getOverview();
    expect(overview.aggregateErrorRate).toBe(0.5);
  });

  it('getInstanceHealth returns instance details', () => {
    collector.register('inst-1');
    collector.updateStatus('inst-1', 'healthy');
    collector.recordRequest('inst-1', 150);
    collector.recordError('inst-1');

    const health = aggregator.getInstanceHealth('inst-1');
    expect(health).toBeDefined();
    expect(health!.instanceId).toBe('inst-1');
    expect(health!.status).toBe('healthy');
    expect(health!.requestCount).toBe(1);
    expect(health!.errorCount).toBe(1);
    expect(health!.errorRate).toBe(1);
    expect(health!.averageLatencyMs).toBe(150);
  });

  it('getInstanceHealth returns undefined for unknown instance', () => {
    expect(aggregator.getInstanceHealth('nonexistent')).toBeUndefined();
  });
});

describe('Health Monitoring Routes', () => {
  let collector: MetricsCollector;

  beforeEach(() => {
    collector = new MetricsCollector();
  });

  async function buildApp(runtimeAvailable = true) {
    const orchestrator = mockOrchestrator(runtimeAvailable);
    const aggregator = new HealthAggregator(collector, orchestrator);
    const app = Fastify();
    registerHealthMonitoringRoutes(app, {
      healthAggregator: aggregator,
      metricsCollector: collector,
    });
    return app;
  }

  it('GET /api/v1/health/overview returns system health', async () => {
    collector.register('a');
    collector.updateStatus('a', 'healthy');
    const app = await buildApp();

    const res = await app.inject({ method: 'GET', url: '/api/v1/health/overview' });
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.overallStatus).toBe('healthy');
    expect(body.totalInstances).toBe(1);
    expect(body.runtimeAvailable).toBe(true);
    expect(body.timestamp).toBeDefined();
  });

  it('GET /api/v1/health/instances/:id returns instance health', async () => {
    collector.register('inst-1');
    collector.updateStatus('inst-1', 'healthy');
    collector.recordRequest('inst-1', 42);
    const app = await buildApp();

    const res = await app.inject({ method: 'GET', url: '/api/v1/health/instances/inst-1' });
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.instanceId).toBe('inst-1');
    expect(body.status).toBe('healthy');
    expect(body.requestCount).toBe(1);
  });

  it('GET /api/v1/health/instances/:id returns 404 for unknown', async () => {
    const app = await buildApp();

    const res = await app.inject({ method: 'GET', url: '/api/v1/health/instances/nonexistent' });
    expect(res.statusCode).toBe(404);
    expect(res.json().error).toContain('not found');
  });

  it('GET /api/v1/metrics returns all instance metrics', async () => {
    collector.register('a');
    collector.register('b');
    collector.recordRequest('a');
    const app = await buildApp();

    const res = await app.inject({ method: 'GET', url: '/api/v1/metrics' });
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.instances).toHaveLength(2);
    expect(body.timestamp).toBeDefined();
  });

  it('GET /api/v1/metrics returns empty with no instances', async () => {
    const app = await buildApp();

    const res = await app.inject({ method: 'GET', url: '/api/v1/metrics' });
    expect(res.statusCode).toBe(200);
    expect(res.json().instances).toEqual([]);
  });
});
