/* Bad Case 机制自动化测试
 * 运行：node tests/badcase_test.js
 * 方法：mock localStorage/document，在 Node 中加载 app.js 核心逻辑，
 * 模拟用户反馈 Bad Case 后再次匹配，验证「排除 / 降权」是否真实生效。
 */
const fs = require('fs');
const vm = require('vm');
const path = require('path');

// ---------- mock 浏览器环境 ----------
const store = {};
globalThis.localStorage = {
    getItem: k => (k in store ? store[k] : null),
    setItem: (k, v) => { store[k] = String(v); },
    removeItem: k => { delete store[k]; }
};
globalThis.document = { addEventListener: () => {}, querySelectorAll: () => [] };

// ---------- 加载 app.js ----------
const appPath = path.join(__dirname, '..', 'app.js');
vm.runInThisContext(fs.readFileSync(appPath, 'utf8'));

// ---------- 工具 ----------
function reset() {
    for (const k of Object.keys(store)) delete store[k];
    discoveredChannels.length = 0;
    saveDiscoveredChannels();
}
function recordBadCase(game, channel, reason, input) {
    const data = loadFeedback();
    data.bad.push({ reason, input, game, wrongChannel: channel, recommended: channel, time: new Date().toISOString() });
    saveFeedback(data);
}
let pass = 0, fail = 0;
function assert(name, cond, detail) {
    const ok = !!cond;
    console.log(`${ok ? '✅' : '❌'} ${name}${detail ? '  →  ' + detail : ''}`);
    ok ? pass++ : fail++;
}

console.log('═══════════════════════════════════════════');
console.log('实测一：本地频道被反馈「推荐频道不匹配」后是否被排除');
console.log('═══════════════════════════════════════════');
reset();
const text1 = '刚玩逆水寒手游，想找几个新手一起打副本。';
const p1 = analyzeInput(text1);
console.log(`[输入] ${text1}`);
console.log(`[画像] 游戏=${p1.game} 意图=${p1.intent} 阶段=${p1.stage} 标签=[${p1.tags.join(',')}]`);
const m1 = matchChannels(p1);
console.log(`[首次匹配] 推荐「${m1.best.channel.name}」 ${m1.best.total}分 (${matchLevel(m1.best.total)}) 候选=${m1.candidateCount}个`);
assert('首次能正常推荐新手组队频道', m1.status === 'ok' && m1.best.channel.name === '逆水寒手游｜新手组队');

recordBadCase(p1.game, m1.best.channel.name, '推荐频道不匹配', text1);
const m2 = matchChannels(p1);
console.log(`[反馈后匹配] 推荐「${m2.best ? m2.best.channel.name : '无'}」 ${m2.best ? m2.best.total + '分' : ''} 生效排除规则=${m2.appliedRules}条 候选=${m2.candidateCount}个`);
assert('反馈后「新手组队」被排除出候选池', m2.candidates.every(c => c.channel.name !== '逆水寒手游｜新手组队'));
assert('反馈后推荐结果改变（换了频道）', m2.best && m2.best.channel.name !== '逆水寒手游｜新手组队');
assert('排除规则计数正确（appliedRules=1）', m2.appliedRules === 1);
assert('排除后剩余候选低于阈值 → 不强行推荐（below_threshold 兜底）', m2.status === 'below_threshold' && m2.best.total < MATCH_THRESHOLD);

console.log('');
console.log('═══════════════════════════════════════════');
console.log('实测二：反馈「意图识别错误」不应触发排除（规则精确性）');
console.log('═══════════════════════════════════════════');
reset();
const m3 = matchChannels(p1);
recordBadCase(p1.game, m3.best.channel.name, '意图识别错误', text1);
const m4 = matchChannels(p1);
console.log(`[反馈后匹配] 推荐「${m4.best.channel.name}」 ${m4.best.total}分 生效排除规则=${m4.appliedRules}条`);
assert('「意图识别错误」不触发频道排除', m4.appliedRules === 0);
assert('频道仍可被推荐（不被误伤）', m4.best.channel.name === m3.best.channel.name);

