import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { parseRoleFile, loadRoles, loadRole, buildAnalysisPrompt, buildLocateLinePrompt, getRoleIntro } from '../roles.js';

const SAMPLE = `---
name: Tester
side: attack
focus: logic
badge: "🔮"
color: "#3B82F6"
personality: 冷靜嚴謹
---

# Tester

審查重點：邊界與空值。`;

describe('parseRoleFile', () => {
  it('parses frontmatter fields and trims the body', () => {
    const role = parseRoleFile(SAMPLE);
    assert.equal(role.name, 'Tester');
    assert.equal(role.side, 'attack');
    assert.equal(role.focus, 'logic');
    assert.equal(role.badge, '🔮');
    assert.equal(role.body, '# Tester\n\n審查重點：邊界與空值。');
  });

  it('tolerates CRLF line endings', () => {
    const role = parseRoleFile(SAMPLE.replace(/\n/g, '\r\n'));
    assert.equal(role.name, 'Tester');
    assert.equal(role.focus, 'logic');
  });

  it('throws when frontmatter is missing', () => {
    assert.throws(() => parseRoleFile('# no frontmatter'), /frontmatter/);
  });
});

describe('loadRoles', () => {
  it('loads only attack-side roles', () => {
    const roles = loadRoles();
    assert.ok(roles.length > 0);
    assert.ok(roles.every(r => r.side === 'attack'));
  });

  it('includes the expected attacker roster and excludes the defender', () => {
    const names = loadRoles().map(r => r.name);
    for (const expected of ['Bard', 'Mage', 'Rogue', 'Assassin', 'Leo', 'Maya']) {
      assert.ok(names.includes(expected), `missing ${expected}`);
    }
    assert.ok(!names.includes('Paladin'), 'Paladin must not be an attacker');
  });
});

describe('loadRole', () => {
  it('returns the defender role by name, case-insensitively', () => {
    const paladin = loadRole('paladin');
    assert.equal(paladin.name, 'Paladin');
    assert.equal(paladin.side, 'defend');
  });

  it('returns null for an unknown role', () => {
    assert.equal(loadRole('nobody'), null);
  });
});

describe('buildAnalysisPrompt', () => {
  it('embeds the role name in the JSON contract and persona/body', () => {
    const prompt = buildAnalysisPrompt(parseRoleFile(SAMPLE));
    assert.match(prompt, /"role": "Tester"/);
    assert.match(prompt, /"problem":/);
    assert.match(prompt, /有問題的原因/);
    assert.match(prompt, /冷靜嚴謹/);
    assert.match(prompt, /審查重點：邊界與空值/);
    assert.match(prompt, /只回傳 JSON 陣列/);
  });

  it('falls back to a default when focus is missing instead of showing undefined', () => {
    const prompt = buildAnalysisPrompt({ name: 'NoFocus', body: 'x' });
    assert.doesNotMatch(prompt, /undefined/);
  });
});

describe('buildAnalysisPrompt 行號要求', () => {
  it('requires a line number in location', () => {
    const prompt = buildAnalysisPrompt(parseRoleFile(SAMPLE));
    assert.match(prompt, /行號為必填/);
    assert.match(prompt, /每一條問題都必須帶行號/);
  });
});

describe('buildLocateLinePrompt', () => {
  it('asks the same role to return a JSON line number', () => {
    const prompt = buildLocateLinePrompt({ name: 'Maya', badge: '🧪', focus: 'testing' });
    assert.match(prompt, /Maya/);
    assert.match(prompt, /找出.*行號|實際行號/);
    assert.match(prompt, /\{"line": 數字\}/);
  });

  it('tolerates a bare role object without badge/focus', () => {
    const prompt = buildLocateLinePrompt({ name: 'Leo' });
    assert.match(prompt, /Leo/);
    assert.doesNotMatch(prompt, /undefined/);
  });
});

describe('getRoleIntro', () => {
  it('renders a table row per role with its badge', () => {
    const intro = getRoleIntro([parseRoleFile(SAMPLE)]);
    assert.match(intro, /🔮 Tester/);
    assert.match(intro, /logic/);
  });

  it('renders empty cells instead of undefined when focus/personality are missing', () => {
    const intro = getRoleIntro([{ name: 'Bare' }]);
    assert.match(intro, /Bare/);
    assert.doesNotMatch(intro, /undefined/);
  });
});
