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

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const workerRoot = path.resolve(__dirname, '../..')
const agentRunUrl = 'https://1242739972938523.agentrun-data.cn-hangzhou.aliyuncs.com/agent-runtimes/AIPRO-SP/endpoints/Default/invocations/openai/v1/chat/completions'

function sse(lines) {
  return new Response(lines.join('\r\n'), {
    status: 200,
    headers: { 'Content-Type': 'text/event-stream' },
  })
}

function execMigrationOnce(db, file) {
  try {
    db.exec(fs.readFileSync(path.join(workerRoot, file), 'utf8'))
  } catch (error) {
    if (!/duplicate column|already exists/i.test(error?.message || '')) throw error
  }
}

test('skill run fails when model claims tool execution without tool calls', async (t) => {
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'aipros-fake-tool-'))
  process.env.DB_PATH = path.join(tempRoot, 'test.db')
  process.env.OUTPUTS_DIR = path.join(tempRoot, 'outputs')

  const originalFetch = globalThis.fetch
  globalThis.fetch = async () => sse([
    'data: {"choices":[{"delta":{"content":"已调用 create_html 工具生成 fake.html"},"finish_reason":"stop"}]}',
    '',
    'data: [DONE]',
    '',
  ])

  t.after(() => {
    globalThis.fetch = originalFetch
    fs.rmSync(tempRoot, { recursive: true, force: true })
  })

  const { default: app } = await import('../index.js')
  const { rawDB } = await import('../db.js')
  execMigrationOnce(rawDB, 'migrations/0010_runtime_engines.sql')
  execMigrationOnce(rawDB, 'migrations/0011_runtime_engine_api_key.sql')
  rawDB.prepare(`
    UPDATE runtime_engines
    SET enabled = 1,
        is_active = 1,
        endpoint_public_url = ?,
        api_key = ?
    WHERE code = 'agentrun'
  `).run(agentRunUrl, 'ak-secret')

  const res = await app.request('/api/skill-run/stream', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-User-Email': '494161546@qq.com',
    },
    body: JSON.stringify({ messages: [{ role: 'user', content: '请生成 HTML 文件' }] }),
  })

  assert.equal(res.status, 200)
  const text = await res.text()
  assert.match(text, /模型声称已调用工具/)
  assert.match(text, /"state":"failed"/)
})

test('local CLI runtime is not blocked by tracked tool claim enforcement', async (t) => {
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'aipros-local-cli-tool-claim-'))
  process.env.DB_PATH = path.join(tempRoot, 'test.db')
  process.env.OUTPUTS_DIR = path.join(tempRoot, 'outputs')

  t.after(() => {
    fs.rmSync(tempRoot, { recursive: true, force: true })
  })

  const { default: app } = await import('../index.js')
  const { rawDB } = await import('../db.js')
  execMigrationOnce(rawDB, 'migrations/0010_runtime_engines.sql')
  execMigrationOnce(rawDB, 'migrations/0014_local_runtime_agents.sql')
  rawDB.prepare(`
    UPDATE runtime_engines
    SET enabled = 0,
        is_active = 0
  `).run()
  rawDB.prepare(`
    UPDATE runtime_engines
    SET local_command = ?,
        local_args_json = ?,
        enabled = 1,
        is_active = 1,
        purpose_scopes_json = '["run"]'
    WHERE code = 'local-workbuddy'
  `).run(process.execPath, JSON.stringify(['-e', "process.stdout.write('文件已生成：report.json')"]))
  rawDB.prepare(`
    UPDATE runtime_agents
    SET runtime_code = 'local-workbuddy',
        applies_to_json = '["run"]',
        enabled = 1
    WHERE id = 'agent-skill-runner'
  `).run()

  const res = await app.request('/api/skill-run/stream', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-User-Email': '494161546@qq.com',
    },
    body: JSON.stringify({
      mode: 'run',
      systemPrompt: '# runner',
      messages: [{ role: 'user', content: '请生成 JSON 报告' }],
    }),
  })

  assert.equal(res.status, 200)
  const text = await res.text()
  assert.match(text, /文件已生成：report\.json/)
  assert.doesNotMatch(text, /模型声称已调用工具/)
  assert.match(text, /"state":"completed"/)
})

