import type { Request } from 'express';
import { AdminAuditController } from './admin-audit.controller';
import type { AdminAuditQueryDto } from './audit-query.dto';
import type { AuditReader, AdminAuditPage } from './audit-reader.service';
import type { AuditWriter } from '../audit/audit.service';

const PAGE: AdminAuditPage = {
  items: [
    {
      id: '11111111-1111-1111-1111-111111111111',
      createdAt: '2026-05-13T10:30:00.000Z',
      eventType: 'auth.sign_in',
      audience: 'workforce',
      actorIdHash: 'hash(jane)',
      traceId: 'trace-abc',
      subject: 'session:sid-1',
      outcome: 'success',
      payload: { amr: ['pwd', 'mfa'] },
    },
  ],
  total: 1,
  limit: 50,
  offset: 0,
};

function makeReq(user?: { oid: string }): Request {
  return { session: user !== undefined ? { user } : {} } as unknown as Request;
}

function makeFixture() {
  const auditReader = {
    findEvents: jest.fn().mockResolvedValue(PAGE),
  };
  const auditWriter = {
    adminAuditQuery: jest.fn().mockResolvedValue(undefined),
  };
  const controller = new AdminAuditController(
    auditReader as unknown as AuditReader,
    auditWriter as unknown as AuditWriter,
  );
  return { controller, auditReader, auditWriter };
}

describe('AdminAuditController.list', () => {
  it('returns the page from AuditReader', async () => {
    const { controller } = makeFixture();
    const page = await controller.list(makeReq({ oid: 'admin-oid' }), {} as AdminAuditQueryDto);
    expect(page).toBe(PAGE);
  });

  it('forwards the validated DTO to AuditReader as-is', async () => {
    const { controller, auditReader } = makeFixture();
    const filters: AdminAuditQueryDto = {
      eventType: 'auth.sign_in',
      audience: 'workforce',
      createdAtFrom: '2026-05-01T00:00:00.000Z',
      limit: 100,
      offset: 0,
    };
    await controller.list(makeReq({ oid: 'admin-oid' }), filters);
    expect(auditReader.findEvents).toHaveBeenCalledWith(filters);
  });

  it('emits admin.audit.query with the filters + result count as deterrent', async () => {
    const { controller, auditWriter } = makeFixture();
    const filters: AdminAuditQueryDto = { eventType: 'auth.sign_in', limit: 50 };
    await controller.list(makeReq({ oid: 'admin-oid' }), filters);
    expect(auditWriter.adminAuditQuery).toHaveBeenCalledWith({
      actor: { oid: 'admin-oid' },
      filters: { eventType: 'auth.sign_in', limit: 50 },
      resultCount: PAGE.items.length,
    });
  });

  it('captures an empty filter object verbatim (the admin asked for everything)', async () => {
    const { controller, auditWriter } = makeFixture();
    await controller.list(makeReq({ oid: 'admin-oid' }), {} as AdminAuditQueryDto);
    expect(auditWriter.adminAuditQuery).toHaveBeenCalledWith(
      expect.objectContaining({ filters: {} }),
    );
  });

  it('still returns the page when there is no session.user (defensive — guard should have caught it)', async () => {
    const { controller, auditWriter } = makeFixture();
    const page = await controller.list(makeReq(), {} as AdminAuditQueryDto);
    expect(page).toBe(PAGE);
    // No actor → no audit row. The guard is the primary gate; this
    // branch exists so a misconfiguration doesn't crash with a
    // misleading error.
    expect(auditWriter.adminAuditQuery).not.toHaveBeenCalled();
  });

  it('propagates audit write failures (blocking per ADR-0013)', async () => {
    const { controller, auditWriter } = makeFixture();
    auditWriter.adminAuditQuery.mockRejectedValueOnce(new Error('audit_writer denied'));
    await expect(
      controller.list(makeReq({ oid: 'admin-oid' }), {} as AdminAuditQueryDto),
    ).rejects.toThrow('audit_writer denied');
  });
});
