import { Hono } from 'hono'
import { DB } from '../db.js'
import {
  AGENTRUN_CODE,
  isAgentRunReady,
  missingAgentRunConfig,
  rowToAgentRunProvider,
  sanitizeAgentRunPatch,
} from '../runtime/agentrun-config.js'
import {
  buildAgentRunChatCompletionsRequest,
  extractOpenAIChunkText,
  isAgentRunIntegrationUrl,
} from '../runtime/agentrun-openai.js'

const app = new Hono()

const ADMIN_EMAIL = '494161546@qq.com'
const ADMIN_PHONE = '13136092523'

function isAdmin(c) {
  const id = c.get('identity') || {}
  return id.email === ADMIN_EMAIL || id.phone === ADMIN_PHONE
}

function rowToProvider(row) {
  let models = []
  try { models = JSON.parse(row.models_json || '[]') } catch {}
  return {
    code:         row.code,
    name:         row.name,
    icon:         row.icon,
    protocol:     row.protocol,
    baseUrl:      row.base_url,
    defaultModel: row.default_model,
    models,
    enabled:      !!row.enabled,
    isActive:     !!row.is_active,
    hasKey:       !!row.api_key,
    keyPreview:   row.api_key ? `...${row.api_key.slice(-4)}` : null,
  }
}

async function getAgentRunRow() {
  return await DB.prepare("SELECT * FROM runtime_engines WHERE code = 'agentrun'").first()
}

async function updateAgentRunConfig(body) {
  const existing = await getAgentRunRow()
  if (!existing) throw new Error('AgentRun 配置不存在，请先执行数据库迁移')

  const { patch, merged } = sanitizeAgentRunPatch(body, existing)
  if (body.enabled === true && missingAgentRunConfig(merged).length > 0) {
    return {
      error: `启用 AgentRun 前，请补齐：${missingAgentRunConfig(merged).join(', ')}`,
      status: 422,
    }
  }

  const keys = Object.keys(patch)
  if (!keys.length) {
    return { ok: true, provider: rowToAgentRunProvider(existing) }
  }

  const sets = keys.map(k => `${k} = ?`)
  const vals = keys.map(k => patch[k])
  sets.push('updated_at = ?')
  vals.push(Math.floor(Date.now() / 1000))
  vals.push(AGENTRUN_CODE)
  await DB.prepare(`UPDATE runtime_engines SET ${sets.join(', ')} WHERE code = ?`).bind(...vals).run()
  const row = await getAgentRunRow()
  return { ok: true, provider: rowToAgentRunProvider(row) }
}

function isRetiredRuntimeProvider(rowOrCode) {
  const code = typeof rowOrCode === 'string' ? rowOrCode : rowOrCode?.code
  const protocol = typeof rowOrCode === 'string' ? '' : rowOrCode?.protocol
  return code === 'qoder' || protocol === 'qoder'
}

function retiredRuntimeResponse(c) {
  return c.json({ error: '旧执行内核已下线，Skill 创建、运行、评测统一使用 AgentRun' }, 410)
}

// GET / — 返回模型服务商 + AgentRun 执行内核（不含明文 key）
app.get('/', async (c) => {
  const { results: rows } = await DB.prepare(
    "SELECT * FROM llm_providers WHERE protocol IN ('openai','anthropic') ORDER BY CASE code WHEN 'qwen' THEN 1 WHEN 'openai' THEN 2 WHEN 'deepseek' THEN 3 WHEN 'moonshot' THEN 4 WHEN 'claude' THEN 5 WHEN 'zhipu' THEN 6 ELSE 50 END"
  ).all()
  const agentRun = await getAgentRunRow()
  const list = (rows || []).map(rowToProvider)
  if (agentRun) list.push(rowToAgentRunProvider(agentRun))
  return c.json({ list })
})

