process.env.VYNDR_INTERNAL_KEY = 'health-test-key';

const mockPing = jest.fn();
jest.mock('../../src/utils/redis', () => ({
  getRedisClient: () => ({ ping: mockPing }),
  isDegraded: () => false,
  cacheGet: async () => null,
  cacheSet: async () => true,
  cacheDel: async () => true,
}));

const mockSupabaseLimit = jest.fn();
jest.mock('../../src/utils/supabase', () => ({
  getSupabaseServiceClient: () => ({
    from() {
      const proxy = {
        select() { return proxy; },
        limit() { return mockSupabaseLimit(); },
      };
      return proxy;
    },
  }),
}));

const mockAxiosGet = jest.fn();
jest.mock('axios', () => ({ get: (...args) => mockAxiosGet(...args), post: jest.fn() }));

// Adapter configured() helpers — fixed boolean returns are enough for the
// detailed branch assertions.
jest.mock('../../src/services/adapters/sharpApiAdapter', () => ({ configured: () => true }));
jest.mock('../../src/services/adapters/propOddsAdapter', () => ({ configured: () => false }));
jest.mock('../../src/services/adapters/parlayApiAdapter', () => ({ configured: () => true }));
jest.mock('../../src/services/adapters/oddsPapiAdapter', () => ({ configured: () => false }));
jest.mock('../../src/services/adapters/cfbdAdapter', () => ({ configured: () => false }));
jest.mock('../../src/services/adapters/openRouterAdapter', () => ({ configured: () => true }));

const app = require('../../src/app');
const http = require('http');

function call(path, headers = {}) {
  return new Promise((resolve) => {
    const server = app.listen(0, '127.0.0.1', () => {
      const port = server.address().port;
      const req = http.request({ host: '127.0.0.1', port, path, method: 'GET', headers }, (res) => {
        const chunks = [];
        res.on('data', (c) => chunks.push(c));
        res.on('end', () => {
          server.close();
          const raw = Buffer.concat(chunks).toString('utf8');
          let parsed; try { parsed = JSON.parse(raw); } catch { parsed = raw; }
          resolve({ status: res.statusCode, body: parsed });
        });
      });
      req.end();
    });
  });
}

beforeEach(() => {
  mockPing.mockReset();
  mockSupabaseLimit.mockReset();
  mockAxiosGet.mockReset();
});

describe('GET /api/health', () => {
  test('public response is minimal when no internal key', async () => {
    mockPing.mockResolvedValue('PONG');
    mockSupabaseLimit.mockResolvedValue({ error: null });
    const res = await call('/api/health');
    expect(res.status).toBe(200);
    expect(res.body).toEqual({ status: 'healthy' });
  });

  test('reports 503 when Redis is down', async () => {
    mockPing.mockRejectedValue(new Error('econnrefused'));
    mockSupabaseLimit.mockResolvedValue({ error: null });
    const res = await call('/api/health');
    expect(res.status).toBe(503);
    expect(res.body.status).toBe('degraded');
  });

  test('reports 503 when Supabase errors', async () => {
    mockPing.mockResolvedValue('PONG');
    mockSupabaseLimit.mockResolvedValue({ error: { message: 'bad' } });
    const res = await call('/api/health');
    expect(res.status).toBe(503);
  });

  test('detailed branch with valid internal key includes adapters + python', async () => {
    mockPing.mockResolvedValue('PONG');
    mockSupabaseLimit.mockResolvedValue({ error: null });
    mockAxiosGet.mockResolvedValue({ status: 200, data: { status: 'ok' } });
    const res = await call('/api/health', { 'x-vyndr-internal-key': 'health-test-key' });
    expect(res.status).toBe(200);
    expect(res.body.checks.python).toBe('ok');
    expect(res.body.checks.adapters).toMatchObject({
      sharpapi: true,
      propodds: false,
      openrouter: true,
    });
    expect(typeof res.body.uptime).toBe('number');
  });

  test('wrong internal key falls through to public response', async () => {
    mockPing.mockResolvedValue('PONG');
    mockSupabaseLimit.mockResolvedValue({ error: null });
    const res = await call('/api/health', { 'x-vyndr-internal-key': 'wrong' });
    expect(res.body).toEqual({ status: 'healthy' });
    expect(res.body.checks).toBeUndefined();
  });

  test('python down does not flip overall status (Python is optional)', async () => {
    mockPing.mockResolvedValue('PONG');
    mockSupabaseLimit.mockResolvedValue({ error: null });
    mockAxiosGet.mockRejectedValue(new Error('refused'));
    const res = await call('/api/health', { 'x-vyndr-internal-key': 'health-test-key' });
    expect(res.status).toBe(200);
    expect(res.body.checks.python).toBe('down');
  });
});
