/**
 * Audit event collector.
 *
 * Batches events in memory and POSTs them to mcpd periodically.
 * Fire-and-forget: audit never blocks the MCP request path.
 */
import type { AuditEvent } from './types.js';
import type { McpdClient } from '../http/mcpd-client.js';

const BATCH_SIZE = 50;
const FLUSH_INTERVAL_MS = 5_000;

interface SessionPrincipal {
  userName?: string;
  tokenName?: string;
  tokenSha?: string;
}

export class AuditCollector {
  private queue: AuditEvent[] = [];
  private flushTimer: ReturnType<typeof setInterval> | null = null;
  private flushing = false;
  private sessionPrincipals = new Map<string, SessionPrincipal>();

  constructor(
    private readonly mcpdClient: McpdClient,
    private readonly projectName: string,
  ) {
    this.flushTimer = setInterval(() => void this.flush(), FLUSH_INTERVAL_MS);
  }

  /** Register a userName for a session. All future events for this session auto-fill it. */
  setSessionUserName(sessionId: string, userName: string): void {
    const existing = this.sessionPrincipals.get(sessionId) ?? {};
    this.sessionPrincipals.set(sessionId, { ...existing, userName });
  }

  /** Register McpToken identity for a session (HTTP-mode authenticated requests). */
  setSessionMcpToken(sessionId: string, token: { tokenName: string; tokenSha: string }): void {
    const existing = this.sessionPrincipals.get(sessionId) ?? {};
    this.sessionPrincipals.set(sessionId, { ...existing, tokenName: token.tokenName, tokenSha: token.tokenSha });
  }

  /** Queue an audit event. Auto-fills projectName, userName, tokenName, and tokenSha. */
  emit(event: Omit<AuditEvent, 'projectName'>): void {
    const enriched: AuditEvent = { ...event, projectName: this.projectName };
    if (enriched.sessionId) {
      const principal = this.sessionPrincipals.get(enriched.sessionId);
      if (principal) {
        if (!enriched.userName && principal.userName) enriched.userName = principal.userName;
        if (!enriched.tokenName && principal.tokenName) enriched.tokenName = principal.tokenName;
        if (!enriched.tokenSha && principal.tokenSha) enriched.tokenSha = principal.tokenSha;
      }
    }
    this.queue.push(enriched);
    if (this.queue.length >= BATCH_SIZE) {
      void this.flush();
    }
  }

  /** Flush queued events to mcpd. Safe to call concurrently. */
  async flush(): Promise<void> {
    if (this.flushing || this.queue.length === 0) return;
    this.flushing = true;

    const batch = this.queue.splice(0);
    try {
      await this.mcpdClient.post('/api/v1/audit/events', batch);
    } catch {
      // Audit is best-effort — never propagate failures
    } finally {
      this.flushing = false;
    }
  }

  /** Flush remaining events and stop the timer. */
  async dispose(): Promise<void> {
    if (this.flushTimer) {
      clearInterval(this.flushTimer);
      this.flushTimer = null;
    }
    await this.flush();
  }
}
