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

import { v4 as uuidv4 } from 'uuid';
import { AuditService } from '../../../src/services/AuditService';
import { AuditRepository } from '../../../src/repositories/AuditRepository';
import {
  AuditEventNotFoundError,
  RetentionWindowError,
  ValidationError,
} from '../../../src/utils/errors';
import { IAuditEvent } from '../../../src/types/index';

jest.mock('../../../src/repositories/AuditRepository');

const MockAuditRepo = AuditRepository as jest.MockedClass<typeof AuditRepository>;

const MOCK_EVENT: IAuditEvent = {
  eventId: uuidv4(),
  agentId: uuidv4(),
  action: 'token.issued',
  outcome: 'success',
  ipAddress: '127.0.0.1',
  userAgent: 'test/1.0',
  metadata: { scope: 'agents:read' },
  timestamp: new Date(), // recent timestamp
};

describe('AuditService', () => {
  let service: AuditService;
  let auditRepo: jest.Mocked<AuditRepository>;

  beforeEach(() => {
    jest.clearAllMocks();
    auditRepo = new MockAuditRepo({} as never) as jest.Mocked<AuditRepository>;
    service = new AuditService(auditRepo);
  });

  // ────────────────────────────────────────────────────────────────
  // logEvent
  // ────────────────────────────────────────────────────────────────
  describe('logEvent()', () => {
    it('should create an audit event', async () => {
      auditRepo.create.mockResolvedValue(MOCK_EVENT);
      const result = await service.logEvent(
        MOCK_EVENT.agentId,
        'token.issued',
        'success',
        '127.0.0.1',
        'test/1.0',
        { scope: 'agents:read' },
      );
      expect(result).toEqual(MOCK_EVENT);
      expect(auditRepo.create).toHaveBeenCalledTimes(1);
    });
  });

  // ────────────────────────────────────────────────────────────────
  // queryEvents
  // ────────────────────────────────────────────────────────────────
  describe('queryEvents()', () => {
    it('should return paginated events', async () => {
      auditRepo.findAll.mockResolvedValue({ events: [MOCK_EVENT], total: 1 });
      const result = await service.queryEvents({ page: 1, limit: 50 });
      expect(result.data).toHaveLength(1);
      expect(result.total).toBe(1);
    });

    it('should throw RetentionWindowError for fromDate before 90-day cutoff', async () => {
      const oldDate = new Date();
      oldDate.setDate(oldDate.getDate() - 100);
      await expect(
        service.queryEvents({ page: 1, limit: 50, fromDate: oldDate.toISOString() }),
      ).rejects.toThrow(RetentionWindowError);
    });

    it('should throw ValidationError when fromDate is after toDate', async () => {
      const future = new Date();
      future.setDate(future.getDate() + 5);
      const past = new Date();
      past.setDate(past.getDate() - 1);
      await expect(
        service.queryEvents({
          page: 1,
          limit: 50,
          fromDate: future.toISOString(),
          toDate: past.toISOString(),
        }),
      ).rejects.toThrow(ValidationError);
    });

    it('should not throw for valid date range within retention window', async () => {
      auditRepo.findAll.mockResolvedValue({ events: [], total: 0 });
      const recentDate = new Date();
      recentDate.setDate(recentDate.getDate() - 30);
      await expect(
        service.queryEvents({ page: 1, limit: 50, fromDate: recentDate.toISOString() }),
      ).resolves.toBeDefined();
    });
  });

  // ────────────────────────────────────────────────────────────────
  // getEventById
  // ────────────────────────────────────────────────────────────────
  describe('getEventById()', () => {
    it('should return the event when found within retention window', async () => {
      auditRepo.findById.mockResolvedValue(MOCK_EVENT);
      const result = await service.getEventById(MOCK_EVENT.eventId);
      expect(result).toEqual(MOCK_EVENT);
    });

    it('should throw AuditEventNotFoundError when not found', async () => {
      auditRepo.findById.mockResolvedValue(null);
      await expect(service.getEventById('nonexistent')).rejects.toThrow(AuditEventNotFoundError);
    });

    it('should throw AuditEventNotFoundError for event outside retention window', async () => {
      const oldEvent: IAuditEvent = {
        ...MOCK_EVENT,
        timestamp: new Date('2020-01-01T00:00:00Z'),
      };
      auditRepo.findById.mockResolvedValue(oldEvent);
      await expect(service.getEventById(oldEvent.eventId)).rejects.toThrow(
        AuditEventNotFoundError,
      );
    });
  });
});
