/**
 * Startup recovery for stale (pending) SSH audit rows.
 * Complements the smoke tests in audit-repo.test.ts with detail-merge,
 * idempotency, and ordering coverage.
 */
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import Database from 'better-sqlite3';
import { runMigrations } from '../db/migrate.js';
import { createAuditRepo } from './audit-repo.js';
import { reconcileStaleSshAudit } from './recovery.js';

const validKey = 'a'.repeat(64);

function bootstrapDb(): Database.Database {
  process.env.MCP_ENCRYPTION_KEY = validKey;
  const db = new Database(':memory:');
  db.pragma('foreign_keys = ON');
  db.exec(`CREATE TABLE users (id TEXT PRIMARY KEY);`);
  db.exec(`CREATE TABLE jobs (id TEXT PRIMARY KEY, wait_reason TEXT);`);
  db.exec(`CREATE TABLE local_tasks (id INTEGER PRIMARY KEY AUTOINCREMENT);`);
  runMigrations(db);
  return db;
}

describe('ssh/recovery reconcileStaleSshAudit', () => {
  let db: Database.Database;
  beforeEach(() => {
    db = bootstrapDb();
  });
  afterEach(() => {
    db.close();
    delete process.env.MCP_ENCRYPTION_KEY;
  });

  it('returns zero counts on an empty audit log', () => {
    const result = reconcileStaleSshAudit(db);
    expect(result).toEqual({ reconciledCount: 0, ids: [] });
  });

  it('marks every pending row aborted with the stale_reason detail', () => {
    const repo = createAuditRepo(db);
    const id1 = repo.begin({ action: 'ssh.exec', connectionId: 'c1' });
    const id2 = repo.begin({ action: 'ssh.upload', connectionId: 'c2' });

    const result = reconcileStaleSshAudit(db);
    expect(result.reconciledCount).toBe(2);
    expect(result.ids.sort()).toEqual([id1, id2].sort());

    for (const id of [id1, id2]) {
      const row = repo.getById(id);
      expect(row?.outcome).toBe('aborted');
      expect(row?.completedAt).not.toBeNull();
      expect(row?.detail).toMatchObject({
        stale_reason: 'orchestrator_restart',
      });
      expect(String(row?.detail?.['detail'])).toMatch(/outcome is unknown/);
    }
  });

  it('merges stale_reason into existing detail without dropping prior keys', () => {
    const repo = createAuditRepo(db);
    const id = repo.begin({
      action: 'ssh.exec',
      connectionId: 'c1',
      detail: { command: 'systemctl restart app', host: 'web-1' },
    });

    reconcileStaleSshAudit(db);

    const row = repo.getById(id);
    expect(row?.detail).toMatchObject({
      command: 'systemctl restart app',
      host: 'web-1',
      stale_reason: 'orchestrator_restart',
    });
  });

  it('does not touch rows that already reached a terminal outcome', () => {
    const repo = createAuditRepo(db);
    const doneId = repo.begin({ action: 'ssh.exec', connectionId: 'c1' });
    repo.complete(doneId, 'success', { exit_code: 0 });
    const failedId = repo.beginAndComplete({ action: 'ssh.exec', connectionId: 'c1' }, 'failed');
    const pendingId = repo.begin({ action: 'ssh.exec', connectionId: 'c1' });

    const result = reconcileStaleSshAudit(db);
    expect(result.reconciledCount).toBe(1);
    expect(result.ids).toEqual([pendingId]);

    expect(repo.getById(doneId)?.outcome).toBe('success');
    expect(repo.getById(doneId)?.detail).toEqual({ exit_code: 0 });
    expect(repo.getById(failedId)?.outcome).toBe('failed');
  });

  it('is idempotent — a second run finds nothing to reconcile', () => {
    const repo = createAuditRepo(db);
    repo.begin({ action: 'ssh.exec', connectionId: 'c1' });

    expect(reconcileStaleSshAudit(db).reconciledCount).toBe(1);
    expect(reconcileStaleSshAudit(db)).toEqual({ reconciledCount: 0, ids: [] });
  });

  it('returns ids in started_at ascending order (oldest first)', () => {
    const repo = createAuditRepo(db);
    const newer = repo.begin({
      action: 'ssh.exec',
      connectionId: 'c1',
      startedAt: '2026-06-10T12:00:00.000Z',
    });
    const oldest = repo.begin({
      action: 'ssh.exec',
      connectionId: 'c1',
      startedAt: '2026-06-01T00:00:00.000Z',
    });
    const middle = repo.begin({
      action: 'ssh.exec',
      connectionId: 'c1',
      startedAt: '2026-06-05T08:30:00.000Z',
    });

    const result = reconcileStaleSshAudit(db);
    expect(result.ids).toEqual([oldest, middle, newer]);
  });
});
