// audit-trail.test.mjs — Tests for structured JSONL audit trail

import { describe, it, beforeEach, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import { writeFileSync, readFileSync, unlinkSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { writeAuditEvent, isAuditEnabled, _resetForTest } from '../../scanners/lib/audit-trail.mjs';

const TEST_LOG = join(tmpdir(), `llm-security-audit-test-${Date.now()}.jsonl`);

describe('audit-trail', () => {
  beforeEach(() => {
    _resetForTest();
    // Clean up test file
    try { unlinkSync(TEST_LOG); } catch {}
  });

  afterEach(() => {
    _resetForTest();
    delete process.env.LLM_SECURITY_AUDIT_LOG;
    try { unlinkSync(TEST_LOG); } catch {}
  });

  it('is disabled when env var not set', () => {
    delete process.env.LLM_SECURITY_AUDIT_LOG;
    assert.equal(isAuditEnabled(), false);
  });

  it('is enabled when env var is set to writable path', () => {
    process.env.LLM_SECURITY_AUDIT_LOG = TEST_LOG;
    assert.equal(isAuditEnabled(), true);
  });

  it('no-op when env var not set', () => {
    delete process.env.LLM_SECURITY_AUDIT_LOG;
    writeAuditEvent({ event_type: 'test', severity: 'info', source: 'test' });
    assert.equal(existsSync(TEST_LOG), false);
  });

  it('writes valid JSONL when enabled', () => {
    process.env.LLM_SECURITY_AUDIT_LOG = TEST_LOG;
    writeAuditEvent({
      event_type: 'trifecta_warning',
      severity: 'high',
      source: 'post-session-guard',
      details: { window_size: 20 },
      owasp: ['ASI01', 'ASI02'],
      action_taken: 'warned',
    });

    const content = readFileSync(TEST_LOG, 'utf8').trim();
    const entry = JSON.parse(content);

    assert.equal(entry.event_type, 'trifecta_warning');
    assert.equal(entry.severity, 'high');
    assert.equal(entry.source, 'post-session-guard');
    assert.deepEqual(entry.owasp, ['ASI01', 'ASI02']);
    assert.equal(entry.action_taken, 'warned');
    assert.ok(entry.timestamp.match(/^\d{4}-\d{2}-\d{2}T/), 'Expected ISO timestamp');
    assert.ok(entry.session_id, 'Expected session_id');
  });

  it('appends multiple events as separate lines', () => {
    process.env.LLM_SECURITY_AUDIT_LOG = TEST_LOG;
    writeAuditEvent({ event_type: 'event1', severity: 'info', source: 'test' });
    writeAuditEvent({ event_type: 'event2', severity: 'medium', source: 'test' });
    writeAuditEvent({ event_type: 'event3', severity: 'high', source: 'test' });

    const lines = readFileSync(TEST_LOG, 'utf8').trim().split('\n');
    assert.equal(lines.length, 3);

    const e1 = JSON.parse(lines[0]);
    const e3 = JSON.parse(lines[2]);
    assert.equal(e1.event_type, 'event1');
    assert.equal(e3.event_type, 'event3');
  });

  it('events contain all required fields', () => {
    process.env.LLM_SECURITY_AUDIT_LOG = TEST_LOG;
    writeAuditEvent({ event_type: 'test', severity: 'info', source: 'test-hook' });

    const entry = JSON.parse(readFileSync(TEST_LOG, 'utf8').trim());
    const required = ['timestamp', 'session_id', 'event_type', 'severity', 'source', 'details', 'owasp', 'action_taken'];
    for (const field of required) {
      assert.ok(field in entry, `Missing required field: ${field}`);
    }
  });

  it('provides defaults for optional fields', () => {
    process.env.LLM_SECURITY_AUDIT_LOG = TEST_LOG;
    writeAuditEvent({ event_type: 'minimal' });

    const entry = JSON.parse(readFileSync(TEST_LOG, 'utf8').trim());
    assert.equal(entry.severity, 'info');
    assert.equal(entry.source, 'unknown');
    assert.deepEqual(entry.details, {});
    assert.deepEqual(entry.owasp, []);
    assert.equal(entry.action_taken, 'warned');
  });

  it('does not crash on invalid path', () => {
    process.env.LLM_SECURITY_AUDIT_LOG = '/nonexistent/dir/audit.jsonl';
    // Should not throw — gracefully logs to stderr
    assert.doesNotThrow(() => {
      writeAuditEvent({ event_type: 'test', severity: 'info', source: 'test' });
    });
  });
});
