import { describe, it, expect, vi, afterEach } from 'vitest';
import { AuditCollector } from '../src/audit/collector.js';
import type { McpdClient } from '../src/http/mcpd-client.js';

function mockClient(): McpdClient {
  return {
    post: vi.fn(async () => ({})),
    get: vi.fn(async () => ({})),
    put: vi.fn(async () => ({})),
    delete: vi.fn(async () => {}),
    forward: vi.fn(async () => ({ status: 200, body: {} })),
    withHeaders: vi.fn(() => mockClient()),
  } as unknown as McpdClient;
}

describe('AuditCollector userName', () => {
  let collector: AuditCollector;
  let client: McpdClient;

  afterEach(async () => {
    if (collector) await collector.dispose();
  });

  it('auto-fills userName from session map', async () => {
    client = mockClient();
    collector = new AuditCollector(client, 'test-project');
    collector.setSessionUserName('sess-1', 'michal');

    collector.emit({
      timestamp: new Date().toISOString(),
      sessionId: 'sess-1',
      eventKind: 'session_bind',
      source: 'mcplocal',
      verified: true,
      payload: {},
    });

    await collector.flush();

    const posted = vi.mocked(client.post).mock.calls[0]![1] as Array<Record<string, unknown>>;
    expect(posted).toHaveLength(1);
    expect(posted[0]!['userName']).toBe('michal');
    expect(posted[0]!['projectName']).toBe('test-project');
  });

  it('does not overwrite explicitly set userName', async () => {
    client = mockClient();
    collector = new AuditCollector(client, 'test-project');
    collector.setSessionUserName('sess-1', 'michal');

    collector.emit({
      timestamp: new Date().toISOString(),
      sessionId: 'sess-1',
      eventKind: 'tool_call_trace',
      source: 'mcplocal',
      verified: true,
      userName: 'admin',
      payload: {},
    });

    await collector.flush();

    const posted = vi.mocked(client.post).mock.calls[0]![1] as Array<Record<string, unknown>>;
    expect(posted[0]!['userName']).toBe('admin');
  });

  it('leaves userName undefined when no session mapping exists', async () => {
    client = mockClient();
    collector = new AuditCollector(client, 'test-project');

    collector.emit({
      timestamp: new Date().toISOString(),
      sessionId: 'sess-unknown',
      eventKind: 'gate_decision',
      source: 'client',
      verified: false,
      payload: {},
    });

    await collector.flush();

    const posted = vi.mocked(client.post).mock.calls[0]![1] as Array<Record<string, unknown>>;
    expect(posted[0]!['userName']).toBeUndefined();
  });

  it('fills userName for multiple sessions independently', async () => {
    client = mockClient();
    collector = new AuditCollector(client, 'test-project');
    collector.setSessionUserName('sess-a', 'alice');
    collector.setSessionUserName('sess-b', 'bob');

    collector.emit({
      timestamp: new Date().toISOString(),
      sessionId: 'sess-a',
      eventKind: 'session_bind',
      source: 'mcplocal',
      verified: true,
      payload: {},
    });
    collector.emit({
      timestamp: new Date().toISOString(),
      sessionId: 'sess-b',
      eventKind: 'session_bind',
      source: 'mcplocal',
      verified: true,
      payload: {},
    });

    await collector.flush();

    const posted = vi.mocked(client.post).mock.calls[0]![1] as Array<Record<string, unknown>>;
    expect(posted).toHaveLength(2);
    expect(posted[0]!['userName']).toBe('alice');
    expect(posted[1]!['userName']).toBe('bob');
  });
});