test('create mode returns runtime text even when it only describes future tool work', async (t) => {
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'aipros-fake-tool-future-'))
  process.env.DB_PATH = path.join(tempRoot, 'test.db')
  process.env.OUTPUTS_DIR = path.join(tempRoot, 'outputs')

  const originalFetch = globalThis.fetch
  globalThis.fetch = async () => sse([
    'data: {"choices":[{"delta":{"content":"现在，我将调用 write_file 工具生成 SKILL.md、examples.md、rubric.md。\\\\n\\\\n草稿已准备好，等待评测/上传确认。"},"finish_reason":"stop"}]}',
    '',
    'data: [DONE]',
    '',
  ])

  t.after(() => {
    globalThis.fetch = originalFetch
    fs.rmSync(tempRoot, { recursive: true, force: true })
  })

  const { default: app } = await import('../index.js')
  const { rawDB } = await import('../db.js')
  execMigrationOnce(rawDB, 'migrations/0010_runtime_engines.sql')
  execMigrationOnce(rawDB, 'migrations/0011_runtime_engine_api_key.sql')
  rawDB.prepare(`
    UPDATE runtime_engines
    SET enabled = 1,
        is_active = 1,
        endpoint_public_url = ?,
        api_key = ?
    WHERE code = 'agentrun'
  `).run(agentRunUrl, 'ak-secret')

  const res = await app.request('/api/skill-run/stream', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-User-Email': '494161546@qq.com',
    },
    body: JSON.stringify({ mode: 'create', messages: [{ role: 'user', content: '请生成标准 Skill 包' }] }),
  })

  assert.equal(res.status, 200)
  const text = await res.text()
  assert.match(text, /草稿已准备好/)
  assert.doesNotMatch(text, /"t":"error"/)
  assert.match(text, /"state":"completed"/)
})

test('create mode completes when AgentRun returns text without generated files', async (t) => {
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'aipros-create-no-files-'))
  process.env.DB_PATH = path.join(tempRoot, 'test.db')
  process.env.OUTPUTS_DIR = path.join(tempRoot, 'outputs')

  const originalFetch = globalThis.fetch
  globalThis.fetch = async () => sse([
    'data: {"choices":[{"delta":{"content":"我可以帮你创建 Skill，下面是一个示例草稿。"},"finish_reason":"stop"}]}',
    '',
    'data: [DONE]',
    '',
  ])

  t.after(() => {
    globalThis.fetch = originalFetch
    fs.rmSync(tempRoot, { recursive: true, force: true })
  })

  const { default: app } = await import('../index.js')
  const { rawDB } = await import('../db.js')
  execMigrationOnce(rawDB, 'migrations/0010_runtime_engines.sql')
  execMigrationOnce(rawDB, 'migrations/0011_runtime_engine_api_key.sql')
  rawDB.prepare(`
    UPDATE runtime_engines
    SET enabled = 1,
        is_active = 1,
        endpoint_public_url = ?,
        api_key = ?
    WHERE code = 'agentrun'
  `).run(agentRunUrl, 'ak-secret')

  const res = await app.request('/api/skill-run/stream', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-User-Email': '494161546@qq.com',
    },
    body: JSON.stringify({ mode: 'create', messages: [{ role: 'user', content: '请生成标准 Skill 包' }] }),
  })

  assert.equal(res.status, 200)
  const text = await res.text()
  assert.match(text, /下面是一个示例草稿/)
  assert.doesNotMatch(text, /"t":"error"/)
  assert.match(text, /"state":"completed"/)
})

