import test from 'node:test'
import assert from 'node:assert/strict'
import {
  buildQoderCloudPrompt,
  isQoderCloudAgentReady,
  missingQoderCloudAgentConfig,
  rowToQoderCloudAgentRuntime,
  runQoderCloudAgent,
  sanitizeQoderCloudAgentPatch,
} from './qoder-cloud-agent.js'

function cloudEventStream(events) {
  const lines = events.flatMap(event => [
    `id: ${event.id || 'evt'}`,
    `event: ${event.event}`,
    `data: ${JSON.stringify({ type: event.event, ...(event.data || {}) })}`,
    '',
  ])
  return new Response(lines.join('\n'), {
    status: 200,
    headers: { 'Content-Type': 'text/event-stream' },
  })
}

const readyRuntime = {
  code: 'qoder-cloud-agent',
  protocol: 'qoder-cloud-agent',
  enabled: 1,
  api_key: 'pat-secret',
  runtime_id: 'env_123',
  endpoint_id: 'agent_123',
  endpoint_public_url: 'https://api.qoder.test/api/v1/cloud',
}

test('QODER Cloud Agent readiness requires PAT, environment id and agent id', () => {
  assert.deepEqual(missingQoderCloudAgentConfig({}), ['api_key', 'environment_id', 'agent_id'])
  assert.equal(isQoderCloudAgentReady({
    enabled: 1,
    api_key: 'pat-secret',
    runtime_id: 'env_123',
    endpoint_id: 'agent_123',
  }), true)
})

test('rowToQoderCloudAgentRuntime hides PAT and maps ids to UI fields', () => {
  const runtime = rowToQoderCloudAgentRuntime({
    code: 'qoder-cloud-agent',
    name: 'QODER Cloud Agent',
    protocol: 'qoder-cloud-agent',
    enabled: 1,
    is_active: 1,
    api_key: 'qoder_pat_secret_value',
    runtime_id: 'env_123',
    endpoint_id: 'agent_123',
  })
  assert.equal(JSON.stringify(runtime).includes('qoder_pat_secret_value'), false)
  assert.equal(runtime.hasInvocationKey, true)
  assert.equal(runtime.environmentId, 'env_123')
  assert.equal(runtime.agentId, 'agent_123')
  assert.equal(runtime.ready, true)
})

test('sanitizeQoderCloudAgentPatch keeps stored PAT when secret input is blank', () => {
  const { patch, merged } = sanitizeQoderCloudAgentPatch({
    invocationKey: '',
    runtimeId: 'env_next',
    endpointId: 'agent_next',
    enabled: true,
  }, {
    api_key: 'stored-pat',
    runtime_id: 'env_old',
    endpoint_id: 'agent_old',
  })
  assert.equal(patch.api_key, undefined)
  assert.equal(merged.api_key, 'stored-pat')
  assert.equal(patch.runtime_id, 'env_next')
  assert.equal(patch.endpoint_id, 'agent_next')
})

test('buildQoderCloudPrompt includes system, agent and chat context in one prompt', () => {
  const prompt = buildQoderCloudPrompt({
    mode: 'create',
    agentPrompt: '创建智能体边界',
    systemPrompt: 'Skill 创建规则',
    chatMessages: [{ role: 'user', content: '做一个需求审核 Skill' }],
  })
  assert.match(prompt, /运行场景：create/)
  assert.match(prompt, /创建智能体边界/)
  assert.match(prompt, /Skill 创建规则/)
  assert.match(prompt, /做一个需求审核 Skill/)
  assert.match(prompt, /信息不足时可以先追问/)
  assert.doesNotMatch(prompt, /skill\.spec\.json|完整 JSON|编译标准包|唯一有效交付/)
})

test('runQoderCloudAgent creates a provider session and streams normalized native events', async (t) => {
  const originalFetch = globalThis.fetch
  const calls = []
  globalThis.fetch = async (url, options = {}) => {
    calls.push({ url: String(url), method: options.method || 'GET' })
    if (String(url).endsWith('/sessions') && options.method === 'POST') {
      return Response.json({ id: 'qoder_session_1' })
    }
    if (String(url).endsWith('/sessions/qoder_session_1/events') && options.method === 'POST') {
      return Response.json({ ok: true })
    }
    if (String(url).endsWith('/sessions/qoder_session_1/events/stream')) {
      return cloudEventStream([
        { id: 'evt_1', event: 'session.status_running', data: { turn_id: 'turn_1' } },
        { id: 'evt_2', event: 'agent.thinking', data: { turn_id: 'turn_1', content: [{ type: 'text', text: '分析中' }] } },
        { id: 'evt_3', event: 'tool.started', data: { turn_id: 'turn_1', tool_name: 'read', input: { path: 'SOP.md' } } },
        { id: 'evt_4', event: 'agent.message', data: { turn_id: 'turn_1', content: [{ type: 'text', text: '已完成' }] } },
        { id: 'evt_5', event: 'session.status_idle', data: { turn_id: 'turn_1' } },
      ])
    }
    throw new Error(`unexpected fetch: ${url}`)
  }
  t.after(() => { globalThis.fetch = originalFetch })

  const events = []
  for await (const event of runQoderCloudAgent({ runtimeRow: readyRuntime, prompt: '审核流程' })) {
    events.push(event)
  }

  assert.equal(events[0].t, 'provider_session')
  assert.equal(events[0].providerSessionId, 'qoder_session_1')
  assert.deepEqual(events.map(event => event.t), [
    'provider_session', 'status', 'reasoning_summary', 'tool_call', 'delta', 'provider_done',
  ])
  assert.ok(calls.some(call => call.url.endsWith('/sessions') && call.method === 'POST'))
})

test('runQoderCloudAgent reuses an existing provider session on the next turn', async (t) => {
  const originalFetch = globalThis.fetch
  let createCalls = 0
  const calls = []
  globalThis.fetch = async (url, options = {}) => {
    calls.push({ url: String(url), method: options.method || 'GET' })
    if (String(url).endsWith('/sessions') && options.method === 'POST') {
      createCalls += 1
      return Response.json({ id: 'should_not_be_created' })
    }
    if (String(url).endsWith('/sessions/qoder_existing/events') && options.method === 'POST') {
      return Response.json({ ok: true })
    }
    if (String(url).includes('/sessions/qoder_existing/events/stream')) {
      return cloudEventStream([
        { id: 'evt_6', event: 'agent.message', data: { turn_id: 'turn_2', content: [{ type: 'text', text: '续答完成' }] } },
        { id: 'evt_7', event: 'session.status_idle', data: { turn_id: 'turn_2' } },
      ])
    }
    throw new Error(`unexpected fetch: ${url}`)
  }
  t.after(() => { globalThis.fetch = originalFetch })

  const events = []
  for await (const event of runQoderCloudAgent({
    runtimeRow: readyRuntime,
    prompt: '这是续答',
    providerSessionId: 'qoder_existing',
    afterProviderEventId: 'evt_5',
  })) events.push(event)

  assert.equal(createCalls, 0)
  assert.equal(events[0].providerSessionId, 'qoder_existing')
  assert.ok(calls.some(call => call.url.includes('/sessions/qoder_existing/events')))
  assert.ok(calls.some(call => call.url.includes('after_id=evt_5')))
})
