import { describe, it, expect, vi, beforeEach } from 'vitest';
import { AuditLogService } from '../src/services/audit-log.service.js';
import type { IAuditLogRepository } from '../src/repositories/interfaces.js';
import { NotFoundError } from '../src/services/mcp-server.service.js';

function makeLog(overrides: Partial<{ id: string; userId: string; action: string; resource: string; resourceId: string | null; createdAt: Date }> = {}) {
  return {
    id: overrides.id ?? 'log-1',
    userId: overrides.userId ?? 'user-1',
    action: overrides.action ?? 'CREATE',
    resource: overrides.resource ?? 'servers',
    resourceId: overrides.resourceId ?? null,
    details: {},
    createdAt: overrides.createdAt ?? new Date('2025-01-15T00:00:00Z'),
  };
}

function mockRepo(): IAuditLogRepository {
  return {
    findAll: vi.fn(async () => [makeLog()]),
    findById: vi.fn(async () => null),
    create: vi.fn(async (data) => ({
      id: 'new-log',
      userId: data.userId,
      action: data.action,
      resource: data.resource,
      resourceId: data.resourceId ?? null,
      details: data.details ?? {},
      createdAt: new Date(),
    })),
    count: vi.fn(async () => 1),
    deleteOlderThan: vi.fn(async () => 5),
  };
}

describe('AuditLogService', () => {
  let repo: ReturnType<typeof mockRepo>;
  let service: AuditLogService;

  beforeEach(() => {
    repo = mockRepo();
    service = new AuditLogService(repo, 90);
  });

  describe('list', () => {
    it('returns logs with total count', async () => {
      const result = await service.list();
      expect(result.logs).toHaveLength(1);
      expect(result.total).toBe(1);
      expect(repo.findAll).toHaveBeenCalled();
      expect(repo.count).toHaveBeenCalled();
    });

    it('passes filter params to repository', async () => {
      await service.list({
        userId: 'user-1',
        action: 'CREATE',
        resource: 'servers',
        limit: 50,
        offset: 10,
      });

      expect(repo.findAll).toHaveBeenCalledWith(
        expect.objectContaining({
          userId: 'user-1',
          action: 'CREATE',
          resource: 'servers',
          limit: 50,
          offset: 10,
        }),
      );
    });

    it('parses date strings in since/until', async () => {
      await service.list({
        since: '2025-01-01T00:00:00Z',
        until: '2025-12-31T23:59:59Z',
      });

      expect(repo.findAll).toHaveBeenCalledWith(
        expect.objectContaining({
          since: expect.any(Date),
          until: expect.any(Date),
        }),
      );
    });
  });

  describe('getById', () => {
    it('returns a log entry', async () => {
      vi.mocked(repo.findById).mockResolvedValue(makeLog({ id: 'log-1' }));
      const log = await service.getById('log-1');
      expect(log.id).toBe('log-1');
    });

    it('throws NotFoundError when log does not exist', async () => {
      await expect(service.getById('nonexistent')).rejects.toThrow(NotFoundError);
    });
  });

  describe('create', () => {
    it('creates an audit log entry', async () => {
      const log = await service.create({
        userId: 'user-1',
        action: 'CREATE',
        resource: 'servers',
        details: { method: 'POST' },
      });

      expect(log.id).toBe('new-log');
      expect(repo.create).toHaveBeenCalledWith(
        expect.objectContaining({
          userId: 'user-1',
          action: 'CREATE',
          resource: 'servers',
        }),
      );
    });

    it('creates log with optional resourceId', async () => {
      await service.create({
        userId: 'user-1',
        action: 'DELETE',
        resource: 'servers',
        resourceId: 'srv-1',
      });

      expect(repo.create).toHaveBeenCalledWith(
        expect.objectContaining({ resourceId: 'srv-1' }),
      );
    });
  });

  describe('purgeExpired', () => {
    it('deletes logs older than retention period', async () => {
      const deleted = await service.purgeExpired();
      expect(deleted).toBe(5);
      expect(repo.deleteOlderThan).toHaveBeenCalledWith(expect.any(Date));
    });

    it('uses configured retention days', async () => {
      const customService = new AuditLogService(repo, 30);
      await customService.purgeExpired();

      const cutoff = vi.mocked(repo.deleteOlderThan).mock.calls[0]?.[0] as Date;
      const now = new Date();
      const daysDiff = Math.round((now.getTime() - cutoff.getTime()) / (1000 * 60 * 60 * 24));
      expect(daysDiff).toBe(30);
    });
  });
});