test('create mode returns each session turn without checking prior package files', async (t) => {
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'aipros-create-stale-files-'))
  process.env.DB_PATH = path.join(tempRoot, 'test.db')
  process.env.OUTPUTS_DIR = path.join(tempRoot, 'outputs')
  const sessionId = 'sid_stale_files'
  const firstSpec = {
    title: '旧 Skill',
    name: 'old-skill',
    description: '用于旧流程的 Skill。',
    target_users: ['业务用户'],
    trigger_phrases: ['旧流程'],
    when_to_use: ['用户需要旧流程处理时。'],
    when_not_to_use: ['用户需要新流程处理时。'],
    inputs: [{ name: 'input', type: 'string', required: true, description: '输入材料' }],
    tools: [{ name: '无外部工具', purpose: '基于输入完成任务', required: false }],
    workflow: ['确认输入', '执行旧流程', '输出结果'],
    output_schema: { type: 'object' },
    boundaries: ['不得编造事实。'],
    examples: [{ name: '正常示例', input: { input: '示例' }, output: { summary: '结果' } }],
    evaluation_rubric: [{ dimension: '规格完整性', criteria: '字段完整', pass: '无缺失' }],
  }
  const firstPackage = [
    'skill.spec.json:',
    '```json',
    JSON.stringify(firstSpec),
    '```',
  ].join('\n')

  const originalFetch = globalThis.fetch
  let upstreamCalls = 0
  globalThis.fetch = async () => {
    upstreamCalls += 1
    const content = upstreamCalls === 1 ? firstPackage : '这里只是解释，没有写任何文件。'
    return sse([
      `data: ${JSON.stringify({ choices: [{ delta: { content }, finish_reason: 'stop' }] })}`,
      '',
      'data: [DONE]',
      '',
    ])
  }

  t.after(() => {
    globalThis.fetch = originalFetch
    fs.rmSync(tempRoot, { recursive: true, force: true })
  })

  const { default: app } = await import('../index.js')
  const { rawDB } = await import('../db.js')
  execMigrationOnce(rawDB, 'migrations/0010_runtime_engines.sql')
  execMigrationOnce(rawDB, 'migrations/0011_runtime_engine_api_key.sql')
  rawDB.prepare(`
    UPDATE runtime_engines
    SET enabled = 1,
        is_active = 1,
        endpoint_public_url = ?,
        api_key = ?
    WHERE code = 'agentrun'
  `).run(agentRunUrl, 'ak-secret')

  const firstRes = await app.request('/api/skill-run/stream', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-User-Email': '494161546@qq.com',
    },
    body: JSON.stringify({
      sessionId,
      mode: 'create',
      messages: [{ role: 'user', content: '请先生成一个标准 Skill 包' }],
    }),
  })

  assert.equal(firstRes.status, 200)
  const firstText = await firstRes.text()
  assert.match(firstText, /"state":"completed"/)
  assert.match(firstText, /skill\.spec\.json/)

  const secondRes = await app.request('/api/skill-run/stream', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-User-Email': '494161546@qq.com',
    },
    body: JSON.stringify({
      sessionId,
      mode: 'create',
      messages: [{ role: 'user', content: '请生成一个新的标准 Skill 包' }],
    }),
  })

  assert.equal(secondRes.status, 200)
  const text = await secondRes.text()
  assert.match(text, /这里只是解释，没有写任何文件/)
  assert.doesNotMatch(text, /"t":"error"/)
  assert.match(text, /"state":"completed"/)
})

