import { type Counter, type Gauge, type Histogram } from 'prom-client';
import { MetricsService } from '../metrics.service';

describe('MetricsService', () => {
  let service: MetricsService;
  let mockListingsCreatedCounter: { inc: ReturnType<typeof vi.fn> };
  let mockPaymentsProcessedCounter: { inc: ReturnType<typeof vi.fn> };
  let mockActiveSubscriptionsGauge: { set: ReturnType<typeof vi.fn> };
  let mockSearchQueriesCounter: { inc: ReturnType<typeof vi.fn> };
  let mockRequestDurationHistogram: { observe: ReturnType<typeof vi.fn> };
  let mockHttpRequestsCounter: { inc: ReturnType<typeof vi.fn> };
  let mockWsConnectedClientsGauge: {
    inc: ReturnType<typeof vi.fn>;
    set: ReturnType<typeof vi.fn>;
  };
  let mockWsMessagesCounter: { inc: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    mockListingsCreatedCounter = { inc: vi.fn() };
    mockPaymentsProcessedCounter = { inc: vi.fn() };
    mockActiveSubscriptionsGauge = { set: vi.fn() };
    mockSearchQueriesCounter = { inc: vi.fn() };
    mockRequestDurationHistogram = { observe: vi.fn() };
    mockHttpRequestsCounter = { inc: vi.fn() };
    mockWsConnectedClientsGauge = { inc: vi.fn(), set: vi.fn() };
    mockWsMessagesCounter = { inc: vi.fn() };

    service = new MetricsService(
      mockListingsCreatedCounter as unknown as Counter,
      mockPaymentsProcessedCounter as unknown as Counter,
      mockActiveSubscriptionsGauge as unknown as Gauge,
      mockSearchQueriesCounter as unknown as Counter,
      mockRequestDurationHistogram as unknown as Histogram,
      mockHttpRequestsCounter as unknown as Counter,
      mockWsConnectedClientsGauge as unknown as Gauge,
      mockWsMessagesCounter as unknown as Counter,
    );
  });

  it('recordListingCreated increments listingsCreatedCounter with the given category', () => {
    service.recordListingCreated('apartment');

    expect(mockListingsCreatedCounter.inc).toHaveBeenCalledOnce();
    expect(mockListingsCreatedCounter.inc).toHaveBeenCalledWith({ category: 'apartment' });
  });

  it('recordPaymentProcessed increments paymentsProcessedCounter with status and method', () => {
    service.recordPaymentProcessed('success', 'vnpay');

    expect(mockPaymentsProcessedCounter.inc).toHaveBeenCalledOnce();
    expect(mockPaymentsProcessedCounter.inc).toHaveBeenCalledWith({
      status: 'success',
      method: 'vnpay',
    });
  });

  it('setActiveSubscriptions sets the gauge with plan and count', () => {
    service.setActiveSubscriptions('pro', 42);

    expect(mockActiveSubscriptionsGauge.set).toHaveBeenCalledOnce();
    expect(mockActiveSubscriptionsGauge.set).toHaveBeenCalledWith({ plan: 'pro' }, 42);
  });

  it('recordSearchQuery increments searchQueriesCounter with collection and type', () => {
    service.recordSearchQuery('properties', 'geo');

    expect(mockSearchQueriesCounter.inc).toHaveBeenCalledOnce();
    expect(mockSearchQueriesCounter.inc).toHaveBeenCalledWith({
      collection: 'properties',
      type: 'geo',
    });
  });

  it('recordHttpRequest observes requestDurationHistogram with correct labels and duration', () => {
    service.recordHttpRequest('GET', '/api/listings', 200, 0.123);

    expect(mockRequestDurationHistogram.observe).toHaveBeenCalledOnce();
    expect(mockRequestDurationHistogram.observe).toHaveBeenCalledWith(
      { method: 'GET', route: '/api/listings', status_code: '200' },
      0.123,
    );
  });

  it('recordHttpRequest increments httpRequestsCounter with correct labels', () => {
    service.recordHttpRequest('POST', '/api/payments', 201, 0.456);

    expect(mockHttpRequestsCounter.inc).toHaveBeenCalledOnce();
    expect(mockHttpRequestsCounter.inc).toHaveBeenCalledWith({
      method: 'POST',
      route: '/api/payments',
      status_code: '201',
    });
  });

  it('recordHttpRequest calls both histogram and counter with the same labels', () => {
    service.recordHttpRequest('DELETE', '/api/listings/1', 204, 0.05);

    const expectedLabels = { method: 'DELETE', route: '/api/listings/1', status_code: '204' };
    expect(mockRequestDurationHistogram.observe).toHaveBeenCalledWith(expectedLabels, 0.05);
    expect(mockHttpRequestsCounter.inc).toHaveBeenCalledWith(expectedLabels);
  });

  it('recordHttpRequest converts numeric statusCode to string in labels', () => {
    service.recordHttpRequest('GET', '/api/health', 503, 0.001);

    expect(mockRequestDurationHistogram.observe).toHaveBeenCalledWith(
      expect.objectContaining({ status_code: '503' }),
      0.001,
    );
    expect(mockHttpRequestsCounter.inc).toHaveBeenCalledWith(
      expect.objectContaining({ status_code: '503' }),
    );
  });

  it('recordWsConnection increments the connected-clients gauge with +1 on connect', () => {
    service.recordWsConnection('/notifications', 1);

    expect(mockWsConnectedClientsGauge.inc).toHaveBeenCalledWith(
      { namespace: '/notifications' },
      1,
    );
  });

  it('recordWsConnection decrements the connected-clients gauge with -1 on disconnect', () => {
    service.recordWsConnection('/notifications', -1);

    expect(mockWsConnectedClientsGauge.inc).toHaveBeenCalledWith(
      { namespace: '/notifications' },
      -1,
    );
  });

  it('setWsConnectedClients sets the gauge for a namespace', () => {
    service.setWsConnectedClients('/notifications', 0);

    expect(mockWsConnectedClientsGauge.set).toHaveBeenCalledWith(
      { namespace: '/notifications' },
      0,
    );
  });

  it('recordWsMessage increments the messages counter with namespace/event/direction', () => {
    service.recordWsMessage('/notifications', 'notification:new', 'out');

    expect(mockWsMessagesCounter.inc).toHaveBeenCalledWith({
      namespace: '/notifications',
      event: 'notification:new',
      direction: 'out',
    });
  });
});
