import { describe, it, expect, afterEach } from 'vitest';
import { mkdtempSync, writeFileSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { assembleSystemPrompt } from './template.js';

describe('assembleSystemPrompt', () => {
  const tempDirs: string[] = [];

  function makeTempDir(): string {
    const dir = mkdtempSync(join(tmpdir(), 'flynn-prompt-test-'));
    tempDirs.push(dir);
    return dir;
  }

  afterEach(() => {
    for (const dir of tempDirs) {
      rmSync(dir, { recursive: true, force: true });
    }
    tempDirs.length = 0;
  });

  it('returns fallback when no files found', () => {
    const dir = makeTempDir();
    const result = assembleSystemPrompt({ searchDirs: [dir] });

    expect(result.prompt).toContain(
      'You are Flynn, a helpful personal AI assistant. Be direct, concise, and helpful. Use markdown when it improves readability.',
    );
    expect(result.loadedFiles).toEqual([]);
  });

  it('loads SOUL.md without section header', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'You are Flynn.');

    const result = assembleSystemPrompt({ searchDirs: [dir] });

    expect(result.prompt).toContain('You are Flynn.');
    expect(result.loadedFiles).toHaveLength(1);
    expect(result.loadedFiles[0]).toContain('SOUL.md');
  });

  it('loads AGENTS.md with "# Agent Instructions" section header', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'AGENTS.md'), 'Follow these rules.');

    const result = assembleSystemPrompt({ searchDirs: [dir] });

    expect(result.prompt).toContain('# Agent Instructions\n\nFollow these rules.');
    expect(result.loadedFiles).toHaveLength(1);
    expect(result.loadedFiles[0]).toContain('AGENTS.md');
  });

  it('loads multiple files in correct order', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'I am Flynn.');
    writeFileSync(join(dir, 'AGENTS.md'), 'Be helpful.');
    writeFileSync(join(dir, 'USER.md'), 'User likes cats.');

    const result = assembleSystemPrompt({ searchDirs: [dir] });

    expect(result.loadedFiles).toHaveLength(3);
    // Verify correct ordering: SOUL → AGENTS → USER
    expect(result.prompt).toContain(
      'I am Flynn.\n\n# Agent Instructions\n\nBe helpful.\n\n# User Context\n\nUser likes cats.',
    );
  });

  it('first search dir takes precedence over later dirs', () => {
    const dir1 = makeTempDir();
    const dir2 = makeTempDir();
    writeFileSync(join(dir1, 'SOUL.md'), 'Primary identity.');
    writeFileSync(join(dir2, 'SOUL.md'), 'Secondary identity.');

    const result = assembleSystemPrompt({ searchDirs: [dir1, dir2] });

    expect(result.prompt).toContain('Primary identity.');
    expect(result.loadedFiles).toHaveLength(1);
    expect(result.loadedFiles[0]).toContain(dir1);
  });

  it('falls through to later dir if file missing in first dir', () => {
    const dir1 = makeTempDir();
    const dir2 = makeTempDir();
    writeFileSync(join(dir2, 'SOUL.md'), 'Fallback identity.');

    const result = assembleSystemPrompt({ searchDirs: [dir1, dir2] });

    expect(result.prompt).toContain('Fallback identity.');
    expect(result.loadedFiles[0]).toContain(dir2);
  });

  it('extra sections are appended', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'Base identity.');

    const result = assembleSystemPrompt({
      searchDirs: [dir],
      extraSections: [
        { name: 'Custom Rules', content: 'Always be polite.' },
      ],
    });

    expect(result.prompt).toContain(
      'Base identity.\n\n# Custom Rules\n\nAlways be polite.',
    );
  });

  it('empty files are skipped', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), '');
    writeFileSync(join(dir, 'AGENTS.md'), '   ');

    const result = assembleSystemPrompt({ searchDirs: [dir] });

    expect(result.prompt).toContain(
      'You are Flynn, a helpful personal AI assistant. Be direct, concise, and helpful. Use markdown when it improves readability.',
    );
    expect(result.loadedFiles).toEqual([]);
  });

  it('empty extra sections are skipped', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'Base identity.');

    const result = assembleSystemPrompt({
      searchDirs: [dir],
      extraSections: [
        { name: 'Empty', content: '   ' },
        { name: 'Populated', content: 'Has content.' },
      ],
    });

    expect(result.prompt).toContain(
      'Base identity.\n\n# Populated\n\nHas content.',
    );
  });

  it('attempts all PROMPT_FILES', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'Soul.');
    writeFileSync(join(dir, 'AGENTS.md'), 'Agents.');
    writeFileSync(join(dir, 'IDENTITY.md'), 'Identity.');
    writeFileSync(join(dir, 'USER.md'), 'User.');
    writeFileSync(join(dir, 'TOOLS.md'), 'Tools.');

    const result = assembleSystemPrompt({ searchDirs: [dir] });

    expect(result.loadedFiles).toHaveLength(5);
    expect(result.prompt).toContain('Soul.');
    expect(result.prompt).toContain('# Agent Instructions\n\nAgents.');
    expect(result.prompt).toContain('# Identity Customization\n\nIdentity.');
    expect(result.prompt).toContain('# User Context\n\nUser.');
    expect(result.prompt).toContain('# Tool Instructions\n\nTools.');
  });

  it('trims whitespace from loaded file content', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), '\n  I am Flynn.  \n\n');

    const result = assembleSystemPrompt({ searchDirs: [dir] });

    expect(result.prompt).toContain('I am Flynn.');
  });

  it('mixes files from different search directories', () => {
    const dir1 = makeTempDir();
    const dir2 = makeTempDir();
    writeFileSync(join(dir1, 'SOUL.md'), 'Primary soul.');
    writeFileSync(join(dir2, 'AGENTS.md'), 'Agent rules.');

    const result = assembleSystemPrompt({ searchDirs: [dir1, dir2] });

    expect(result.loadedFiles).toHaveLength(2);
    expect(result.prompt).toContain('Primary soul.\n\n# Agent Instructions\n\nAgent rules.');
  });

  it('always includes Runtime Context section', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'I am Flynn.');
    const result = assembleSystemPrompt({ searchDirs: [dir] });
    expect(result.prompt).toContain('# Runtime Context');
    expect(result.prompt).toContain('Current date:');
    expect(result.prompt).toContain('Current time:');
  });

  it('uses normal as default context level', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'Soul.');
    writeFileSync(join(dir, 'AGENTS.md'), 'Agent rules.');

    const result = assembleSystemPrompt({
      searchDirs: [dir],
      extraSections: [{ name: 'Custom', content: 'Keep this.' }],
    });

    expect(result.prompt).toContain('Soul.');
    expect(result.prompt).toContain('# Agent Instructions\n\nAgent rules.');
    expect(result.prompt).toContain('# Custom\n\nKeep this.');
  });

  it('minimal loads SOUL plus runtime only', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'Soul only.');
    writeFileSync(join(dir, 'AGENTS.md'), 'Do not include.');
    writeFileSync(join(dir, 'IDENTITY.md'), 'Do not include.');
    writeFileSync(join(dir, 'USER.md'), 'Do not include.');
    writeFileSync(join(dir, 'TOOLS.md'), 'Do not include.');

    const result = assembleSystemPrompt({
      searchDirs: [dir],
      extraSections: [{ name: 'Custom', content: 'Do not include.' }],
      contextLevel: 'minimal',
    });

    expect(result.prompt).toContain('Soul only.');
    expect(result.prompt).toContain('# Runtime Context');
    expect(result.prompt).not.toContain('# Agent Instructions');
    expect(result.prompt).not.toContain('# Identity Customization');
    expect(result.prompt).not.toContain('# User Context');
    expect(result.prompt).not.toContain('# Tool Instructions');
    expect(result.prompt).not.toContain('# Custom');
  });

  it('normal keeps current template behavior', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'Soul.');
    writeFileSync(join(dir, 'AGENTS.md'), 'Agents.');
    writeFileSync(join(dir, 'IDENTITY.md'), 'Identity.');
    writeFileSync(join(dir, 'USER.md'), 'User.');
    writeFileSync(join(dir, 'TOOLS.md'), 'Tools.');

    const result = assembleSystemPrompt({
      searchDirs: [dir],
      contextLevel: 'normal',
    });

    expect(result.prompt).toContain('Soul.');
    expect(result.prompt).toContain('# Agent Instructions\n\nAgents.');
    expect(result.prompt).toContain('# Identity Customization\n\nIdentity.');
    expect(result.prompt).toContain('# User Context\n\nUser.');
    expect(result.prompt).toContain('# Tool Instructions\n\nTools.');
  });

  it('detailed includes extra sections', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'Soul.');

    const result = assembleSystemPrompt({
      searchDirs: [dir],
      extraSections: [{ name: 'Detailed Notes', content: 'Include me.' }],
      contextLevel: 'detailed',
    });

    expect(result.prompt).toContain('Soul.');
    expect(result.prompt).toContain('# Detailed Notes\n\nInclude me.');
  });

  it('debug appends prompt debug section with loaded files', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'Soul.');
    writeFileSync(join(dir, 'AGENTS.md'), 'Agents.');

    const result = assembleSystemPrompt({
      searchDirs: [dir],
      contextLevel: 'debug',
    });

    expect(result.prompt).toContain('# Prompt Debug');
    expect(result.prompt).toContain('Context level: debug');
    expect(result.prompt).toContain('Loaded files:');
    expect(result.prompt).toContain('SOUL.md');
    expect(result.prompt).toContain('AGENTS.md');
    expect(result.prompt).toContain('Directory resolution notes:');
  });

  it('minimal skips extra sections', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'Soul.');

    const result = assembleSystemPrompt({
      searchDirs: [dir],
      extraSections: [{ name: 'Skipped', content: 'Should not appear.' }],
      contextLevel: 'minimal',
    });

    expect(result.prompt).not.toContain('# Skipped');
  });

  it('injects truthfulness policy for standard mode', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'Soul.');

    const result = assembleSystemPrompt({
      searchDirs: [dir],
      truthfulnessMode: 'standard',
    });

    expect(result.prompt).toContain('## Truthfulness Policy');
  });

  it('injects strict truthfulness policy in strict mode', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'Soul.');

    const result = assembleSystemPrompt({
      searchDirs: [dir],
      truthfulnessMode: 'strict',
    });

    expect(result.prompt).toContain('STRICT MODE');
  });

  it('does not inject truthfulness policy in relaxed mode', () => {
    const dir = makeTempDir();
    writeFileSync(join(dir, 'SOUL.md'), 'Soul.');

    const result = assembleSystemPrompt({
      searchDirs: [dir],
      truthfulnessMode: 'relaxed',
    });

    expect(result.prompt).not.toContain('## Truthfulness Policy');
    expect(result.prompt).toContain('# Runtime Context');
  });
});