test('create mode accepts explicit package file sections as normal runtime text', async (t) => {
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'aipros-create-text-package-'))
  process.env.DB_PATH = path.join(tempRoot, 'test.db')
  process.env.OUTPUTS_DIR = path.join(tempRoot, 'outputs')

  const content = [
    'SKILL.md:',
    '~~~markdown',
    '---',
    'name: meeting-minutes',
    'description: Use when users need meeting notes summarized.',
    '---',
    '# Meeting Minutes',
    'Run the workflow.',
    '~~~',
    '',
    'examples.md:',
    '~~~markdown',
    '# Examples',
    '- Input: meeting notes',
    '- Output: structured minutes',
    '~~~',
    '',
    'rubric.md:',
    '~~~markdown',
    '# Rubric',
    '- Pass: includes actions',
    '- Revise: missing owners',
    '~~~',
  ].join('\n')

  const originalFetch = globalThis.fetch
  globalThis.fetch = async () => sse([
    `data: ${JSON.stringify({ choices: [{ delta: { content }, finish_reason: 'stop' }] })}`,
    '',
    'data: [DONE]',
    '',
  ])

  t.after(() => {
    globalThis.fetch = originalFetch
    fs.rmSync(tempRoot, { recursive: true, force: true })
  })

  const { default: app } = await import('../index.js')
  const { rawDB } = await import('../db.js')
  execMigrationOnce(rawDB, 'migrations/0010_runtime_engines.sql')
  execMigrationOnce(rawDB, 'migrations/0011_runtime_engine_api_key.sql')
  rawDB.prepare(`
    UPDATE runtime_engines
    SET enabled = 1,
        is_active = 1,
        endpoint_public_url = ?,
        api_key = ?
    WHERE code = 'agentrun'
  `).run(agentRunUrl, 'ak-secret')

  const res = await app.request('/api/skill-run/stream', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-User-Email': '494161546@qq.com',
    },
    body: JSON.stringify({ mode: 'create', messages: [{ role: 'user', content: '请生成标准 Skill 包' }] }),
  })

  assert.equal(res.status, 200)
  const text = await res.text()
  assert.match(text, /Meeting Minutes/)
  assert.doesNotMatch(text, /"t":"error"/)
  assert.match(text, /"state":"completed"/)
})

test('create mode accepts arbitrary nested file fences without a platform spec requirement', async (t) => {
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'aipros-create-bold-text-package-'))
  process.env.DB_PATH = path.join(tempRoot, 'test.db')
  process.env.OUTPUTS_DIR = path.join(tempRoot, 'outputs')

  const content = [
    '草稿文件已准备好。',
    '',
    '**SKILL.md:**',
    '```markdown',
    '---',
    'name: 文本三点摘要',
    'description: 运营人员快速从长文本中提取核心要点。',
    '---',
    '# 文本三点摘要',
    '## 输出格式',
    '```json',
    '{"summary":"一句话总结","key_points":[],"risks":[]}',
    '```',
    '```',
    '',
    '**examples.md:**',
    '```markdown',
    '# 示例',
    '输出：',
    '```json',
    '{"summary":"示例"}',
    '```',
    '```',
    '',
    '**rubric.md:**',
    '```markdown',
    '# 评分',
    '- 通过：三字段完整',
    '```',
  ].join('\n')

  const originalFetch = globalThis.fetch
  globalThis.fetch = async () => sse([
    `data: ${JSON.stringify({ choices: [{ delta: { content }, finish_reason: 'stop' }] })}`,
    '',
    'data: [DONE]',
    '',
  ])

  t.after(() => {
    globalThis.fetch = originalFetch
    fs.rmSync(tempRoot, { recursive: true, force: true })
  })

  const { default: app } = await import('../index.js')
  const { rawDB } = await import('../db.js')
  execMigrationOnce(rawDB, 'migrations/0010_runtime_engines.sql')
  execMigrationOnce(rawDB, 'migrations/0011_runtime_engine_api_key.sql')
  rawDB.prepare(`
    UPDATE runtime_engines
    SET enabled = 1,
        is_active = 1,
        endpoint_public_url = ?,
        api_key = ?
    WHERE code = 'agentrun'
  `).run(agentRunUrl, 'ak-secret')

  const res = await app.request('/api/skill-run/stream', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-User-Email': '494161546@qq.com',
    },
    body: JSON.stringify({ mode: 'create', messages: [{ role: 'user', content: '请生成标准 Skill 包' }] }),
  })

  assert.equal(res.status, 200)
  const text = await res.text()
  assert.match(text, /文本三点摘要/)
  assert.doesNotMatch(text, /"t":"error"/)
  assert.match(text, /"state":"completed"/)
})
