import test from 'node:test'
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')

test('settings mounts platform skills instead of storing system skill text', () => {
  const api = fs.readFileSync(path.join(ROOT, 'src/services/api.js'), 'utf8')
  assert.match(api, /export async function listSkills/)
  assert.match(api, /export async function listRuntimes/)
  assert.match(api, /export async function saveRuntimeAgent/)

  const settings = fs.readFileSync(path.join(ROOT, 'src/pages/LlmSettings.jsx'), 'utf8')
  assert.match(settings, /loadPlatformSkills/)
  assert.match(settings, /挂载平台 Skill/)
  assert.match(settings, /skillId/)
  assert.doesNotMatch(settings, /SystemSkillPanel/)
  assert.doesNotMatch(settings, /可粘贴 SKILL\.md|导入本机默认|把 skill-creator 或 skill-evaluator 的 SKILL\.md/)
})

test('settings page separates runtime registration from model providers', () => {
  const settings = fs.readFileSync(path.join(ROOT, 'src/pages/LlmSettings.jsx'), 'utf8')
  const api = fs.readFileSync(path.join(ROOT, 'src/services/api.js'), 'utf8')

  assert.match(settings, /const RUNTIME_PROTOCOLS = \['agentrun', 'qoder-cloud-agent', 'local-cli'\]/)
  assert.match(settings, /QODER Cloud Agent/)
  assert.match(settings, /RuntimeRegistryPanel/)
  assert.match(settings, /智能体配置/)
  assert.match(settings, /智能体名称/)
  assert.match(settings, /智能体提示词/)
  assert.match(settings, /生效范围/)
  assert.match(settings, /模型配置/)
  assert.match(settings, /运行时注册/)
  assert.match(settings, /activeConfigTab === 'models'/)
  assert.match(api, /export async function listRuntimes/)
  assert.match(api, /export async function saveRuntimeAgent/)
  assert.doesNotMatch(settings, /protocol === ['"]qoder['"].*执行内核|Qoder 执行内核|Qoder Cloud Agents/s)
})

test('architecture document describes the current AgentRun lifecycle without legacy workspace copy', () => {
  const architecture = fs.readFileSync(path.join(ROOT, 'src/pages/Architecture.jsx'), 'utf8')

  assert.match(architecture, /AgentRun/)
  assert.match(architecture, /mode=create/)
  assert.match(architecture, /mode=run/)
  assert.match(architecture, /mode=evaluate/)
  assert.doesNotMatch(architecture, /SkillWorkspace|Workspace|my_skills_v1|localStorage\[my_skills|\.skill\.json|ALJ|接真实大模型/)
})

test('evaluation panel sends real evaluator-mode requests without local prompt injection', () => {
  const panel = fs.readFileSync(path.join(ROOT, 'src/components/EvaluationPanel.jsx'), 'utf8')

  assert.match(panel, /mode:\s*['"]evaluate['"]/, 'evaluation requests must use evaluate mode')
  assert.match(panel, /bodyObj\.packageKey\s*=\s*skill\.package_key/, 'evaluation must pass the real uploaded package key')
  assert.doesNotMatch(panel, /systemPrompt:\s*sessionPromptRef\.current/, 'evaluation must not send a frontend evaluator prompt')
  assert.doesNotMatch(panel, /qoder_eval_system_prompt|EVAL_SYSTEM_PROMPT/, 'frontend must not keep a local evaluator prompt')
})

test('all visible evaluation entry points open the shared evaluation overlay with a skill id', () => {
  const allSkills = fs.readFileSync(path.join(ROOT, 'src/pages/AllSkills.jsx'), 'utf8')
  const editPage = fs.readFileSync(path.join(ROOT, 'src/pages/SkillEditPage.jsx'), 'utf8')
  const evalOverlay = fs.readFileSync(path.join(ROOT, 'src/components/EvalOverlay.jsx'), 'utf8')
  const evalPage = fs.readFileSync(path.join(ROOT, 'src/pages/SkillEvalPage.jsx'), 'utf8')

  assert.match(allSkills, /handleEvaluatePreviewSkill\s*=\s*\(\)\s*=>\s*\{[\s\S]*?openOverlay\(['"]eval['"],\s*\{\s*skillId\s*\}\)/)
  assert.match(allSkills, /onEvaluate=\{handleEvaluatePreviewSkill\}/)
  assert.match(allSkills, /function SkillDetailModal\(\{[\s\S]*?onEvaluate[\s\S]*?\}\)/)
  assert.match(allSkills, /onClick=\{onEvaluate\}[\s\S]*?>\s*✓ 评测\s*</)

  assert.match(editPage, /openOverlay\(['"]eval['"],\s*\{\s*skillId:\s*skill\.id\s*\}\)/)
  assert.doesNotMatch(editPage, /import EvaluationPanel/)
  assert.doesNotMatch(editPage, /showEval|setShowEval/)

  assert.match(evalOverlay, /overlayProps\?\.skillId/)
  assert.match(evalPage, /const autoId = overlaySkillId \|\| location\.state\?\.autoEvalSkillId/)
})

test('sandbox overlay uses the dedicated run shell instead of the creator shell', () => {
  const app = fs.readFileSync(path.join(ROOT, 'src/App.jsx'), 'utf8')
  const overlay = fs.readFileSync(path.join(ROOT, 'src/components/SandboxOverlay.jsx'), 'utf8')
  const createOverlay = fs.readFileSync(path.join(ROOT, 'src/components/CreateChatOverlay.jsx'), 'utf8')
  const allSkills = fs.readFileSync(path.join(ROOT, 'src/pages/AllSkills.jsx'), 'utf8')
  const skillOps = fs.readFileSync(path.join(ROOT, 'src/pages/SkillOps.jsx'), 'utf8')

  assert.match(app, /import SandboxOverlay from ['"]\.\/components\/SandboxOverlay\.jsx['"]/)
  assert.match(app, /case ['"]sandbox['"]:\s*return <SandboxOverlay \/>/)
  assert.doesNotMatch(app, /case ['"]sandbox['"]:\s*return <CreateChatOverlay \/>/)
  assert.match(overlay, /<ChatOverlayShell title=['"]运行['"]/)
  assert.match(overlay, /<SkillSandboxPage overlayMode=\{true\} overlaySkillId=\{skillId\} \/>/)
  assert.doesNotMatch(createOverlay, /SkillSandboxPage|runSkillId|AI 创建 \/ 运行/)
  assert.match(allSkills, /openOverlay\(['"]sandbox['"],\s*\{\s*skillId:\s*previewSkill\.id\s*\}\)/)
  assert.match(skillOps, /openOverlay\(['"]sandbox['"],\s*\{\s*skillId\s*\}\)/)
  assert.doesNotMatch(allSkills, /openOverlay\(['"]create['"],\s*\{\s*runSkillId/)
  assert.doesNotMatch(skillOps, /openOverlay\(['"]create['"],\s*\{\s*runSkillId/)
})

test('skill sandbox runs through explicit runtime run mode with the real package key', () => {
  const sandbox = fs.readFileSync(path.join(ROOT, 'src/pages/SkillSandboxPage.jsx'), 'utf8')
  const api = fs.readFileSync(path.join(ROOT, 'src/services/api.js'), 'utf8')

  assert.match(sandbox, /mode:\s*['"]run['"]/, 'sandbox requests must use explicit run mode')
  assert.match(sandbox, /systemPrompt:\s*sessionPromptRef\.current/, 'run mode must pass the loaded Skill content as the runner prompt')
  assert.match(sandbox, /skillRef\.current\s*=\s*s/, 'sandbox must keep the freshly loaded Skill available for the first auto run')
  assert.match(sandbox, /const activeSkill = skillRef\.current \|\| skill/)
  assert.match(sandbox, /packageKey:\s*!agentSessionIdRef\.current && activeSkill\?\.package_key \? activeSkill\.package_key : undefined/)
  assert.doesNotMatch(api, /chatSkillStream|chatSkill\(|\/skills\/\$\{skillId\}\/chat/, 'frontend must not expose the legacy skill chat bypass')
})

test('create, evaluate and run share one live runtime timeline without heartbeat clearing progress', () => {
  const create = fs.readFileSync(path.join(ROOT, 'src/pages/CreateSkillChat.jsx'), 'utf8')
  const evaluate = fs.readFileSync(path.join(ROOT, 'src/components/EvaluationPanel.jsx'), 'utf8')
  const run = fs.readFileSync(path.join(ROOT, 'src/pages/SkillSandboxPage.jsx'), 'utf8')

  for (const source of [create, evaluate, run]) {
    assert.match(source, /RuntimeTimeline/)
    assert.match(source, /reduceRuntimeTimeline/)
    assert.match(source, /onEvent:/)
  }
  assert.doesNotMatch(create, /onHeartbeat:\s*\(\)\s*=>\s*setStreamTip\(['"]['"]\)/)
  assert.doesNotMatch(run, /onHeartbeat:\s*\(\)\s*=>\s*\{\s*setStreamError\(['"]['"]\)/)
})
