import pino from 'pino';
import type { CanonicalAlert } from '../ingestion/webhook.js';

const logger = pino({ name: 'correlation' });

// --- Interfaces ---

export interface WindowStore {
  getWindow(tenantId: string, fingerprint: string): Promise<CorrelationWindow | null>;
  upsertWindow(tenantId: string, window: CorrelationWindow): Promise<void>;
  closeWindow(tenantId: string, fingerprint: string): Promise<CorrelationWindow | null>;
  getExpiredWindows(cutoffMs: number): Promise<CorrelationWindow[]>;
}

export interface Clock {
  now(): number;
}

export class RealClock implements Clock {
  now() { return Date.now(); }
}

export class FakeClock implements Clock {
  private current: number;
  constructor(start = Date.now()) { this.current = start; }
  now() { return this.current; }
  advanceBy(ms: number) { this.current += ms; }
  set(ms: number) { this.current = ms; }
}

export interface CorrelationWindow {
  fingerprint: string;
  service: string;
  alerts: CanonicalAlert[];
  openedAt: number;
  lastAlertAt: number;
  incidentId?: string;
  shipped: boolean;
}

export interface CorrelationResult {
  incidentId: string;
  action: 'new_incident' | 'attached_to_existing' | 'window_updated';
  alertCount: number;
}

// --- Correlation Engine ---

const DEFAULT_WINDOW_MS = 5 * 60 * 1000; // 5 minutes
const LATE_ATTACH_MULTIPLIER = 2; // Attach late alerts up to 2x window

export class CorrelationEngine {
  private windowStore: WindowStore;
  private clock: Clock;
  private windowMs: number;

  constructor(windowStore: WindowStore, clock: Clock = new RealClock(), windowMs = DEFAULT_WINDOW_MS) {
    this.windowStore = windowStore;
    this.clock = clock;
    this.windowMs = windowMs;
  }

  async process(tenantId: string, alert: CanonicalAlert): Promise<CorrelationResult> {
    const now = this.clock.now();
    const existing = await this.windowStore.getWindow(tenantId, alert.fingerprint);

    // Case 1: No existing window — create new
    if (!existing) {
      const window: CorrelationWindow = {
        fingerprint: alert.fingerprint,
        service: alert.service ?? 'unknown',
        alerts: [alert],
        openedAt: now,
        lastAlertAt: now,
        shipped: false,
      };
      await this.windowStore.upsertWindow(tenantId, window);
      return { incidentId: '', action: 'window_updated', alertCount: 1 };
    }

    // Case 2: Window exists and is still open
    if (!existing.shipped) {
      existing.alerts.push(alert);
      existing.lastAlertAt = now;
      await this.windowStore.upsertWindow(tenantId, existing);
      return {
        incidentId: existing.incidentId ?? '',
        action: 'window_updated',
        alertCount: existing.alerts.length,
      };
    }

    // Case 3: Window shipped (incident created) — late alert
    const windowAge = now - existing.openedAt;

    if (windowAge <= this.windowMs * LATE_ATTACH_MULTIPLIER) {
      // Within 2x window — attach to existing incident
      existing.alerts.push(alert);
      existing.lastAlertAt = now;
      await this.windowStore.upsertWindow(tenantId, existing);
      return {
        incidentId: existing.incidentId ?? '',
        action: 'attached_to_existing',
        alertCount: existing.alerts.length,
      };
    }

    // Case 4: Very late alert (>2x window) — new incident
    const newWindow: CorrelationWindow = {
      fingerprint: alert.fingerprint,
      service: alert.service ?? 'unknown',
      alerts: [alert],
      openedAt: now,
      lastAlertAt: now,
      shipped: false,
    };
    await this.windowStore.upsertWindow(tenantId, newWindow);
    return { incidentId: '', action: 'new_incident', alertCount: 1 };
  }

  async flushWindows(tenantId: string): Promise<CorrelationWindow[]> {
    const now = this.clock.now();
    const cutoff = now - this.windowMs;
    const expired = await this.windowStore.getExpiredWindows(cutoff);

    const shipped: CorrelationWindow[] = [];
    for (const window of expired) {
      if (window.shipped) continue;
      window.shipped = true;
      window.incidentId = `inc_${crypto.randomUUID().slice(0, 8)}`;
      await this.windowStore.upsertWindow(tenantId, window);
      shipped.push(window);
    }

    return shipped;
  }
}

// --- In-Memory Window Store (for testing) ---

export class InMemoryWindowStore implements WindowStore {
  private windows = new Map<string, CorrelationWindow>();

  private key(tenantId: string, fingerprint: string) {
    return `${tenantId}:${fingerprint}`;
  }

  async getWindow(tenantId: string, fingerprint: string) {
    return this.windows.get(this.key(tenantId, fingerprint)) ?? null;
  }

  async upsertWindow(tenantId: string, window: CorrelationWindow) {
    this.windows.set(this.key(tenantId, window.fingerprint), window);
  }

  async closeWindow(tenantId: string, fingerprint: string) {
    const w = this.windows.get(this.key(tenantId, fingerprint));
    if (w) this.windows.delete(this.key(tenantId, fingerprint));
    return w ?? null;
  }

  async getExpiredWindows(cutoffMs: number) {
    return Array.from(this.windows.values()).filter(w => w.lastAlertAt <= cutoffMs && !w.shipped);
  }
}
