import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { checkService, generateHealthReport, type ServiceTarget } from '../index.js';

type FetchInput = Parameters<typeof globalThis.fetch>[0];
type MockFetchResult = Pick<Response, 'ok' | 'status' | 'json'>;

function mockFetch(handler: (input: FetchInput) => Promise<MockFetchResult>): void {
  globalThis.fetch = vi.fn(handler) as unknown as typeof globalThis.fetch;
}

describe('monitoring', () => {
  const originalFetch = globalThis.fetch;

  beforeEach(() => {
    vi.restoreAllMocks();
  });

  afterEach(() => {
    globalThis.fetch = originalFetch;
  });

  it('checkService returns healthy for 200', async () => {
    mockFetch(async () => ({
      ok: true,
      status: 200,
      json: async () => ({ status: 'ok' }),
    }));

    const res = await checkService({ name: 'svc', url: 'http://x', path: '/health' });
    expect(res.status).toBe('healthy');
    expect(res.details).toEqual({ status: 'ok' });
  });

  it('checkService returns unhealthy for non-2xx', async () => {
    mockFetch(async () => ({
      ok: false,
      status: 503,
      json: async () => ({}),
    }));

    const res = await checkService({ name: 'svc', url: 'http://x', path: '/health' });
    expect(res.status).toBe('unhealthy');
    expect(res.error).toContain('HTTP 503');
  });

  it('checkService returns unreachable on fetch error', async () => {
    mockFetch(async () => {
      throw new Error('network');
    });

    const res = await checkService({ name: 'svc', url: 'http://x', path: '/health' });
    expect(res.status).toBe('unreachable');
    expect(res.error).toContain('network');
  });

  it('generateHealthReport sets overall=down when all unreachable', async () => {
    mockFetch(async () => {
      throw new Error('network');
    });

    const services: ServiceTarget[] = [
      { name: 'a', url: 'http://a', path: '/health' },
      { name: 'b', url: 'http://b', path: '/health' },
    ];

    const report = await generateHealthReport(services, { timeoutMs: 10 });
    expect(report.overall).toBe('down');
    expect(report.summary.unreachable).toBe(2);
  });

  it('generateHealthReport sets overall=degraded when mixed', async () => {
    mockFetch(async url => {
      if (String(url).includes('good')) return { ok: true, status: 200, json: async () => ({}) };
      return { ok: false, status: 500, json: async () => ({}) };
    });

    const services: ServiceTarget[] = [
      { name: 'good', url: 'http://good', path: '/health' },
      { name: 'bad', url: 'http://bad', path: '/health' },
    ];

    const report = await generateHealthReport(services);
    expect(report.overall).toBe('degraded');
    expect(report.summary.healthy).toBe(1);
    expect(report.summary.unhealthy).toBe(1);
  });

  it('generateHealthReport sets overall=healthy when all healthy', async () => {
    mockFetch(async () => ({
      ok: true,
      status: 200,
      json: async () => ({}),
    }));

    const services: ServiceTarget[] = [
      { name: 'a', url: 'http://a', path: '/health' },
      { name: 'b', url: 'http://b', path: '/health' },
    ];

    const report = await generateHealthReport(services);
    expect(report.overall).toBe('healthy');
    expect(report.summary.healthy).toBe(2);
  });
});
