// Dify 应用级 Service API（/v1）封装
// 鉴权：Authorization: Bearer <api_key>   —— api_key 来自 /business/apps/<id>/api-key
// user 字段：'user=' + cookie 中的 access_token
// 主要用于应用执行页：拉取应用信息 / 表单参数 / 触发流式执行
import { DIFY_V1_PREFIX } from '@/config'

// 登录后端通过 Set-Cookie 种在浏览器上的会话 token
// 兼容多种存储位置和命名（不同 Dify 版本/部署方式可能不同）
const ACCESS_TOKEN_KEYS = [
  'access_token',
  'console_token',
  'webapp_access_token',
  'token',
  'auth_token',
]

// 无效 token 黑名单（之前测试时可能被存进 localStorage）
const INVALID_TOKEN_VALUES = new Set([
  'no_auth', 'null', 'undefined', 'guest', 'anonymous', 'false', '',
])

function isValidToken(v) {
  if (!v) return false
  if (INVALID_TOKEN_VALUES.has(v)) return false
  if (v.length < 10) return false
  // 允许 JWT 字符集：字母数字 + -_ + .（JWT 有 2 个点）+ =（base64 padding）
  if (/^[\w\-./+=]+$/.test(v) === false) return false
  return true
}

/**
 * 从 localStorage / document.cookie 中读取 access_token。
 * 优先级：localStorage（多种 key）→ cookie（多种 name）→ 生成 UUID 兜底
 * 注意：HttpOnly cookie 不会被 document.cookie 列出，所以优先靠 localStorage。
 */
function getAccessTokenFromCookie() {
  if (typeof window === 'undefined') return ''

  const found = []

  // 1) 优先从 localStorage 读（Dify 前端的常见做法，不受 HttpOnly 限制）
  try {
    for (const k of ACCESS_TOKEN_KEYS) {
      const v = window.localStorage.getItem(k)
      if (v != null) found.push({ source: 'ls:' + k, value: v, valid: isValidToken(v) })
      if (isValidToken(v)) return v
    }
  } catch (_) { /* localStorage 不可用 */ }

  // 2) 兜底：document.cookie（非 HttpOnly 的情况下能读到）
  //    注意：必须按 ACCESS_TOKEN_KEYS 优先级查找，而不是按 cookie 字符串里的出现顺序，
  //    否则 `token=no_auth` 这种垃圾值会先匹配上，把真正的 `access_token=eyJ...` 盖掉。
  try {
    if (document.cookie) {
      const cookieMap = {}
      for (const c of document.cookie.split(';')) {
        const [k, ...rest] = c.trim().split('=')
        if (k) cookieMap[k] = decodeURIComponent(rest.join('='))
      }
      for (const k of ACCESS_TOKEN_KEYS) {
        const v = cookieMap[k]
        if (v != null) {
          found.push({ source: 'cookie:' + k, value: v, valid: isValidToken(v) })
          if (isValidToken(v)) return v
        }
      }
    }
  } catch (_) { /* noop */ }

  // 调试：把找到的所有 token 候选都打出来，一眼看清谁被过滤了
  if (typeof console !== 'undefined') {
    console.log('[getAccessTokenFromCookie] candidates →', found)
  }

  // 3) 最终兜底：生成 UUID 存到 localStorage
  try {
    let uuid = window.localStorage.getItem('dify_webapp_user_uuid')
    if (!uuid || !isValidToken(uuid)) {
      uuid = ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
        (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4)).toString(16)
      )
      window.localStorage.setItem('dify_webapp_user_uuid', uuid)
    }
    return uuid
  } catch (_) { /* noop */ }

  return ''
}

/**
 * 构造工作流请求体中的 user 字段
 * 规则：'user=' + access_token (与 Dify 后端约定)
 */
export function buildWorkflowUser() {
  const token = getAccessTokenFromCookie()
  return token ? `user=${token}` : ''
}