// 带超时和重试的 fetch：应对 Node 内置 undici 在 HTTP/2 + 阿里全球加速节点下
// 偶发的 keep-alive socket RST → 'terminated' / ECONNRESET 错误。
async function fetchWithRetry(url, options = {}, { timeoutMs = 15000, retries = 2 } = {}) {
  let lastErr
  for (let attempt = 0; attempt <= retries; attempt++) {
    const ctrl = new AbortController()
    const timer = setTimeout(() => ctrl.abort(new Error(`timeout after ${timeoutMs}ms`)), timeoutMs)
    try {
      const res = await fetch(url, {
        ...options,
        signal: ctrl.signal,
        // 关闭 keep-alive 池复用，避免拿到对端已 RST 的旧 socket
        keepalive: false,
        headers: { 'Connection': 'close', ...(options.headers || {}) },
      })
      clearTimeout(timer)
      return res
    } catch (e) {
      clearTimeout(timer)
      lastErr = e
      const msg = String(e?.message || '')
      const causeCode = e?.cause?.code || ''
      const causeMsg = e?.cause?.message || ''
      // 仅对 socket 层瞬态错误重试
      const transient = /terminated|ECONNRESET|EPIPE|UND_ERR_SOCKET|other side closed/i.test(msg + ' ' + causeCode + ' ' + causeMsg)
      if (attempt < retries && transient) {
        await new Promise(r => setTimeout(r, 200 * (attempt + 1)))
        continue
      }
      throw e
    }
  }
  throw lastErr
}

function describeFetchError(e) {
  const msg = String(e?.message || '')
  const causeCode = e?.cause?.code || ''
  const causeMsg = e?.cause?.message || ''
  if (/timeout/i.test(msg)) return `请求超时：${msg}`
  if (/terminated/i.test(msg) || causeCode === 'UND_ERR_SOCKET' || /ECONNRESET/.test(causeCode + causeMsg)) {
    return `连接被远端中断（socket terminated / ECONNRESET）。已自动重试仍失败，请稍后再试。${causeMsg ? '底层原因：' + causeMsg : ''}`
  }
  if (causeCode === 'ENOTFOUND' || /ENOTFOUND/.test(causeMsg)) return `DNS 解析失败：${causeMsg || msg}`
  if (causeCode === 'ECONNREFUSED' || /ECONNREFUSED/.test(causeMsg)) return `服务器拒绝连接：${causeMsg || msg}`
  if (/abort/i.test(msg)) return `请求被中止：${msg}`
  return msg + (causeMsg ? `（cause: ${causeMsg}）` : '')
}

// PATCH /:code — 更新配置（仅管理员）
app.patch('/:code', async (c) => {
  if (!isAdmin(c)) return c.json({ error: '无权限' }, 403)
  const code = c.req.param('code')
  if (code === AGENTRUN_CODE) {
    const body = await c.req.json()
    const result = await updateAgentRunConfig(body)
    if (result.error) return c.json({ error: result.error }, result.status || 400)
    return c.json(result)
  }

  const existing = await DB.prepare('SELECT * FROM llm_providers WHERE code = ?').bind(code).first()
  if (!existing) return c.json({ error: '服务商不存在' }, 404)
  if (isRetiredRuntimeProvider(existing)) return retiredRuntimeResponse(c)
  const body = await c.req.json()
  const sets = []
  const vals = []
  const incomingKey = typeof body.apiKey === 'string' ? body.apiKey.trim() : ''
  if (typeof body.apiKey === 'string' && body.apiKey.trim()) {
    sets.push('api_key = ?'); vals.push(incomingKey)
  }
  if (typeof body.baseUrl === 'string') {
    sets.push('base_url = ?'); vals.push(body.baseUrl.trim())
  }
  if (typeof body.defaultModel === 'string') {
    sets.push('default_model = ?'); vals.push(body.defaultModel)
  }
  if (typeof body.enabled === 'boolean') {
    sets.push('enabled = ?'); vals.push(body.enabled ? 1 : 0)
  }
  if (!sets.length) return c.json({ ok: true, message: '无更新' })
  sets.push('updated_at = ?'); vals.push(Math.floor(Date.now() / 1000))
  vals.push(code)
  await DB.prepare(`UPDATE llm_providers SET ${sets.join(', ')} WHERE code = ?`).bind(...vals).run()
  const row = await DB.prepare('SELECT * FROM llm_providers WHERE code = ?').bind(code).first()
  return c.json({ ok: true, provider: rowToProvider(row) })
})

