import { describe, it, expect, vi, beforeEach } from 'vitest';
import { TieredHealthMonitor } from '../src/health/tiered.js';
import type { TieredHealthMonitorDeps } from '../src/health/tiered.js';
import type { McpdClient } from '../src/http/mcpd-client.js';
import { ProviderRegistry } from '../src/providers/registry.js';
import type { LlmProvider } from '../src/providers/types.js';

function mockMcpdClient(overrides?: {
  getResult?: unknown;
  getFails?: boolean;
  instancesResult?: { instances: Array<{ name: string; status: string }> };
  instancesFails?: boolean;
}): McpdClient {
  const client = {
    get: vi.fn(async (path: string) => {
      if (path === '/health') {
        if (overrides?.getFails) {
          throw new Error('Connection refused');
        }
        return overrides?.getResult ?? { status: 'ok' };
      }
      if (path === '/instances') {
        if (overrides?.instancesFails) {
          throw new Error('Connection refused');
        }
        return overrides?.instancesResult ?? { instances: [] };
      }
      return {};
    }),
    post: vi.fn(),
    put: vi.fn(),
    delete: vi.fn(),
    forward: vi.fn(),
  } as unknown as McpdClient;
  return client;
}

function mockLlmProvider(name: string): LlmProvider {
  return {
    name,
    complete: vi.fn(),
    listModels: vi.fn(async () => []),
    isAvailable: vi.fn(async () => true),
  };
}

describe('TieredHealthMonitor', () => {
  let providerRegistry: ProviderRegistry;

  beforeEach(() => {
    providerRegistry = new ProviderRegistry();
  });

  describe('mcplocal health', () => {
    it('reports healthy status with uptime', async () => {
      const monitor = new TieredHealthMonitor({
        mcpdClient: null,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      expect(result.mcplocal.status).toBe('healthy');
      expect(result.mcplocal.uptime).toBeGreaterThanOrEqual(0);
    });

    it('reports null llmProvider when none registered', async () => {
      const monitor = new TieredHealthMonitor({
        mcpdClient: null,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      expect(result.mcplocal.llmProvider).toBeNull();
    });

    it('reports active llmProvider name when one is registered', async () => {
      const provider = mockLlmProvider('openai');
      providerRegistry.register(provider);

      const monitor = new TieredHealthMonitor({
        mcpdClient: null,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      expect(result.mcplocal.llmProvider).toBe('openai');
    });

    it('reports the currently active provider when multiple registered', async () => {
      providerRegistry.register(mockLlmProvider('openai'));
      providerRegistry.register(mockLlmProvider('anthropic'));
      providerRegistry.setActive('anthropic');

      const monitor = new TieredHealthMonitor({
        mcpdClient: null,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      expect(result.mcplocal.llmProvider).toBe('anthropic');
    });
  });

  describe('mcpd health', () => {
    it('reports connected when mcpd /health responds successfully', async () => {
      const client = mockMcpdClient();

      const monitor = new TieredHealthMonitor({
        mcpdClient: client,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      expect(result.mcpd.status).toBe('connected');
      expect(result.mcpd.url).toBe('http://localhost:3100');
    });

    it('reports disconnected when mcpd /health throws', async () => {
      const client = mockMcpdClient({ getFails: true });

      const monitor = new TieredHealthMonitor({
        mcpdClient: client,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      expect(result.mcpd.status).toBe('disconnected');
      expect(result.mcpd.url).toBe('http://localhost:3100');
    });

    it('reports disconnected when mcpdClient is null', async () => {
      const monitor = new TieredHealthMonitor({
        mcpdClient: null,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      expect(result.mcpd.status).toBe('disconnected');
      expect(result.mcpd.url).toBe('http://localhost:3100');
    });

    it('includes the configured mcpd URL in the response', async () => {
      const monitor = new TieredHealthMonitor({
        mcpdClient: null,
        providerRegistry,
        mcpdUrl: 'http://custom-host:9999',
      });

      const result = await monitor.checkHealth();

      expect(result.mcpd.url).toBe('http://custom-host:9999');
    });
  });

  describe('instances', () => {
    it('returns instances from mcpd /instances endpoint', async () => {
      const client = mockMcpdClient({
        instancesResult: {
          instances: [
            { name: 'slack', status: 'running' },
            { name: 'github', status: 'stopped' },
          ],
        },
      });

      const monitor = new TieredHealthMonitor({
        mcpdClient: client,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      expect(result.instances).toHaveLength(2);
      expect(result.instances[0]).toEqual({ name: 'slack', status: 'running' });
      expect(result.instances[1]).toEqual({ name: 'github', status: 'stopped' });
    });

    it('returns empty array when mcpdClient is null', async () => {
      const monitor = new TieredHealthMonitor({
        mcpdClient: null,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      expect(result.instances).toEqual([]);
    });

    it('returns empty array when /instances request fails', async () => {
      const client = mockMcpdClient({ instancesFails: true });

      const monitor = new TieredHealthMonitor({
        mcpdClient: client,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      expect(result.instances).toEqual([]);
    });

    it('returns empty array when mcpd has no instances', async () => {
      const client = mockMcpdClient({
        instancesResult: { instances: [] },
      });

      const monitor = new TieredHealthMonitor({
        mcpdClient: client,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      expect(result.instances).toEqual([]);
    });
  });

  describe('full integration', () => {
    it('returns complete tiered status with all sections', async () => {
      providerRegistry.register(mockLlmProvider('openai'));

      const client = mockMcpdClient({
        instancesResult: {
          instances: [
            { name: 'slack', status: 'running' },
          ],
        },
      });

      const monitor = new TieredHealthMonitor({
        mcpdClient: client,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      // Verify structure
      expect(result).toHaveProperty('mcplocal');
      expect(result).toHaveProperty('mcpd');
      expect(result).toHaveProperty('instances');

      // mcplocal
      expect(result.mcplocal.status).toBe('healthy');
      expect(typeof result.mcplocal.uptime).toBe('number');
      expect(result.mcplocal.llmProvider).toBe('openai');

      // mcpd
      expect(result.mcpd.status).toBe('connected');

      // instances
      expect(result.instances).toHaveLength(1);
      expect(result.instances[0]?.name).toBe('slack');
    });

    it('handles degraded scenario: no mcpd, no provider', async () => {
      const monitor = new TieredHealthMonitor({
        mcpdClient: null,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      expect(result.mcplocal.status).toBe('healthy');
      expect(result.mcplocal.llmProvider).toBeNull();
      expect(result.mcpd.status).toBe('disconnected');
      expect(result.instances).toEqual([]);
    });

    it('handles mcpd connected but instances endpoint failing', async () => {
      const client = mockMcpdClient({ instancesFails: true });

      const monitor = new TieredHealthMonitor({
        mcpdClient: client,
        providerRegistry,
        mcpdUrl: 'http://localhost:3100',
      });

      const result = await monitor.checkHealth();

      expect(result.mcpd.status).toBe('connected');
      expect(result.instances).toEqual([]);
    });
  });
});
