import { describe, it, expect } from 'vitest';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import {
  generateClaudeMd,
  rankReadFiles,
  resolveInsideProject,
  redact,
} from '@/lib/claude-code/claudemd.js';

function tmpDir(label) {
  const p = path.join(os.tmpdir(), `dashclaw-claudemd-${label}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
  fs.mkdirSync(p, { recursive: true });
  return p;
}

describe('claude-code/claudemd', () => {
  it('redact strips obvious secret patterns', () => {
    const sample = [
      'STRIPE_SECRET_KEY=sk_test_AbCdEfGhIjKlMnOpQrSt12345',
      'webhook=whsec_abcdefghij1234567890',
      'token: ghp_aaaaaaaaaaaaaaaaaaaaaaaa1234567890ab',
      'API_PASSWORD=hunter2',
      'plain text stays',
    ].join('\n');
    const out = redact(sample);
    expect(out).not.toMatch(/sk_test_AbCdEfGhIjKlMnOpQrSt12345/);
    expect(out).not.toMatch(/whsec_abcdefghij1234567890/);
    expect(out).not.toMatch(/ghp_aaaaaaaaaaaaaaaaaaaaaaaa1234567890ab/);
    expect(out).not.toMatch(/hunter2/);
    expect(out).toMatch(/plain text stays/);
    expect(out).toMatch(/<REDACTED:/);
  });

  it('rankReadFiles orders by total read count, ties broken by first-seen index', () => {
    const events = [
      { name: 'Read', target: 'src/a.js' },
      { name: 'Bash', target: 'noise' },
      { name: 'Read', target: 'src/b.js' },
      { name: 'Read', target: 'src/a.js' },
      { name: 'Read', target: 'src/b.js' },
      { name: 'Read', target: 'src/b.js' },
    ];
    const ranked = rankReadFiles(events);
    expect(ranked[0].target).toBe('src/b.js');
    expect(ranked[0].count).toBe(3);
    expect(ranked[1].target).toBe('src/a.js');
    expect(ranked[1].count).toBe(2);
  });

  it('rankReadFiles ignores node_modules and other build/git noise', () => {
    const events = [
      { name: 'Read', target: 'node_modules/react/index.js' },
      { name: 'Read', target: '.git/HEAD' },
      { name: 'Read', target: 'dist/bundle.js' },
      { name: 'Read', target: 'src/real.js' },
    ];
    const ranked = rankReadFiles(events);
    expect(ranked.length).toBe(1);
    expect(ranked[0].target).toBe('src/real.js');
  });

  it('resolveInsideProject refuses paths that escape the project cwd', () => {
    const cwd = tmpDir('escape');
    try {
      expect(resolveInsideProject('../../../etc/passwd', cwd)).toBe(null);
      expect(resolveInsideProject('/etc/passwd', cwd)).toBe(null);
      const ok = resolveInsideProject('src/db.js', cwd);
      expect(ok).toBeTruthy();
      expect(ok.startsWith(path.normalize(cwd))).toBe(true);
    } finally {
      fs.rmSync(cwd, { recursive: true, force: true });
    }
  });

  it('generateClaudeMd uses projectFiles map for summaries when supplied', () => {
    const cwd = tmpDir('happy');
    try {
      const projectFiles = new Map([
        ['package.json', JSON.stringify({ name: 'demo', version: '1.0.0', scripts: { test: 'node --test', start: 'node src/index.js' } }, null, 2)],
        [path.join('src', 'index.js'), "'use strict';\nfunction hello() { return 42; }\nmodule.exports = { hello };\n"],
      ]);
      const events = [
        { name: 'Read', target: 'src/index.js' },
        { name: 'Read', target: 'src/index.js' },
        { name: 'Read', target: 'src/index.js' },
        { name: 'Read', target: 'package.json' },
        { name: 'Read', target: 'package.json' },
        { name: 'Bash', target: null },
      ];
      const out = generateClaudeMd({
        session: { session_uuid: 'abc123', source_file: '/tmp/fake.jsonl' },
        project: { slug: 'demo', cwd },
        toolEvents: events,
        projectCwd: cwd,
        projectFiles,
        now: new Date('2026-05-12T00:00:00Z'),
      });
      expect(out.markdown).toMatch(/# CLAUDE\.md — demo/);
      expect(out.markdown).toMatch(/Generated by DashClaw Code Sessions/);
      expect(out.markdown).toMatch(/src[\\/]index\.js/);
      expect(out.markdown).toMatch(/package\.json/);
      expect(out.markdown).toMatch(/scripts: test, start/);
      expect(out.filename).toBe('CLAUDE.md');
      expect(out.suggestedPath).toBe(path.join(cwd, 'CLAUDE.md'));
      expect(out.stats.heavyReads).toBeGreaterThanOrEqual(2);
    } finally {
      fs.rmSync(cwd, { recursive: true, force: true });
    }
  });

  it('generateClaudeMd redacts secrets that live inside the inspected files', () => {
    const cwd = tmpDir('secret');
    try {
      const projectFiles = new Map([
        [path.join('src', 'cfg.js'), "const STRIPE_SECRET_KEY = 'sk_test_AbCdEfGhIjKlMnOpQrSt12345';\nmodule.exports = { STRIPE_SECRET_KEY };\n"],
      ]);
      const events = [
        { name: 'Read', target: 'src/cfg.js' },
        { name: 'Read', target: 'src/cfg.js' },
      ];
      const out = generateClaudeMd({
        session: { session_uuid: 'sec', source_file: '/tmp/fake.jsonl' },
        project: { slug: 'demo', cwd },
        toolEvents: events,
        projectCwd: cwd,
        projectFiles,
      });
      expect(out.markdown).not.toMatch(/sk_test_AbCdEfGhIjKlMnOpQrSt12345/);
      expect(out.markdown).toMatch(/<REDACTED:/);
    } finally {
      fs.rmSync(cwd, { recursive: true, force: true });
    }
  });

  it('generateClaudeMd handles a session whose targets are outside project_cwd safely', () => {
    const cwd = tmpDir('outside');
    try {
      const events = [
        { name: 'Read', target: 'C:\\Windows\\System32\\drivers\\etc\\hosts' },
        { name: 'Read', target: 'C:\\Windows\\System32\\drivers\\etc\\hosts' },
        { name: 'Read', target: '/etc/passwd' },
        { name: 'Read', target: '/etc/passwd' },
        { name: 'Read', target: '../../somewhere/else.js' },
        { name: 'Read', target: '../../somewhere/else.js' },
      ];
      const out = generateClaudeMd({
        session: { session_uuid: 'out', source_file: '/tmp/fake.jsonl' },
        project: { slug: 'demo', cwd },
        toolEvents: events,
        projectCwd: cwd,
        projectFiles: new Map(),
      });
      expect(out.markdown).toMatch(/outside project root/);
      expect(out.markdown).not.toMatch(/root:x:0:0/);
    } finally {
      fs.rmSync(cwd, { recursive: true, force: true });
    }
  });
});
