import { describe, it, expect, beforeEach } from 'vitest';
import { Registry } from 'prom-client';
import { createGatewayMetrics } from './gateway-metrics.js';

describe('metrics/gateway-metrics', () => {
  let reg: Registry;

  beforeEach(() => {
    reg = new Registry();
  });

  it('registers all 11 metrics under the supplied prefix', async () => {
    createGatewayMetrics(reg, 'aao_gateway_t');
    const out = await reg.metrics();
    for (const name of [
      'aao_gateway_t_requests_total',
      'aao_gateway_t_request_duration_seconds',
      'aao_gateway_t_tokens_total',
      'aao_gateway_t_backend_busy_slots',
      'aao_gateway_t_backend_total_slots',
      'aao_gateway_t_backend_online',
      'aao_gateway_t_cache_hit_total',
      'aao_gateway_t_cache_miss_total',
      'aao_gateway_t_virtual_key_budget_used_ratio',
      'aao_gateway_t_rate_limit_rejections_total',
      'aao_gateway_t_active_streams',
    ]) {
      expect(out).toContain(name);
    }
  });

  it('counters emit a series per unique label set', async () => {
    const m = createGatewayMetrics(reg, 'aao_gateway_c');
    m.requestsTotal.inc({ team: 'alpha', backend: 'gpu-a', model: 'qwen3:8b', status: 'success' }, 3);
    m.requestsTotal.inc({ team: 'alpha', backend: 'gpu-a', model: 'qwen3:8b', status: 'success' }, 2);
    m.requestsTotal.inc({ team: 'bravo', backend: 'gpu-b', model: 'qwen3:8b', status: 'success' }, 1);
    const out = await reg.metrics();
    // 3 + 2 = 5 on the alpha series
    expect(out).toMatch(/aao_gateway_c_requests_total\{[^}]*team="alpha"[^}]*\} 5/);
    expect(out).toMatch(/aao_gateway_c_requests_total\{[^}]*team="bravo"[^}]*\} 1/);
  });

  it('histogram observe records a single sample', async () => {
    const m = createGatewayMetrics(reg, 'aao_gateway_h');
    m.requestDurationSeconds.observe(
      { team: 'alpha', backend: 'gpu-a', model: 'qwen3:8b', status: 'success' },
      0.42,
    );
    const out = await reg.metrics();
    expect(out).toContain('aao_gateway_h_request_duration_seconds_count{');
    expect(out).toMatch(/aao_gateway_h_request_duration_seconds_sum\{[^}]+\} 0\.42/);
  });

  it('gauge set + reset returns latest value', async () => {
    const m = createGatewayMetrics(reg, 'aao_gateway_g');
    m.backendBusySlots.set({ backend: 'gpu-a' }, 4);
    m.backendBusySlots.set({ backend: 'gpu-a' }, 7);
    const out = await reg.metrics();
    expect(out).toMatch(/aao_gateway_g_backend_busy_slots\{backend="gpu-a"\} 7/);
  });

  it('activeStreams gauge has no labels', async () => {
    const m = createGatewayMetrics(reg, 'aao_gateway_s');
    m.activeStreams.set(5);
    const out = await reg.metrics();
    expect(out).toContain('aao_gateway_s_active_streams 5');
  });

  it('resetMetrics zeroes counters without unregistering', async () => {
    const m = createGatewayMetrics(reg, 'aao_gateway_r');
    m.cacheHitTotal.inc({ cache: 'key' }, 10);
    reg.resetMetrics();
    const out = await reg.metrics();
    expect(out).toContain('aao_gateway_r_cache_hit_total');
    // No data line after reset (counter not yet observed post-reset)
    expect(out).not.toMatch(/aao_gateway_r_cache_hit_total\{cache="key"\} 10/);
  });

  it('is idempotent on (registry, prefix) — returns the cached handle, does NOT throw', () => {
    // CRITICAL-1 fix: prom-client throws on duplicate metric name
    // registration, but createGatewayMetrics is memoized so callers
    // (e.g. same-process gateway bounces with a shared bridge
    // Registry) can safely call it again. The cache is per (registry,
    // prefix) so a different prefix on the same registry still
    // registers fresh counters.
    const first = createGatewayMetrics(reg, 'aao_gateway_dup');
    expect(() => createGatewayMetrics(reg, 'aao_gateway_dup')).not.toThrow();
    const second = createGatewayMetrics(reg, 'aao_gateway_dup');
    expect(second).toBe(first); // same handle, no fresh Counter instances
    // Different prefix → fresh registration, no cache hit
    const other = createGatewayMetrics(reg, 'aao_gateway_other');
    expect(other).not.toBe(first);
  });
});
