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 { munge, parseLines, scanProjects } from '../src/core/jsonl-discovery.js';

function writeJsonl(projectsDir: string, cwd: string, sid: string, lines: object[]): void {
  const dir = join(projectsDir, munge(cwd));
  mkdirSync(dir, { recursive: true });
  writeFileSync(join(dir, `${sid}.jsonl`), `${lines.map((l) => JSON.stringify(l)).join('\n')}\n`);
}

describe('munge', () => {
  it('reproduit le nom de dossier ~/.claude/projects', () => {
    expect(munge('/home/x/My Project!')).toBe('-home-x-My-Project-');
    expect(munge('/home/user/projects/demo')).toBe('-home-user-projects-demo');
  });
});

describe('parseLines (tolérance)', () => {
  it('ignore les lignes vides, coupées ou malformées sans throw', () => {
    const text = '{"a":1}\n\n{not json}\n{"b":2}\n{"c":';
    expect(parseLines(text)).toEqual([{ a: 1 }, { b: 2 }]);
  });
  it('shift/pop les lignes partielles en bord de chunk', () => {
    const text = 'partial}\n{"ok":1}\n{also partial';
    expect(parseLines(text, true, true)).toEqual([{ ok: 1 }]);
  });
});

describe('scanProjects', () => {
  let dir: string;
  beforeEach(() => {
    dir = mkdtempSync(join(tmpdir(), 'arb-jsonl-'));
  });
  afterEach(() => {
    rmSync(dir, { recursive: true, force: true });
  });

  it('dossier absent → tableau vide', async () => {
    expect(await scanProjects(join(dir, 'nope'))).toEqual([]);
  });

  it('extrait claudeSessionId + cwd + branche + version', async () => {
    writeJsonl(dir, '/home/u/proj', 'sid-1', [
      { type: 'user', sessionId: 'sid-1', cwd: '/home/u/proj', gitBranch: 'main', version: '2.1.170', message: { content: 'hello world' } },
    ]);
    const res = await scanProjects(dir);
    expect(res).toHaveLength(1);
    expect(res[0]).toMatchObject({ claudeSessionId: 'sid-1', cwd: '/home/u/proj', gitBranch: 'main', version: '2.1.170' });
  });

  it('écarte un fichier sans cwd (queue-operation only)', async () => {
    writeJsonl(dir, '/x', 'sid-q', [{ type: 'queue-operation', sessionId: 'sid-q' }]);
    expect(await scanProjects(dir)).toEqual([]);
  });

  it('survit à une ligne malformée et reste exploitable', async () => {
    const d = join(dir, munge('/home/u/p2'));
    mkdirSync(d, { recursive: true });
    writeFileSync(
      join(d, 'sid-2.jsonl'),
      `{bad json\n${JSON.stringify({ type: 'user', sessionId: 'sid-2', cwd: '/home/u/p2', message: { content: 'ok' } })}\n`,
    );
    const res = await scanProjects(dir);
    expect(res).toHaveLength(1);
    expect(res[0].claudeSessionId).toBe('sid-2');
  });

  describe('priorité de titre (ai-title > summary > last-prompt > 1er user)', () => {
    it('ai-title gagne sur tout', async () => {
      writeJsonl(dir, '/t/1', 's', [
        { type: 'user', sessionId: 's', cwd: '/t/1', message: { content: 'premier prompt' } },
        { type: 'summary', sessionId: 's', summary: 'un résumé' },
        { type: 'last-prompt', sessionId: 's', lastPrompt: 'dernier prompt' },
        { type: 'ai-title', sessionId: 's', aiTitle: 'Titre IA' },
      ]);
      expect((await scanProjects(dir))[0].title).toBe('Titre IA');
    });
    it('summary gagne sur last-prompt et user', async () => {
      writeJsonl(dir, '/t/2', 's', [
        { type: 'user', sessionId: 's', cwd: '/t/2', message: { content: 'premier prompt' } },
        { type: 'last-prompt', sessionId: 's', lastPrompt: 'dernier prompt' },
        { type: 'summary', sessionId: 's', summary: 'un résumé' },
      ]);
      expect((await scanProjects(dir))[0].title).toBe('un résumé');
    });
    it('repli ultime sur le premier prompt user', async () => {
      writeJsonl(dir, '/t/3', 's', [{ type: 'user', sessionId: 's', cwd: '/t/3', message: { content: 'premier prompt' } }]);
      expect((await scanProjects(dir))[0].title).toBe('premier prompt');
    });
  });
});