// POST /:code/active — 设为激活（仅管理员）
app.post('/:code/active', async (c) => {
  if (!isAdmin(c)) return c.json({ error: '无权限' }, 403)
  const code = c.req.param('code')
  if (code === AGENTRUN_CODE) {
    const row = await getAgentRunRow()
    if (!row) return c.json({ error: 'AgentRun 配置不存在' }, 404)
    if (!isAgentRunReady(row)) {
      return c.json({ error: `请先补齐 AgentRun 配置：${missingAgentRunConfig(row).join(', ')}` }, 400)
    }
    await DB.prepare("UPDATE runtime_engines SET is_active = 0").run()
    await DB.prepare("UPDATE llm_providers SET is_active = 0 WHERE protocol = 'qoder'").run()
    await DB.prepare("UPDATE runtime_engines SET is_active = 1, enabled = 1, updated_at = ? WHERE code = 'agentrun'")
      .bind(Math.floor(Date.now() / 1000)).run()
    return c.json({ ok: true })
  }

  const target = await DB.prepare('SELECT * FROM llm_providers WHERE code = ?').bind(code).first()
  if (!target) return c.json({ error: '服务商不存在' }, 404)
  if (isRetiredRuntimeProvider(target)) return retiredRuntimeResponse(c)
  if (!target.api_key) return c.json({ error: '请先配置 API Key' }, 400)
  await DB.prepare("UPDATE llm_providers SET is_active = 0 WHERE protocol IN ('openai','anthropic')").run()
  await DB.prepare('UPDATE llm_providers SET is_active = 1, enabled = 1 WHERE code = ?').bind(code).run()
  return c.json({ ok: true })
})

// GET /:code/models — 拉取服务商真实模型列表（登录即可）
const CLAUDE_MODELS = [
  'claude-sonnet-4-20250514',
  'claude-3-5-sonnet-20241022',
  'claude-3-5-haiku-20241022',
  'claude-3-opus-20240229',
]

app.get('/:code/models', async (c) => {
  const code = c.req.param('code')
  if (code === AGENTRUN_CODE) {
    const row = await getAgentRunRow()
    const endpointName = row?.endpoint_name || 'default'
    return c.json({ models: [{ value: endpointName, label: endpointName }] })
  }

  const row = await DB.prepare('SELECT * FROM llm_providers WHERE code = ?').bind(code).first()
  if (!row) return c.json({ models: [], error: '服务商不存在' })
  if (isRetiredRuntimeProvider(row)) return c.json({ models: [], error: '旧执行内核已下线，请使用 AgentRun' }, 410)
  if (!row.api_key) return c.json({ models: [] })

  try {
    let models = []

    if (row.protocol === 'anthropic') {
      // Anthropic 没有公开的 /models 端点，返回硬编码列表
      models = CLAUDE_MODELS.map(m => ({ value: m, label: m }))
    } else {
      // OpenAI 兼容协议：把 base_url 中的 /chat/completions 替换为 /models
      let modelsUrl = (row.base_url || '').replace(/\/chat\/completions\/?$/, '/models')
      // 如果替换后没变化（可能 base_url 本身就是 /v1 结尾），追加 /models
      if (modelsUrl === row.base_url) {
        modelsUrl = modelsUrl.replace(/\/?$/, '/models')
      }

      const res = await fetchWithRetry(modelsUrl, {
        method: 'GET',
        headers: { 'Authorization': `Bearer ${row.api_key}` },
      }, { timeoutMs: 15000, retries: 2 })

      if (!res.ok) {
        const text = await res.text().catch(() => '')
        return c.json({ models: [], error: `HTTP ${res.status}: ${text.slice(0, 200)}` })
      }

      const data = await res.json()
      const list = Array.isArray(data.data) ? data.data : []
      models = list.map(m => ({ value: m.id, label: m.id })).sort((a, b) => a.label.localeCompare(b.label))
    }

    return c.json({ models })
  } catch (e) {
    return c.json({ models: [], error: describeFetchError(e) || '拉取模型列表超时或网络错误' })
  }
})

