import { describe, it, expect } from 'vitest';
import express from 'express';
import request from 'supertest';
import { Registry } from 'prom-client';
import { createMetricsHandler } from './http-handler.js';
import { createGatewayMetrics } from './gateway-metrics.js';

describe('metrics/http-handler', () => {
  function makeApp(): { app: express.Express; reg: Registry } {
    const app = express();
    const reg = new Registry();
    createGatewayMetrics(reg, 'aao_gateway_h');
    // Default (no auth opts) → localhost-only allowlist; supertest
    // connects from 127.0.0.1 so it passes.
    app.get('/metrics', createMetricsHandler(reg));
    return { app, reg };
  }

  it('responds 200 with text/plain version=0.0.4', async () => {
    const { app } = makeApp();
    const res = await request(app).get('/metrics');
    expect(res.status).toBe(200);
    expect(res.headers['content-type']).toMatch(/^text\/plain.*version=0\.0\.4/);
  });

  it('payload contains registered metric names', async () => {
    const { app } = makeApp();
    const res = await request(app).get('/metrics');
    expect(res.text).toContain('aao_gateway_h_requests_total');
  });

  it('returns 500 with text/plain when render throws', async () => {
    const reg = {
      contentType: 'text/plain',
      // Force a rejection out of metrics()
      metrics: () => Promise.reject(new Error('boom')),
    } as unknown as Registry;
    const app = express();
    app.get('/metrics', createMetricsHandler(reg));
    const res = await request(app).get('/metrics');
    expect(res.status).toBe(500);
    expect(res.headers['content-type']).toMatch(/text\/plain/);
  });

  describe('auth gating (Phase 3b post-review)', () => {
    it('default config allows 127.0.0.1 (localhost)', async () => {
      const reg = new Registry();
      createGatewayMetrics(reg, 'aao_gateway_l1');
      const app = express();
      app.get('/metrics', createMetricsHandler(reg));
      const res = await request(app).get('/metrics');
      expect(res.status).toBe(200);
    });

    it('default config rejects a non-localhost client IP with 403', async () => {
      const reg = new Registry();
      createGatewayMetrics(reg, 'aao_gateway_l2');
      const app = express();
      // Simulate an external IP via a stub middleware before the
      // handler. Defining `req.ip` after Express assigned it requires
      // setting the underlying socket; we override the getter directly.
      app.use((req, _res, next) => {
        Object.defineProperty(req, 'ip', { value: '203.0.113.5', configurable: true });
        // Also override socket.remoteAddress so the fallback check
        // doesn't see the real supertest socket (127.0.0.1).
        Object.defineProperty(req, 'socket', {
          value: { remoteAddress: '203.0.113.5' },
          configurable: true,
        });
        next();
      });
      app.get('/metrics', createMetricsHandler(reg));
      const res = await request(app).get('/metrics');
      expect(res.status).toBe(403);
    });

    it('bearer token: 200 on correct Authorization header', async () => {
      const reg = new Registry();
      createGatewayMetrics(reg, 'aao_gateway_t1');
      const app = express();
      app.get('/metrics', createMetricsHandler(reg, { bearerToken: 'sk-metrics-secret' }));
      const res = await request(app).get('/metrics').set('Authorization', 'Bearer sk-metrics-secret');
      expect(res.status).toBe(200);
    });

    it('bearer token: 401 with WWW-Authenticate when missing/wrong', async () => {
      const reg = new Registry();
      createGatewayMetrics(reg, 'aao_gateway_t2');
      const app = express();
      app.get('/metrics', createMetricsHandler(reg, { bearerToken: 'sk-metrics-secret' }));
      const r1 = await request(app).get('/metrics');
      expect(r1.status).toBe(401);
      expect(r1.headers['www-authenticate']).toMatch(/Bearer/);
      const r2 = await request(app).get('/metrics').set('Authorization', 'Bearer wrong-token');
      expect(r2.status).toBe(401);
    });

    it('bearer token wins over IP allowlist (correct token from external IP → 200)', async () => {
      const reg = new Registry();
      createGatewayMetrics(reg, 'aao_gateway_t3');
      const app = express();
      app.use((req, _res, next) => {
        Object.defineProperty(req, 'ip', { value: '203.0.113.5', configurable: true });
        // Also override socket.remoteAddress so the fallback check
        // doesn't see the real supertest socket (127.0.0.1).
        Object.defineProperty(req, 'socket', {
          value: { remoteAddress: '203.0.113.5' },
          configurable: true,
        });
        next();
      });
      app.get('/metrics', createMetricsHandler(reg, {
        bearerToken: 'tok',
        allowedHosts: ['127.0.0.1'],
      }));
      const res = await request(app).get('/metrics').set('Authorization', 'Bearer tok');
      expect(res.status).toBe(200);
    });

    it('allowedHosts containing 0.0.0.0 disables IP checks', async () => {
      const reg = new Registry();
      createGatewayMetrics(reg, 'aao_gateway_h0');
      const app = express();
      app.use((req, _res, next) => {
        Object.defineProperty(req, 'ip', { value: '198.51.100.7', configurable: true });
        Object.defineProperty(req, 'socket', {
          value: { remoteAddress: '198.51.100.7' },
          configurable: true,
        });
        next();
      });
      app.get('/metrics', createMetricsHandler(reg, { allowedHosts: ['0.0.0.0'] }));
      const res = await request(app).get('/metrics');
      expect(res.status).toBe(200);
    });

    it('allowedHosts custom list accepts listed IPs and rejects others', async () => {
      const reg = new Registry();
      createGatewayMetrics(reg, 'aao_gateway_h2');
      // Listed IP → 200
      const appOk = express();
      appOk.use((req, _res, next) => {
        Object.defineProperty(req, 'ip', { value: '10.0.0.5', configurable: true });
        Object.defineProperty(req, 'socket', {
          value: { remoteAddress: '10.0.0.5' },
          configurable: true,
        });
        next();
      });
      appOk.get('/metrics', createMetricsHandler(reg, { allowedHosts: ['10.0.0.5'] }));
      const r1 = await request(appOk).get('/metrics');
      expect(r1.status).toBe(200);
      // Unlisted IP → 403
      const reg2 = new Registry();
      createGatewayMetrics(reg2, 'aao_gateway_h3');
      const appBlocked = express();
      appBlocked.use((req, _res, next) => {
        Object.defineProperty(req, 'ip', { value: '10.0.0.99', configurable: true });
        Object.defineProperty(req, 'socket', {
          value: { remoteAddress: '10.0.0.99' },
          configurable: true,
        });
        next();
      });
      appBlocked.get('/metrics', createMetricsHandler(reg2, { allowedHosts: ['10.0.0.5'] }));
      const r2 = await request(appBlocked).get('/metrics');
      expect(r2.status).toBe(403);
    });
  });
});
