/**
 * Unit tests for src/metrics/registry.ts
 *
 * Verifies that all Prometheus metrics are registered on the shared
 * metricsRegistry (not the default global registry), have the correct
 * names, and carry the correct label names.
 */

import {
  metricsRegistry,
  tokensIssuedTotal,
  agentsRegisteredTotal,
  httpRequestsTotal,
  httpRequestDurationSeconds,
  dbQueryDurationSeconds,
  redisCommandDurationSeconds,
  credentialsExpiringSoonTotal,
  auditChainIntegrity,
  rateLimitHitsTotal,
  dbPoolActiveConnections,
  dbPoolWaitingRequests,
  tenantApiCallsTotal,
  billingLimitRejectionsTotal,
} from '../../../src/metrics/registry';

describe('metricsRegistry', () => {
  // ──────────────────────────────────────────────────────────────────
  // Registry isolation
  // ──────────────────────────────────────────────────────────────────
  it('uses a non-default registry instance', async () => {
    // prom-client default registry is accessed via Registry.default or
    // by calling register.metrics(). The shared registry must NOT be
    // the same reference as the default one.
    const { register } = await import('prom-client');
    expect(metricsRegistry).not.toBe(register);
  });

  it('contains exactly 19 metric entries', async () => {
    const entries = await metricsRegistry.getMetricsAsJSON();
    expect(entries).toHaveLength(19);
  });

  // ──────────────────────────────────────────────────────────────────
  // Metric names
  // ──────────────────────────────────────────────────────────────────
  it.each([
    'agentidp_tokens_issued_total',
    'agentidp_agents_registered_total',
    'agentidp_http_requests_total',
    'agentidp_http_request_duration_seconds',
    'agentidp_db_query_duration_seconds',
    'agentidp_redis_command_duration_seconds',
    'agentidp_webhook_dead_letters_total',
    'agentidp_credentials_expiring_soon_total',
    'agentidp_audit_chain_integrity',
    'agentidp_rate_limit_hits_total',
    'agentidp_db_pool_active_connections',
    'agentidp_db_pool_waiting_requests',
    'agentidp_tenant_api_calls_total',
    'agentidp_billing_limit_rejections_total',
  ])('registers metric "%s"', async (metricName) => {
    const entries = await metricsRegistry.getMetricsAsJSON();
    const names = entries.map((e) => e.name);
    expect(names).toContain(metricName);
  });

  // ──────────────────────────────────────────────────────────────────
  // Label names per metric
  // ──────────────────────────────────────────────────────────────────
  describe('tokensIssuedTotal', () => {
    it('has name agentidp_tokens_issued_total', () => {
      // Access the internal name via the metric object
      const metric = tokensIssuedTotal as unknown as { name: string };
      expect(metric.name).toBe('agentidp_tokens_issued_total');
    });

    it('has label "scope"', async () => {
      const entries = await metricsRegistry.getMetricsAsJSON();
      const entry = entries.find((e) => e.name === 'agentidp_tokens_issued_total');
      expect(entry).toBeDefined();
      // Counter with no observations has an empty values array but the metric exists
      expect(entry!.type).toBe('counter');
    });
  });

  describe('agentsRegisteredTotal', () => {
    it('has name agentidp_agents_registered_total', () => {
      const metric = agentsRegisteredTotal as unknown as { name: string };
      expect(metric.name).toBe('agentidp_agents_registered_total');
    });
  });

  describe('httpRequestsTotal', () => {
    it('has name agentidp_http_requests_total', () => {
      const metric = httpRequestsTotal as unknown as { name: string };
      expect(metric.name).toBe('agentidp_http_requests_total');
    });

    it('increments with method, route, status_code labels without throwing', () => {
      expect(() =>
        httpRequestsTotal.inc({ method: 'GET', route: '/test', status_code: '200' }),
      ).not.toThrow();
    });
  });

  describe('httpRequestDurationSeconds', () => {
    it('has name agentidp_http_request_duration_seconds', () => {
      const metric = httpRequestDurationSeconds as unknown as { name: string };
      expect(metric.name).toBe('agentidp_http_request_duration_seconds');
    });

    it('observes with method, route, status_code labels without throwing', () => {
      expect(() =>
        httpRequestDurationSeconds.observe({ method: 'GET', route: '/test', status_code: '200' }, 0.05),
      ).not.toThrow();
    });
  });

  describe('dbQueryDurationSeconds', () => {
    it('has name agentidp_db_query_duration_seconds', () => {
      const metric = dbQueryDurationSeconds as unknown as { name: string };
      expect(metric.name).toBe('agentidp_db_query_duration_seconds');
    });

    it('observes with operation label without throwing', () => {
      expect(() =>
        dbQueryDurationSeconds.observe({ operation: 'query' }, 0.002),
      ).not.toThrow();
    });
  });

  describe('redisCommandDurationSeconds', () => {
    it('has name agentidp_redis_command_duration_seconds', () => {
      const metric = redisCommandDurationSeconds as unknown as { name: string };
      expect(metric.name).toBe('agentidp_redis_command_duration_seconds');
    });

    it('observes with command label without throwing', () => {
      expect(() =>
        redisCommandDurationSeconds.observe({ command: 'get' }, 0.001),
      ).not.toThrow();
    });
  });

  describe('credentialsExpiringSoonTotal', () => {
    it('has name agentidp_credentials_expiring_soon_total', () => {
      const metric = credentialsExpiringSoonTotal as unknown as { name: string };
      expect(metric.name).toBe('agentidp_credentials_expiring_soon_total');
    });

    it('increments with agent_id label without throwing', () => {
      expect(() =>
        credentialsExpiringSoonTotal.inc({ agent_id: 'agent-test-001' }),
      ).not.toThrow();
    });
  });

  describe('auditChainIntegrity', () => {
    it('has name agentidp_audit_chain_integrity', () => {
      const metric = auditChainIntegrity as unknown as { name: string };
      expect(metric.name).toBe('agentidp_audit_chain_integrity');
    });

    it('can be set to 1 (passing) without throwing', () => {
      expect(() => auditChainIntegrity.set(1)).not.toThrow();
    });

    it('can be set to 0 (failing) without throwing', () => {
      expect(() => auditChainIntegrity.set(0)).not.toThrow();
    });
  });

  describe('rateLimitHitsTotal', () => {
    it('has name agentidp_rate_limit_hits_total', () => {
      const metric = rateLimitHitsTotal as unknown as { name: string };
      expect(metric.name).toBe('agentidp_rate_limit_hits_total');
    });

    it('increments with endpoint label without throwing', () => {
      expect(() =>
        rateLimitHitsTotal.inc({ endpoint: '/api/v1/agents' }),
      ).not.toThrow();
    });
  });

  describe('dbPoolActiveConnections', () => {
    it('has name agentidp_db_pool_active_connections', () => {
      const metric = dbPoolActiveConnections as unknown as { name: string };
      expect(metric.name).toBe('agentidp_db_pool_active_connections');
    });

    it('can be set without throwing', () => {
      expect(() => dbPoolActiveConnections.set(5)).not.toThrow();
    });
  });

  describe('dbPoolWaitingRequests', () => {
    it('has name agentidp_db_pool_waiting_requests', () => {
      const metric = dbPoolWaitingRequests as unknown as { name: string };
      expect(metric.name).toBe('agentidp_db_pool_waiting_requests');
    });

    it('can be set without throwing', () => {
      expect(() => dbPoolWaitingRequests.set(2)).not.toThrow();
    });
  });

  describe('tenantApiCallsTotal', () => {
    it('has name agentidp_tenant_api_calls_total', () => {
      const metric = tenantApiCallsTotal as unknown as { name: string };
      expect(metric.name).toBe('agentidp_tenant_api_calls_total');
    });

    it('increments with tenant_id label without throwing', () => {
      expect(() =>
        tenantApiCallsTotal.inc({ tenant_id: 'org-test-001' }),
      ).not.toThrow();
    });
  });

  describe('billingLimitRejectionsTotal', () => {
    it('has name agentidp_billing_limit_rejections_total', () => {
      const metric = billingLimitRejectionsTotal as unknown as { name: string };
      expect(metric.name).toBe('agentidp_billing_limit_rejections_total');
    });

    it('increments with tenant_id and limit_type labels without throwing', () => {
      expect(() =>
        billingLimitRejectionsTotal.inc({ tenant_id: 'org-test-001', limit_type: 'agent_limit' }),
      ).not.toThrow();
      expect(() =>
        billingLimitRejectionsTotal.inc({ tenant_id: 'org-test-002', limit_type: 'api_limit' }),
      ).not.toThrow();
    });
  });
});