console.log('');
console.log('═══════════════════════════════════════════');
console.log('实测三：反馈「游戏识别错误」同样触发排除');
console.log('═══════════════════════════════════════════');
reset();
recordBadCase(p1.game, '逆水寒手游｜新手组队', '游戏识别错误', text1);
const m5 = matchChannels(p1);
console.log(`[反馈后匹配] 推荐「${m5.best ? m5.best.channel.name : '无'}」 生效排除规则=${m5.appliedRules}条`);
assert('「游戏识别错误」触发排除', m5.appliedRules === 1 && m5.candidates.every(c => c.channel.name !== '逆水寒手游｜新手组队'));

console.log('');
console.log('═══════════════════════════════════════════');
console.log('实测四：AI 发现频道被反馈后的降权机制');
console.log('═══════════════════════════════════════════');
reset();
const text4 = '最近在玩一个叫幻塔的游戏，想交流一下';
const p4 = analyzeInput(text4);
const cov = checkChannelCoverage(p4);
console.log(`[输入] ${text4}`);
console.log(`[画像] 游戏=${p4.game} 意图=${p4.intent} 阶段=${p4.stage}`);
console.log(`[覆盖检测] coverage=${cov.coverage} shouldExpand=${cov.shouldExpand}`);
assert('幻塔在频道库中不存在（not_found）', cov.coverage === 'not_found' && cov.shouldExpand);

discoveredChannels.push({ name: '幻塔｜玩家讨论区', game: '幻塔', tags: ['幻塔', '游戏讨论', '交流'], source: 'AI Discovery', confidence: 0.8 });
saveDiscoveredChannels();
const m6 = matchChannels(p4);
const s0 = m6.best.total;
console.log(`[首次匹配] 推荐「${m6.best.channel.name}」(AI Discovery) ${s0}分`);
assert('AI 发现频道首次可被推荐', m6.best && m6.best.channel.source === 'AI Discovery');

recordBadCase('幻塔', '幻塔｜玩家讨论区', '推荐频道不匹配', text4);
const m7 = matchChannels(p4);
const s1 = m7.best ? m7.best.total : -1;
console.log(`[第1次反馈] 分数 ${s0} → ${s1}（应降 15 分） lowConfidence=${m7.best ? m7.best.lowConfidence : '?'}`);
assert('第1次反馈后降 15 分', s1 === s0 - 15);
assert('第1次反馈未标记低置信', m7.best && m7.best.lowConfidence === false);

recordBadCase('幻塔', '幻塔｜玩家讨论区', '推荐频道不匹配', text4);
const m8 = matchChannels(p4);
const s2 = m8.best ? m8.best.total : -1;
console.log(`[第2次反馈] 分数 ${s0} → ${s2}（应降 30 分） lowConfidence=${m8.best ? m8.best.lowConfidence : '?'}`);
assert('第2次反馈后共降 30 分', s2 === s0 - 30);
assert('第2次反馈后标记为低置信频道', m8.best && m8.best.lowConfidence === true);

console.log('');
console.log('═══════════════════════════════════════════');
console.log('实测五：某游戏全部频道被排除后 → 不强行推荐（兜底）');
console.log('═══════════════════════════════════════════');
reset();
// 动态排除该游戏的全部频道（频道库会扩充，不能写死数量）
const nshChannels = getAllChannels().filter(c => c.game === '逆水寒手游');
console.log(`[准备] 逆水寒手游共有 ${nshChannels.length} 个频道，全部标记为 Bad Case`);
nshChannels.forEach(c => recordBadCase(p1.game, c.name, '推荐频道不匹配', text1));
const m9 = matchChannels(p1);
console.log(`[匹配结果] 状态=${m9.status} 生效排除规则=${m9.appliedRules}条`);
assert('全部被排除后状态为 no_channel（不推荐其他游戏）', m9.status === 'no_channel');

console.log('');
console.log(`结果：${pass} 项通过 / ${fail} 项失败`);
if (fail > 0) process.exit(1);
