import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';

import {
  slugify,
  personaNameFromMarkdown,
  loadBoardPersonas,
  loadPersonaOverrides,
  loadForgeConfig,
  getEffectivePersonas,
} from '../src/persona-loader.js';

describe('slugify', () => {
  it('converts to lowercase and replaces non-alphanumeric with hyphens', () => {
    expect(slugify('Chief Executive Officer')).toBe('chief-executive-officer');
  });

  it('strips leading and trailing hyphens', () => {
    expect(slugify('--hello--')).toBe('hello');
  });

  it('returns "persona" for empty string', () => {
    expect(slugify('')).toBe('persona');
  });

  it('handles special characters', () => {
    expect(slugify('CTO — Technical')).toBe('cto-technical');
  });
});

describe('personaNameFromMarkdown', () => {
  it('extracts name from heading', () => {
    expect(personaNameFromMarkdown('# CEO — Chief Executive Officer', 'FALLBACK')).toBe('CEO');
  });

  it('strips markdown heading markers', () => {
    expect(personaNameFromMarkdown('## CTO - Technical Lead', 'FALLBACK')).toBe('CTO');
  });

  it('returns fallback for empty content', () => {
    expect(personaNameFromMarkdown('', 'FALLBACK')).toBe('FALLBACK');
  });

  it('returns full heading if no separator', () => {
    expect(personaNameFromMarkdown('# SimpleTitle', 'FALLBACK')).toBe('SimpleTitle');
  });
});

describe('loadBoardPersonas', () => {
  let tmpDir: string;

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'forge-personas-'));
  });

  afterEach(() => {
    fs.rmSync(tmpDir, { recursive: true, force: true });
  });

  it('returns empty array for non-existent directory', () => {
    expect(loadBoardPersonas('/nonexistent')).toEqual([]);
  });

  it('loads personas from markdown files', () => {
    fs.writeFileSync(
      path.join(tmpDir, 'ceo.md'),
      '# CEO — Visionary Leader\n\nThe CEO sets direction.',
    );
    fs.writeFileSync(
      path.join(tmpDir, 'cto.md'),
      '# CTO — Technical Realist\n\nThe CTO evaluates feasibility.',
    );

    const personas = loadBoardPersonas(tmpDir);
    expect(personas).toHaveLength(2);
    expect(personas[0]!.name).toBe('CEO');
    expect(personas[0]!.slug).toBe('ceo');
    expect(personas[1]!.name).toBe('CTO');
  });

  it('sorts by filename', () => {
    fs.writeFileSync(path.join(tmpDir, 'z-last.md'), '# Z Last');
    fs.writeFileSync(path.join(tmpDir, 'a-first.md'), '# A First');

    const personas = loadBoardPersonas(tmpDir);
    expect(personas[0]!.slug).toBe('a-first');
    expect(personas[1]!.slug).toBe('z-last');
  });

  it('ignores non-markdown files', () => {
    fs.writeFileSync(path.join(tmpDir, 'notes.txt'), 'not a persona');
    fs.writeFileSync(path.join(tmpDir, 'ceo.md'), '# CEO');

    const personas = loadBoardPersonas(tmpDir);
    expect(personas).toHaveLength(1);
  });
});

describe('loadPersonaOverrides', () => {
  let tmpDir: string;

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'forge-overrides-'));
  });

  afterEach(() => {
    fs.rmSync(tmpDir, { recursive: true, force: true });
  });

  it('returns empty object when .forge/personas/ does not exist', () => {
    expect(loadPersonaOverrides(tmpDir)).toEqual({});
  });

  it('loads override files', () => {
    const overridesDir = path.join(tmpDir, '.forge', 'personas');
    fs.mkdirSync(overridesDir, { recursive: true });
    fs.writeFileSync(path.join(overridesDir, 'ceo.md'), 'Additional CEO context');

    const overrides = loadPersonaOverrides(tmpDir);
    expect(overrides['ceo']).toBe('Additional CEO context');
  });
});

describe('loadForgeConfig', () => {
  let tmpDir: string;

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'forge-config-'));
  });

  afterEach(() => {
    fs.rmSync(tmpDir, { recursive: true, force: true });
  });

  it('returns empty config when file does not exist', () => {
    expect(loadForgeConfig(tmpDir)).toEqual({});
  });

  it('parses board skipMembers', () => {
    const configDir = path.join(tmpDir, '.forge');
    fs.mkdirSync(configDir, { recursive: true });
    fs.writeFileSync(
      path.join(configDir, 'config.yaml'),
      'board:\n  skipMembers:\n    - cfo\n    - coo\n',
    );

    const config = loadForgeConfig(tmpDir);
    expect(config.board?.skipMembers).toEqual(['cfo', 'coo']);
  });
});

describe('getEffectivePersonas', () => {
  let tmpDir: string;
  let boardDir: string;

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'forge-effective-'));
    boardDir = path.join(tmpDir, 'board-agents');
    fs.mkdirSync(boardDir, { recursive: true });
    fs.writeFileSync(path.join(boardDir, 'ceo.md'), '# CEO — Visionary');
    fs.writeFileSync(path.join(boardDir, 'cto.md'), '# CTO — Technical');
    fs.writeFileSync(path.join(boardDir, 'cfo.md'), '# CFO — Financial');
  });

  afterEach(() => {
    fs.rmSync(tmpDir, { recursive: true, force: true });
  });

  it('returns all personas with no overrides or config', () => {
    const personas = getEffectivePersonas(tmpDir, boardDir);
    expect(personas).toHaveLength(3);
  });

  it('appends project overrides to base description', () => {
    const overridesDir = path.join(tmpDir, '.forge', 'personas');
    fs.mkdirSync(overridesDir, { recursive: true });
    fs.writeFileSync(path.join(overridesDir, 'ceo.md'), 'Focus on AI strategy');

    const personas = getEffectivePersonas(tmpDir, boardDir);
    const ceo = personas.find((p) => p.slug === 'ceo')!;
    expect(ceo.description).toContain('# CEO — Visionary');
    expect(ceo.description).toContain('Focus on AI strategy');
  });

  it('removes skipped members via config', () => {
    const configDir = path.join(tmpDir, '.forge');
    fs.mkdirSync(configDir, { recursive: true });
    fs.writeFileSync(path.join(configDir, 'config.yaml'), 'board:\n  skipMembers:\n    - cfo\n');

    const personas = getEffectivePersonas(tmpDir, boardDir);
    expect(personas).toHaveLength(2);
    expect(personas.find((p) => p.slug === 'cfo')).toBeUndefined();
  });
});
