import { describe, it, expect, beforeEach } from 'vitest';
import {
  CorrelationEngine,
  InMemoryWindowStore,
  FakeClock,
} from '../../src/correlation/engine.js';
import type { CanonicalAlert } from '../../src/ingestion/webhook.js';

function makeAlert(overrides: Partial<CanonicalAlert> = {}): CanonicalAlert {
  return {
    sourceProvider: 'datadog',
    sourceId: `alert-${Math.random().toString(36).slice(2)}`,
    fingerprint: 'cpu-high',
    title: 'CPU High',
    severity: 'high',
    status: 'firing',
    service: 'auth',
    tags: {},
    rawPayload: {},
    timestamp: Date.now(),
    ...overrides,
  };
}

describe('CorrelationEngine', () => {
  let store: InMemoryWindowStore;
  let clock: FakeClock;
  let engine: CorrelationEngine;

  beforeEach(() => {
    store = new InMemoryWindowStore();
    clock = new FakeClock(1000000);
    engine = new CorrelationEngine(store, clock, 5 * 60 * 1000); // 5min window
  });

  it('creates a new window for first alert', async () => {
    const result = await engine.process('t1', makeAlert());
    expect(result.action).toBe('window_updated');
    expect(result.alertCount).toBe(1);
  });

  it('groups alerts with same fingerprint into one window', async () => {
    await engine.process('t1', makeAlert({ fingerprint: 'cpu-high' }));
    const result = await engine.process('t1', makeAlert({ fingerprint: 'cpu-high' }));
    expect(result.action).toBe('window_updated');
    expect(result.alertCount).toBe(2);
  });

  it('keeps different fingerprints in separate windows', async () => {
    await engine.process('t1', makeAlert({ fingerprint: 'cpu-high' }));
    const result = await engine.process('t1', makeAlert({ fingerprint: 'disk-full' }));
    expect(result.action).toBe('window_updated');
    expect(result.alertCount).toBe(1); // New window, only 1 alert
  });

  it('late alert within 2x window attaches to existing incident', async () => {
    // Alert 1 at T=0
    await engine.process('t1', makeAlert());

    // Flush at T=5min (window closes, incident created)
    clock.advanceBy(5 * 60 * 1000);
    const shipped = await engine.flushWindows('t1');
    expect(shipped).toHaveLength(1);
    expect(shipped[0].incidentId).toBeTruthy();

    // Late alert at T=6min (within 2x window = 10min)
    clock.advanceBy(1 * 60 * 1000);
    const result = await engine.process('t1', makeAlert());
    expect(result.action).toBe('attached_to_existing');
    expect(result.incidentId).toBe(shipped[0].incidentId);
  });

  it('very late alert (>2x window) creates new incident', async () => {
    await engine.process('t1', makeAlert());

    clock.advanceBy(5 * 60 * 1000);
    await engine.flushWindows('t1');

    // 15 minutes later (3x window)
    clock.advanceBy(10 * 60 * 1000);
    const result = await engine.process('t1', makeAlert());
    expect(result.action).toBe('new_incident');
  });

  it('flushWindows ships expired windows', async () => {
    await engine.process('t1', makeAlert({ fingerprint: 'a' }));
    await engine.process('t1', makeAlert({ fingerprint: 'b' }));

    clock.advanceBy(6 * 60 * 1000); // Past 5min window
    const shipped = await engine.flushWindows('t1');
    expect(shipped).toHaveLength(2);
    expect(shipped[0].incidentId).toBeTruthy();
    expect(shipped[1].incidentId).toBeTruthy();
  });

  it('does not re-ship already shipped windows', async () => {
    await engine.process('t1', makeAlert());
    clock.advanceBy(6 * 60 * 1000);

    const first = await engine.flushWindows('t1');
    expect(first).toHaveLength(1);

    const second = await engine.flushWindows('t1');
    expect(second).toHaveLength(0);
  });
});