// POST /:code/test — 测试连通（仅管理员）
app.post('/:code/test', async (c) => {
  if (!isAdmin(c)) return c.json({ error: '无权限' }, 403)
  const code = c.req.param('code')
  if (code === AGENTRUN_CODE) {
    const row = await getAgentRunRow()
    if (!row) return c.json({ ok: false, message: 'AgentRun 配置不存在' })
    const missing = missingAgentRunConfig(row)
    if (missing.length) {
      return c.json({ ok: false, message: `请先补齐 AgentRun 配置：${missing.join(', ')}` })
    }

    const t0 = Date.now()
    try {
      if (isAgentRunIntegrationUrl(row.endpoint_public_url)) {
        const { url, options } = buildAgentRunChatCompletionsRequest({
          endpointPublicUrl: row.endpoint_public_url,
          invocationKey: row.api_key,
          sessionId: globalThis.crypto?.randomUUID?.() || `test-${Date.now()}`,
          messages: [{ role: 'user', content: 'Hi' }],
          stream: false,
        })
        const resp = await fetchWithRetry(url, options, { timeoutMs: 30000, retries: 1 })
        const data = await resp.json().catch(async () => ({ raw: await resp.text().catch(() => '') }))
        if (!resp.ok) return c.json({ ok: false, message: data?.error?.message || data?.message || data?.raw || `HTTP ${resp.status}` })
        return c.json({
          ok: true,
          model: row.endpoint_name || 'Default',
          latencyMs: Date.now() - t0,
          content: extractOpenAIChunkText(data).slice(0, 80) || 'AgentRun Runtime 已响应',
        })
      }

      const { AgentRuntimeClient, Config } = await import('@agentrun/sdk')
      const config = new Config({
        accessKeyId: row.access_key_id,
        accessKeySecret: row.access_key_secret,
        securityToken: row.security_token || undefined,
        accountId: row.account_id,
        regionId: row.region_id || 'cn-hangzhou',
        readTimeout: 30000,
        timeout: 30000,
      })
      const client = new AgentRuntimeClient(config)
      const resp = await client.invokeOpenai({
        agentRuntimeName: row.runtime_name,
        agentRuntimeEndpointName: row.endpoint_name || 'default',
        messages: [{ role: 'user', content: 'Hi' }],
        stream: false,
      })
      const content = resp?.choices?.[0]?.message?.content || ''
      return c.json({
        ok: true,
        model: row.endpoint_name || 'default',
        latencyMs: Date.now() - t0,
        content: String(content).slice(0, 80) || 'AgentRun Runtime 已响应',
      })
    } catch (e) {
      return c.json({ ok: false, message: describeFetchError(e) || e?.message || String(e) })
    }
  }

  const row = await DB.prepare('SELECT * FROM llm_providers WHERE code = ?').bind(code).first()
  if (!row) return c.json({ error: '服务商不存在' }, 404)
  if (isRetiredRuntimeProvider(row)) return retiredRuntimeResponse(c)
  if (!row.api_key) return c.json({ ok: false, message: '未配置 API Key' })

  const t0 = Date.now()
  try {
    let res, data
    if (row.protocol === 'anthropic') {
      res = await fetchWithRetry(row.base_url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': row.api_key,
          'anthropic-version': '2023-06-01',
        },
        body: JSON.stringify({
          model: row.default_model,
          max_tokens: 16,
          messages: [{ role: 'user', content: 'Hi' }],
        }),
      }, { timeoutMs: 20000, retries: 2 })
      data = await res.json()
      if (!res.ok) return c.json({ ok: false, message: data?.error?.message || `HTTP ${res.status}` })
      return c.json({ ok: true, model: data.model, latencyMs: Date.now() - t0, content: data.content?.[0]?.text?.slice(0, 80) || '' })
    } else {
      res = await fetchWithRetry(row.base_url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${row.api_key}`,
        },
        body: JSON.stringify({
          model: row.default_model,
          max_tokens: 16,
          messages: [{ role: 'user', content: 'Hi' }],
        }),
      }, { timeoutMs: 20000, retries: 2 })
      data = await res.json()
      if (!res.ok) return c.json({ ok: false, message: data?.error?.message || `HTTP ${res.status}` })
      return c.json({ ok: true, model: data.model, latencyMs: Date.now() - t0, content: data.choices?.[0]?.message?.content?.slice(0, 80) || '' })
    }
  } catch (e) {
    return c.json({ ok: false, message: describeFetchError(e) })
  }
})

export default app
