/**
 * Smoke tests for metrics.js
 * Verifies the Metrics singleton exposes the expected interface, accumulates
 * request/error/business counters, normalizes paths, formats uptime, and resets.
 *
 * The module exports a singleton instance, so we import it once and mutate its
 * state in beforeEach.
 */

const metrics = require('../src/monitoring/metrics');

describe('Metrics (singleton)', () => {
  beforeEach(() => {
    metrics.reset();
  });

  test('exposes the documented public API', () => {
    expect(typeof metrics.recordRequest).toBe('function');
    expect(typeof metrics.recordError).toBe('function');
    expect(typeof metrics.recordBusinessEvent).toBe('function');
    expect(typeof metrics.normalizePath).toBe('function');
    expect(typeof metrics.getSummary).toBe('function');
    expect(typeof metrics.formatUptime).toBe('function');
    expect(typeof metrics.reset).toBe('function');
  });

  describe('recordRequest', () => {
    test('increments total request count', () => {
      metrics.recordRequest('GET', '/api/services', 200, 12);
      metrics.recordRequest('GET', '/api/services', 200, 8);
      expect(metrics.requests.total).toBe(2);
    });

    test('aggregates by status code', () => {
      metrics.recordRequest('GET', '/a', 200, 5);
      metrics.recordRequest('GET', '/b', 200, 5);
      metrics.recordRequest('POST', '/c', 500, 5);
      expect(metrics.requests.byStatus[200]).toBe(2);
      expect(metrics.requests.byStatus[500]).toBe(1);
    });

    test('aggregates by HTTP method', () => {
      metrics.recordRequest('GET', '/a', 200, 1);
      metrics.recordRequest('GET', '/b', 200, 1);
      metrics.recordRequest('DELETE', '/c', 200, 1);
      expect(metrics.requests.byMethod.GET).toBe(2);
      expect(metrics.requests.byMethod.DELETE).toBe(1);
    });

    test('aggregates by normalized path with totalDuration', () => {
      // Real-looking UUID and long hex hash; both should normalize to /:id
      const id1 = '550e8400-e29b-41d4-a716-446655440000';
      const id2 = 'abcdef0123456789abcdef0123456789';
      metrics.recordRequest('GET', `/api/services/${id1}`, 200, 10);
      metrics.recordRequest('GET', `/api/services/${id2}`, 200, 20);
      const entry = metrics.requests.byPath['/api/services/:id'];
      expect(entry).toBeDefined();
      expect(entry.count).toBe(2);
      expect(entry.totalDuration).toBe(30);
    });
  });

  describe('recordError', () => {
    test('increments total error count and per-type counts', () => {
      metrics.recordError('ValidationError');
      metrics.recordError('ValidationError');
      metrics.recordError('DockerError');
      expect(metrics.errors.total).toBe(3);
      expect(metrics.errors.byType.ValidationError).toBe(2);
      expect(metrics.errors.byType.DockerError).toBe(1);
    });
  });

  describe('recordBusinessEvent', () => {
    test('increments known business counters', () => {
      metrics.recordBusinessEvent('containersDeployed');
      metrics.recordBusinessEvent('containersDeployed');
      metrics.recordBusinessEvent('dnsRecordsCreated');
      expect(metrics.business.containersDeployed).toBe(2);
      expect(metrics.business.dnsRecordsCreated).toBe(1);
    });

    test('ignores unknown event types without throwing', () => {
      expect(() => metrics.recordBusinessEvent('not-a-real-event')).not.toThrow();
      expect(metrics.business.notARealEvent).toBeUndefined();
    });
  });

  describe('normalizePath', () => {
    test('replaces UUIDs with /:id', () => {
      const normalized = metrics.normalizePath('/api/services/550e8400-e29b-41d4-a716-446655440000');
      expect(normalized).toBe('/api/services/:id');
    });

    test('replaces long hex segments with /:id', () => {
      expect(metrics.normalizePath('/api/containers/abc123def4567890'))
        .toBe('/api/containers/:id');
    });

    test('replaces numeric path segments with /:n', () => {
      expect(metrics.normalizePath('/api/services/42/edit'))
        .toBe('/api/services/:n/edit');
    });

    test('leaves static paths unchanged', () => {
      expect(metrics.normalizePath('/api/health')).toBe('/api/health');
      expect(metrics.normalizePath('/')).toBe('/');
    });
  });

  describe('getSummary', () => {
    test('returns an object with the documented top-level shape', () => {
      const summary = metrics.getSummary();
      expect(summary).toHaveProperty('uptime');
      expect(summary.uptime).toHaveProperty('ms');
      expect(summary.uptime).toHaveProperty('human');
      expect(summary).toHaveProperty('requests');
      expect(summary.requests).toHaveProperty('total');
      expect(summary.requests).toHaveProperty('perSecond');
      expect(summary.requests).toHaveProperty('byStatus');
      expect(summary.requests).toHaveProperty('byMethod');
      expect(summary.requests).toHaveProperty('topEndpoints');
      expect(Array.isArray(summary.requests.topEndpoints)).toBe(true);
      expect(summary).toHaveProperty('errors');
      expect(summary.errors).toHaveProperty('total');
      expect(summary.errors).toHaveProperty('rate');
      expect(summary.errors).toHaveProperty('byType');
      expect(summary).toHaveProperty('business');
      expect(summary).toHaveProperty('process');
      expect(summary.process).toHaveProperty('pid');
    });

    test('reflects recorded activity', () => {
      metrics.recordRequest('GET', '/api/foo', 200, 10);
      metrics.recordError('BoomError');
      const summary = metrics.getSummary();
      expect(summary.requests.total).toBe(1);
      expect(summary.requests.byStatus[200]).toBe(1);
      expect(summary.errors.total).toBe(1);
      expect(summary.errors.byType.BoomError).toBe(1);
      // 1 error / 1 request = 100% error rate
      expect(summary.errors.rate).toBe(100);
    });

    test('topEndpoints is sorted by count descending and capped at 15', () => {
      // /a gets 3 hits, /b gets 1, /c gets 2
      metrics.recordRequest('GET', '/a', 200, 1);
      metrics.recordRequest('GET', '/a', 200, 2);
      metrics.recordRequest('GET', '/a', 200, 3);
      metrics.recordRequest('GET', '/b', 200, 1);
      metrics.recordRequest('GET', '/c', 200, 1);
      metrics.recordRequest('GET', '/c', 200, 2);
      const top = metrics.getSummary().requests.topEndpoints;
      expect(top[0].path).toBe('/a');
      expect(top[0].count).toBe(3);
      expect(top[0].avgMs).toBe(2);
    });
  });

  describe('formatUptime', () => {
    test('formats seconds-only when under a minute', () => {
      expect(metrics.formatUptime(0)).toBe('0s');
      expect(metrics.formatUptime(45)).toBe('45s');
    });

    test('formats minutes and seconds when under an hour', () => {
      expect(metrics.formatUptime(60)).toBe('1m 0s');
      expect(metrics.formatUptime(125)).toBe('2m 5s');
    });

    test('formats hours/minutes/seconds when under a day', () => {
      expect(metrics.formatUptime(3600)).toBe('1h 0m 0s');
      expect(metrics.formatUptime(3725)).toBe('1h 2m 5s');
    });

    test('formats days/hours/minutes when over a day', () => {
      expect(metrics.formatUptime(86400)).toBe('1d 0h 0m');
      // 1 day, 2 hours, 5 minutes, 0 seconds
      expect(metrics.formatUptime(86400 + 2 * 3600 + 5 * 60)).toBe('1d 2h 5m');
    });
  });

  describe('reset', () => {
    test('clears request counters and error counters', () => {
      metrics.recordRequest('GET', '/x', 200, 1);
      metrics.recordError('E');
      metrics.reset();
      expect(metrics.requests.total).toBe(0);
      expect(metrics.errors.total).toBe(0);
      expect(metrics.requests.byStatus).toEqual({});
      expect(metrics.requests.byMethod).toEqual({});
      expect(metrics.requests.byPath).toEqual({});
      expect(metrics.errors.byType).toEqual({});
    });

    test('resets startTime so uptime is small after reset', () => {
      const before = metrics.startTime;
      // Sleep a tick so Date.now() moves forward
      const start = Date.now();
      while (Date.now() - start < 5) {} // ~5ms busy-wait
      metrics.reset();
      expect(metrics.startTime).toBeGreaterThanOrEqual(before);
      const summary = metrics.getSummary();
      expect(summary.uptime.ms).toBeLessThan(5000);
    });
  });
});