/**
 * 通用 Dify 鉴权 fetch
 *
 * **不发送 cookie**：Dify Service API（/v1/*）采用 Bearer token（Authorization 头）
 *  + body 中的 user 字段双重鉴权，不需要 cookie。如果带 credentials: 'include'，
 *  浏览器会要求后端 preflight 返回 Access-Control-Allow-Credentials: true，
 *  不配置就会触发 CORS 拒绝 → 真实请求不发。
 *
 * @param {string} url 完整 URL
 * @param {string} apiKey 应用 API Key（来自 /business/apps/<id>/api-key）
 * @param {object} options fetch 选项
 * @returns {Promise<Response>} 原生 Response 对象
 */
async function difyFetch(url, apiKey, options = {}) {
  const res = await fetch(url, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${apiKey}`,
      Accept: 'text/event-stream',
      ...options.headers,
    },
    // 关键：omit 而不是 include，避免触发 Access-Control-Allow-Credentials 检查
    credentials: 'omit',
  })
  if (!res.ok) {
    const err = await res.json().catch(() => ({}))
    throw {
      code: err.code || res.status,
      message: err.message || `Dify request failed (${res.status})`,
      status: res.status
    }
  }
  return res
}

// ===== 应用配置 =====

async function difyFormFetch(url, apiKey, formData, options = {}) {
  const { headers, ...restOptions } = options
  const res = await fetch(url, {
    ...restOptions,
    method: restOptions.method || 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      ...headers,
    },
    body: formData,
    credentials: 'omit',
  })
  if (!res.ok) {
    const err = await res.json().catch(() => ({}))
    throw {
      code: err.code || res.status,
      message: err.message || `Dify request failed (${res.status})`,
      status: res.status
    }
  }
  return res
}

/**
 * GET /v1/info - app basic info.
 * Returns: { name, description, tags, mode, author_name }
 */
export const getDifyAppInfo = (apiKey, options = {}) => {
  return difyFetch(`${DIFY_V1_PREFIX}/info`, apiKey, { method: 'GET', ...options }).then(r => r.json())
}

/**
 * GET /v1/parameters  ——  应用输入参数（user_input_form 等）
 * 返回：{ opening_statement, suggested_questions, user_input_form, file_upload, system_parameters, ... }
 */
export const getDifyAppParameters = (apiKey, options = {}) => {
  return difyFetch(`${DIFY_V1_PREFIX}/parameters`, apiKey, { method: 'GET', ...options }).then(r => r.json())
}

/**
 * GET /v1/site  ——  WebApp 设置
 */
export const getDifyAppSite = (apiKey, options = {}) => {
  return difyFetch(`${DIFY_V1_PREFIX}/site`, apiKey, { method: 'GET', ...options }).then(r => r.json())
}

/**
 * 获取应用完整信息：合并 /v1/info（基本元信息）+ /v1/site（图标、主题、站点信息）
 * @param {string} apiKey 应用 API Key（来自 /business/apps/<id>/api-key）
 * @returns {Promise<{
 *   name, description, tags, mode, author_name,
 *   title, icon_type, icon, icon_background, icon_url, chat_color_theme, show_workflow_steps
 * }>}
 *
 * 即使其中一个失败（站点未配置等），也会尽量返回另一个的字段。
 */
export const getAppFullInfo = async (apiKey, options = {}) => {
  const [infoRes, siteRes] = await Promise.allSettled([
    getDifyAppInfo(apiKey, options),
    getDifyAppSite(apiKey, options),
  ])
  const info = infoRes.status === 'fulfilled' ? (infoRes.value || {}) : {}
  const site = siteRes.status === 'fulfilled' ? (siteRes.value || {}) : {}

  return {
    // ---- /v1/info ----
    name: info.name || site.title || '智能体',
    description: info.description || site.description || '',
    tags: Array.isArray(info.tags) ? info.tags : [],
    mode: info.mode || '',
    author_name: info.author_name || '',
    // ---- /v1/site ----
    title: site.title || info.name || '智能体',
    icon_type: site.icon_type || 'emoji',
    icon: site.icon || '',
    icon_background: site.icon_background || '#FFEAD5',
    icon_url: site.icon_url || null,
    chat_color_theme: site.chat_color_theme || '',
    chat_color_theme_inverted: !!site.chat_color_theme_inverted,
    show_workflow_steps: !!site.show_workflow_steps,
    use_icon_as_answer_icon: !!site.use_icon_as_answer_icon,
    copyright: site.copyright || '',
    privacy_policy: site.privacy_policy || '',
    custom_disclaimer: site.custom_disclaimer || '',
    default_language: site.default_language || 'zh-Hans',
    // ---- 错误聚合（供 UI 决策）----
    _errors: {
      info: infoRes.status === 'rejected' ? infoRes.reason?.message : null,
      site: siteRes.status === 'rejected' ? siteRes.reason?.message : null,
    },
  }
}

// ===== 工作流执行 =====

/**
 * POST /v1/workflows/run  ——  流式执行工作流
 *
 * - Authorization:  Bearer <apiKey>           ← apiKey 来自 /business/apps/<id>/api-key
 * - request body:   { inputs, response_mode: 'streaming', user: 'user=<access_token>' }
 *
 * @param {string} apiKey 应用 API Key
 * @param {object} inputs 用户输入的 inputs
 * @param {string} [user] 可选，user 字段。默认自动从 cookie 中读取 access_token 并拼接 'user=' 前缀
 * @returns {Promise<Response>} 返回 SSE 流（不调用 .json()）
 */
/**
 * POST /files/upload - upload one file with multipart/form-data.
 * Returns: { id, name, size, extension, mime_type, created_by, created_at, ... }
 */
export const uploadFile = (apiKey, file, user, options = {}) => {
  const finalUser = user || buildWorkflowUser() || 'webapp-user'
  const formData = new FormData()
  formData.append('file', file)
  formData.append('user', finalUser)

  return difyFormFetch(`${DIFY_V1_PREFIX}/files/upload`, apiKey, formData, options).then(r => r.json())
}

export const runWorkflow = (apiKey, inputs, user, options = {}) => {
  const finalUser = user || buildWorkflowUser() || 'webapp-user'
  const body = {
    inputs,
    response_mode: 'streaming',
    user: finalUser,
  }
  // 调试日志：开 dev 时直接看实际请求体结构
  if (typeof console !== 'undefined') {
    console.log('[runWorkflow] final body →', JSON.stringify(body))
  }
  return difyFetch(`${DIFY_V1_PREFIX}/workflows/run`, apiKey, {
    ...options,
    method: 'POST',
    body: JSON.stringify(body),
  })
}

// ===== 对话流执行 =====

/**
 * POST /v1/chat-messages  ——  向对话型应用发送消息（流式）
 *
 * - Authorization:  Bearer <apiKey>
 * - request body:   {
 *                       query,                     // 用户消息
 *                       inputs,                    // 应用输入参数
 *                       response_mode: 'streaming',
 *                       conversation_id,           // 续传时填，否则留空字符串
 *                       user: 'user=<access_token>',
 *                       files?                     // 多模态文件
 *                     }
 *
 * @param {string} apiKey 应用 API Key
 * @param {object} params { query, inputs, conversation_id, files }
 * @param {string} [user] 可选，user 字段。默认自动从 cookie 中读取 access_token 并拼接 'user=' 前缀
 * @returns {Promise<Response>} 返回 SSE 流
 */
export const runChatMessage = (apiKey, params = {}, user, options = {}) => {
  const finalUser = user || buildWorkflowUser() || 'webapp-user'
  const body = {
    query: params.query || '',
    inputs: params.inputs || {},
    response_mode: 'streaming',
    conversation_id: params.conversation_id || '',
    user: finalUser,
  }
  if (Array.isArray(params.files) && params.files.length > 0) {
    body.files = params.files
  }
  if (typeof console !== 'undefined') {
    console.log('[runChatMessage] final body →', JSON.stringify(body))
  }
  return difyFetch(`${DIFY_V1_PREFIX}/chat-messages`, apiKey, {
    ...options,
    method: 'POST',
    body: JSON.stringify(body),
  })
}

/**
 * GET /v1/conversations  —— 当前智能体的会话列表（历史记录抽屉，滚动分页）
 *
 * - Authorization:  Bearer <apiKey>
 * - query:          user（必填，网关要求 user=<登录token>）、limit（页大小）、
 *                   sort_by=-updated_at（按更新时间倒序：第一页返回最新 limit 条）、
 *                   last_id（分页游标：传上一页最后一条的 id 取下一页）
 * - 响应:           { data: [{ id, name, inputs, status, introduction, created_at, updated_at }], has_more, limit, page, total }
 */
export const getConversations = (apiKey, { lastId = '', limit = 20 } = {}, options = {}) => {
  const user = buildWorkflowUser() || 'webapp-user'
  const params = [
    `user=${encodeURIComponent(user)}`,
    `limit=${limit}`,
    'sort_by=-updated_at',
  ]
  if (lastId) params.push(`last_id=${encodeURIComponent(lastId)}`)
  return difyFetch(`${DIFY_V1_PREFIX}/conversations?${params.join('&')}`, apiKey, { method: 'GET', ...options }).then(r => r.json())
}

/**
 * GET /v1/messages  —— 会话历史消息（恢复历史会话时拉取）
 *
 * - Authorization:  Bearer <apiKey>
 * - query:          conversation_id（必填）、user（必填，网关要求 user=<登录token>）、limit
 * - 响应:           { data: [{ id, conversation_id, query, answer, created_at }], has_more, limit }
 */
export const getChatMessages = (apiKey, conversationId, options = {}) => {
  const user = buildWorkflowUser() || 'webapp-user'
  const qs = `conversation_id=${encodeURIComponent(conversationId)}&user=${encodeURIComponent(user)}&limit=100`
  return difyFetch(`${DIFY_V1_PREFIX}/messages?${qs}`, apiKey, { method: 'GET', ...options }).then(r => r.json())
}

/**
 * 解析 SSE（Server-Sent Events）流
 * 自动忽略 ping 事件，逐个回调解析后的 JSON
 * @param {Response} response fetch 响应对象（response.body.getReader()）
 * @param {(evt: object) => void} onEvent 每解析一个事件就回调
 * @param {() => void} onDone 流结束时回调
 * @param {(err: Error) => void} onError 出错时回调
 * @param {{signal?: AbortSignal}} [options] 可选取消信号
 * @returns {Promise<void>}
 */
export async function consumeSSE(response, onEvent, onDone, onError, options = {}) {
  if (!response.body) {
    onError?.(new Error('Response body is empty'))
    return
  }
  const reader = response.body.getReader()
  const cancelReader = () => reader.cancel().catch(() => {})
  if (options.signal?.aborted) {
    cancelReader()
    return
  }
  options.signal?.addEventListener('abort', cancelReader, { once: true })
  const decoder = new TextDecoder('utf-8')
  let buffer = ''
  try {
    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      buffer += decoder.decode(value, { stream: true })

      // SSE 以 \n\n 分隔事件
      const parts = buffer.split('\n\n')
      buffer = parts.pop() // 保留未完成的一段

      for (const part of parts) {
        const lines = part.split('\n')
        for (const line of lines) {
          const trimmed = line.trim()
          if (!trimmed.startsWith('data:')) continue
          const payload = trimmed.slice(5).trim()
          if (!payload) continue
          // 跳过 ping 心跳
          if (payload === 'ping' || payload.includes('"event":"ping"')) continue
          try {
            const evt = JSON.parse(payload)
            onEvent?.(evt)
          } catch (e) {
            // 忽略非 JSON 行
            // console.warn('SSE parse failed:', e, payload)
          }
        }
      }
    }
    // 处理残留 buffer
    if (buffer.trim()) {
      const lines = buffer.split('\n')
      for (const line of lines) {
        const trimmed = line.trim()
        if (!trimmed.startsWith('data:')) continue
        const payload = trimmed.slice(5).trim()
        if (!payload || payload === 'ping' || payload.includes('"event":"ping"')) continue
        try {
          const evt = JSON.parse(payload)
          onEvent?.(evt)
        } catch (e) {}
      }
    }
    onDone?.()
  } catch (e) {
    if (!options.signal?.aborted) {
      onError?.(e)
    }
  } finally {
    options.signal?.removeEventListener('abort', cancelReader)
  }
}
