import { describe, expect, it, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import type { SessionSummary } from '@arboretum/shared';
import { DiscoveryService, mergeSessions } from '../src/core/discovery-service.js';
import { munge } from '../src/core/jsonl-discovery.js';
import { readProcStart } from '../src/core/session-registry.js';
import { PtyManager } from '../src/core/pty-manager.js';
import { openDb, type Db } from '../src/db/index.js';

function writeJsonl(projectsDir: string, cwd: string, sid: string): void {
  const dir = join(projectsDir, munge(cwd));
  mkdirSync(dir, { recursive: true });
  writeFileSync(
    join(dir, `${sid}.jsonl`),
    `${JSON.stringify({ type: 'user', sessionId: sid, cwd, message: { content: `prompt ${sid}` } })}\n`,
  );
}

describe('DiscoveryService', () => {
  let projectsDir: string;
  let sessionsDir: string;
  let db: Db;
  let manager: PtyManager;
  let svc: DiscoveryService;

  beforeEach(() => {
    projectsDir = mkdtempSync(join(tmpdir(), 'arb-proj-'));
    sessionsDir = mkdtempSync(join(tmpdir(), 'arb-sess-'));
    db = openDb(':memory:');
    manager = new PtyManager(db, sessionsDir);
    svc = new DiscoveryService({ ptyManager: manager, projectsDir, sessionsDir });
  });
  afterEach(() => {
    svc.stop();
    rmSync(projectsDir, { recursive: true, force: true });
    rmSync(sessionsDir, { recursive: true, force: true });
  });

  it('session morte (pas d’entrée registre) → resumable, non attachable', async () => {
    writeJsonl(projectsDir, '/home/u/dead', 'dead-sid');
    await svc.refresh();
    const s = svc.list().find((x) => x.id === 'dead-sid');
    expect(s).toMatchObject({ source: 'discovered', live: false, resumable: true, attachable: false, cwd: '/home/u/dead' });
    expect(svc.getDiscovered('dead-sid')?.cwd).toBe('/home/u/dead');
  });

  it('session vivante (registre + pid vivant) → live, non resumable, registryStatus exposé', async () => {
    writeJsonl(projectsDir, '/home/u/live', 'live-sid');
    const ps = readProcStart(process.pid) as string;
    writeFileSync(
      join(sessionsDir, `${process.pid}.json`),
      JSON.stringify({ pid: process.pid, procStart: ps, sessionId: 'live-sid', cwd: '/home/u/live', status: 'waiting', waitingFor: 'permission prompt' }),
    );
    await svc.refresh();
    const s = svc.list().find((x) => x.id === 'live-sid');
    expect(s).toMatchObject({ live: true, resumable: false, attachable: false, registryStatus: 'waiting', pid: process.pid });
  });

  it('exclut une session déjà connue d’Arboretum (managée / historique)', async () => {
    writeJsonl(projectsDir, '/home/u/managed', 'managed-sid');
    db.prepare('INSERT INTO sessions (id, cwd, command, created_at, claude_session_id) VALUES (?, ?, ?, ?, ?)').run(
      'uuid-1',
      '/home/u/managed',
      'claude',
      new Date().toISOString(),
      'managed-sid',
    );
    await svc.refresh();
    expect(svc.list().find((x) => x.id === 'managed-sid')).toBeUndefined();
  });

  it('émet discovery_update sur changement uniquement', async () => {
    writeJsonl(projectsDir, '/home/u/x', 'sid-x');
    const seen: SessionSummary[] = [];
    svc.on('discovery_update', (s) => seen.push(s));
    await svc.refresh();
    expect(seen.map((s) => s.id)).toContain('sid-x');
    seen.length = 0;
    await svc.refresh(); // rien n'a changé → aucune ré-émission
    expect(seen).toHaveLength(0);
  });
});

describe('mergeSessions', () => {
  const mk = (id: string, claudeSessionId: string | null, source: 'managed' | 'discovered'): SessionSummary => ({
    id,
    cwd: '/x',
    command: 'claude',
    title: null,
    status: 'exited',
    live: false,
    createdAt: '',
    endedAt: null,
    exitCode: null,
    clients: 0,
    source,
    claudeSessionId,
    pid: null,
    resumable: false,
    attachable: false,
    registryStatus: null,
  });

  it('dédoublonne par claudeSessionId, managé prioritaire', () => {
    const managed = [mk('uuid', 'sid-1', 'managed')];
    const discovered = [mk('sid-1', 'sid-1', 'discovered'), mk('sid-2', 'sid-2', 'discovered')];
    expect(mergeSessions(managed, discovered).map((s) => s.id)).toEqual(['uuid', 'sid-2']);
  });
});
