/**
 * Unit tests for src/services/AnalyticsService.ts
 */

import { Pool, QueryResult } from 'pg';
import { AnalyticsService } from '../../../src/services/AnalyticsService';

// ── Mock pg Pool ──────────────────────────────────────────────────────────────

function makePool(queryFn: jest.Mock): Pool {
  return { query: queryFn } as unknown as Pool;
}

// ════════════════════════════════════════════════════════════════════════════
// AnalyticsService
// ════════════════════════════════════════════════════════════════════════════

describe('AnalyticsService', () => {
  beforeEach(() => jest.clearAllMocks());

  // ────────────────────────────────────────────────────────────────
  // recordEvent()
  // ────────────────────────────────────────────────────────────────

  describe('recordEvent()', () => {
    it('should call pool.query with the upsert SQL on success', async () => {
      const mockQuery = jest.fn().mockResolvedValue({ rows: [] } as unknown as QueryResult);
      const service = new AnalyticsService(makePool(mockQuery));

      await service.recordEvent('tenant-1', 'token_issued');

      expect(mockQuery).toHaveBeenCalledTimes(1);
      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining('INSERT INTO analytics_events'),
        ['tenant-1', 'token_issued'],
      );
      expect(mockQuery).toHaveBeenCalledWith(
        expect.stringContaining('ON CONFLICT'),
        expect.any(Array),
      );
    });

    it('should silently swallow errors — never rejects or throws', async () => {
      const mockQuery = jest.fn().mockRejectedValue(new Error('DB connection failed'));
      const service = new AnalyticsService(makePool(mockQuery));

      // Must not throw — fire-and-forget contract
      await expect(service.recordEvent('tenant-err', 'token_issued')).resolves.toBeUndefined();
    });
  });

  // ────────────────────────────────────────────────────────────────
  // getTokenTrend()
  // ────────────────────────────────────────────────────────────────

  describe('getTokenTrend()', () => {
    it('should cap days at 90 (MAX_TREND_DAYS)', async () => {
      const mockQuery = jest.fn().mockResolvedValue({
        rows: [{ date: '2026-04-04', count: '5' }],
      } as unknown as QueryResult);

      const service = new AnalyticsService(makePool(mockQuery));
      await service.getTokenTrend('tenant-1', 200);

      // The first positional parameter passed to pool.query should be 90, not 200
      const callArgs = mockQuery.mock.calls[0] as [string, unknown[]];
      expect(callArgs[1][0]).toBe(90);
    });

    it('should return mapped rows with date and count as numbers', async () => {
      const mockQuery = jest.fn().mockResolvedValue({
        rows: [
          { date: '2026-04-01', count: '3' },
          { date: '2026-04-02', count: '7' },
          { date: '2026-04-03', count: '0' },
        ],
      } as unknown as QueryResult);

      const service = new AnalyticsService(makePool(mockQuery));
      const result = await service.getTokenTrend('tenant-1', 3);

      expect(result).toHaveLength(3);
      expect(result[0]).toEqual({ date: '2026-04-01', count: 3 });
      expect(result[1]).toEqual({ date: '2026-04-02', count: 7 });
      expect(result[2]).toEqual({ date: '2026-04-03', count: 0 });
      // count must be a number, not a string
      expect(typeof result[0].count).toBe('number');
    });
  });

  // ────────────────────────────────────────────────────────────────
  // getAgentActivity()
  // ────────────────────────────────────────────────────────────────

  describe('getAgentActivity()', () => {
    it('should return rows mapped to correct IAgentActivityEntry shape', async () => {
      const mockQuery = jest.fn().mockResolvedValue({
        rows: [
          { agent_id: 'agent-uuid-1', dow: '1', hour: '0', count: '12' },
          { agent_id: 'agent-uuid-1', dow: '3', hour: '0', count: '5' },
          { agent_id: 'agent-uuid-2', dow: '5', hour: '0', count: '20' },
        ],
      } as unknown as QueryResult);

      const service = new AnalyticsService(makePool(mockQuery));
      const result = await service.getAgentActivity('tenant-1');

      expect(result).toHaveLength(3);
      expect(result[0]).toEqual({ agent_id: 'agent-uuid-1', dow: 1, hour: 0, count: 12 });
      expect(result[1]).toEqual({ agent_id: 'agent-uuid-1', dow: 3, hour: 0, count: 5 });
      expect(result[2]).toEqual({ agent_id: 'agent-uuid-2', dow: 5, hour: 0, count: 20 });
      // Numeric types
      expect(typeof result[0].dow).toBe('number');
      expect(typeof result[0].hour).toBe('number');
      expect(typeof result[0].count).toBe('number');
    });

    it('should return an empty array when no activity rows exist', async () => {
      const mockQuery = jest.fn().mockResolvedValue({
        rows: [],
      } as unknown as QueryResult);

      const service = new AnalyticsService(makePool(mockQuery));
      const result = await service.getAgentActivity('tenant-empty');

      expect(result).toEqual([]);
    });
  });

  // ────────────────────────────────────────────────────────────────
  // getAgentUsageSummary()
  // ────────────────────────────────────────────────────────────────

  describe('getAgentUsageSummary()', () => {
    it('should return rows mapped to correct IAgentUsageSummaryEntry shape', async () => {
      const mockQuery = jest.fn().mockResolvedValue({
        rows: [
          { agent_id: 'agent-uuid-1', name: 'team-a', token_count: '200' },
          { agent_id: 'agent-uuid-2', name: 'team-b', token_count: '50' },
        ],
      } as unknown as QueryResult);

      const service = new AnalyticsService(makePool(mockQuery));
      const result = await service.getAgentUsageSummary('tenant-1');

      expect(result).toHaveLength(2);
      expect(result[0]).toEqual({ agent_id: 'agent-uuid-1', name: 'team-a', token_count: 200 });
      expect(result[1]).toEqual({ agent_id: 'agent-uuid-2', name: 'team-b', token_count: 50 });
      // token_count must be a number
      expect(typeof result[0].token_count).toBe('number');
    });

    it('should return an empty array when no agents exist', async () => {
      const mockQuery = jest.fn().mockResolvedValue({
        rows: [],
      } as unknown as QueryResult);

      const service = new AnalyticsService(makePool(mockQuery));
      const result = await service.getAgentUsageSummary('tenant-empty');

      expect(result).toEqual([]);
    });
  });
});
