import { NextRequest, NextResponse } from 'next/server'
import crypto from 'crypto'
import { EngineRegistry } from '@/lib/engine'
import type { ForgeAttachment } from '@/lib/engine/adapters/claude/client'
import type { ContentBlock } from '@/lib/types'
import { createSseCallbacks } from '@/lib/engine/consumers/sse-adapter'
import { createPermissionBridge } from '@/lib/engine/adapters/claude/permission-bridge'
import { sessionRepo } from '@/lib/database/repositories/session.repo'
import { messageRepo } from '@/lib/database/repositories/message.repo'
import { h5PageRepo } from '@/lib/database/repositories/h5-page.repo'
import { resolveProvider } from '@/lib/provider'
import { runSessionCleanup } from '@/lib/session-cleanup'
import { cleanupStaleSessionAllowances } from '@/lib/engine/adapters/claude/permission-bridge'
import { authenticateH5FromCookieHeader, getCookieHeaderFromRequest } from '@/lib/h5/auth'
import { h5Router, H5ForbiddenError } from '@/lib/h5/router'
import { registerH5Stream, unregisterH5Stream } from '@/lib/h5/active-streams'
import fs from 'fs'
import path from 'path'
import { getUploadsDir } from '@/lib/forge-data'
import { parseMediaProtocol, extractFilePaths, resolveFileAttachments } from '@/lib/im/shared/attachment-parsing'

export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'

/**
 * POST /api/h5/chat — H5 外部用户对话（§6.1）。
 *
 * 鉴权：forge_h5 cookie（解出 pageId + externalUserId）
 * session 归属：用 external_user_id 校验（不是 user_id，见安全关键点）
 * 权限：page.permission_mode（默认 'full' → bypassPermissions:true）
 *
 * SSE 主体复用 chat/route.ts 的实现：TransformStream + engine.process + createSseCallbacks。
 * 401 时返回 JSON（前端据此渲染「会话已过期」整页态）。
 */
export async function POST(req: NextRequest): Promise<Response> {
  // ── 鉴权 ──
  const auth = authenticateH5FromCookieHeader(getCookieHeaderFromRequest(req))
  if (!auth) {
    return NextResponse.json({ error: 'Session expired', code: 'h5_expired' }, { status: 401 })
  }

  const { page, pageRow, payload } = auth

  // ── 解析请求体 ──
  let body: {
    sessionId?: string
    message?: string
    attachments?: Array<{ name: string; filename: string; mimeType: string; tier: string }>
  }
  try {
    body = await req.json()
  } catch {
    return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })
  }

  const { sessionId: requestedSessionId, message } = body

  // 映射附件（与 chat/route.ts 一致的清理逻辑）
  const uploadsDir = getUploadsDir()
  const attachments: ForgeAttachment[] = (body.attachments || []).map((a) => {
    const safeFilename = a.filename.replace(/\.\./g, '').replace(/[/\\]/g, '_')
    const normalizedTier = a.tier === 'image' ? 'image' : 'file'
    return {
      name: a.name,
      serverPath: path.join(uploadsDir, safeFilename),
      mimeType: a.mimeType,
      tier: normalizedTier as ForgeAttachment['tier'],
    }
  })

  if (!message && attachments.length === 0) {
    return NextResponse.json({ error: 'message (or attachments) required' }, { status: 400 })
  }

  // ── 解析/创建 session ──
  let session
  try {
    const resolution = h5Router.resolveSession(page, payload.externalUserId, requestedSessionId, payload.name)
    session = resolution.session
  } catch (err) {
    if (err instanceof H5ForbiddenError) {
      return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
    }
    throw err
  }

  const engine = EngineRegistry.get(session.backend)

  // ── 权限模式：page.permission_mode（默认 full → bypass） ──
  const permissionMode = pageRow.permissionMode || 'full'

  // ── provider 解析（thinking mode 用） ──
  const resolved = resolveProvider(session.model, session.userId)
  const providerType = resolved.provider

  // ── 历史消息数（判断 resume） ──
  const existingMsgCount = messageRepo.countBySession(session.id)

  // ── 附件 prompt 构建 ──
  const buildAttachmentPrompt = (): string => {
    const imageAtts = attachments.filter((a) => a.tier === 'image')
    const fileAtts = attachments.filter((a) => a.tier === 'file')
    const parts: string[] = []
    if (imageAtts.length > 0) {
      const refs = imageAtts.map((a) => `[User attached image saved at: ${a.serverPath} (${a.name})]`).join('\n')
      parts.push(`${refs}\n(The image is also sent via vision above; use Read tool on this path if vision fails)`)
    }
    if (fileAtts.length > 0) {
      const refs = fileAtts.map((a) => `[User attached file: ${a.serverPath} (${a.name})]`).join('\n')
      parts.push(`${refs}\nPlease read the attached file(s) using your Read tool, then respond.`)
    }
    return parts.join('\n\n')
  }
  const attachmentPrompt = buildAttachmentPrompt()
  const effectiveMessage = message || (attachments.length > 0 ? attachments.map((a) => `[${a.name}]`).join(' ') : '')
  const finalPrompt = attachmentPrompt ? `${attachmentPrompt}\n\n${effectiveMessage}` : effectiveMessage

  // ── 保存用户消息（ContentBlock[] 格式，含 text + 附件块，前端可渲染图片/文件） ──
  const userMsgId = crypto.randomUUID()
  const userBlocks: ContentBlock[] = []
  if (effectiveMessage) {
    userBlocks.push({ type: 'text', text: effectiveMessage })
  }
  for (const att of attachments) {
    const url = `/api/upload/${att.serverPath.split('/').pop()!.replace(/^.*[\\/]/, '')}`
    if (att.tier === 'image') {
      userBlocks.push({ type: 'image_attachment', url, name: att.name })
    } else {
      userBlocks.push({ type: 'file_attachment', url, name: att.name, size: 0, mimeType: att.mimeType })
    }
  }
  messageRepo.create({ id: userMsgId, sessionId: session.id, role: 'user', content: JSON.stringify(userBlocks) })

  // ── 清理 ──
  runSessionCleanup()
  cleanupStaleSessionAllowances()

  // ── SSE 流 ──
  const encoder = new TextEncoder()
  const abortController = new AbortController()
  const { readable, writable } = new TransformStream()
  const writer = writable.getWriter()

  const emit = async (data: Record<string, unknown>): Promise<void> => {
    try {
      await writer.write(encoder.encode(`data: ${JSON.stringify(data)}\n\n`))
    } catch {
      // writer closed (client disconnected)
    }
  }

  // 注册到运行中流表：供 /api/h5/chat/stop 主动停止（被动断开不再 abort 引擎）
  registerH5Stream(session.id, abortController)

  // 后台处理流
  void (async () => {
    let assistantMsgId: string | undefined
    try {
      const canUseTool =
        permissionMode === 'confirm'
          ? createPermissionBridge(session.id, emit as (data: Record<string, unknown>) => void)
          : undefined

      const isResume = existingMsgCount > 0

      const baseRequest = {
        prompt: finalPrompt,
        sessionId: session.id,
        model: session.model,
        workspaceId: session.workspace,
        userId: session.userId,
        abortController,
        canUseTool,
        bypassPermissions: permissionMode === 'full',
        resumeSession: isResume,
        providerType,
        attachments: attachments.length > 0 ? attachments : undefined,
      }

      const result = await engine.process(baseRequest, createSseCallbacks(emit))

      // Detect file/image attachments from two sources (对齐 chat/route.ts):
      // 1. MEDIA: protocol lines in text blocks (explicit, preferred)
      // 2. Tool use heuristics (fallback)
      const blocks = result.blocks

      // Parse MEDIA: lines from text blocks and strip them from displayed text
      const mediaPaths: string[] = []
      for (const block of blocks) {
        if ((block as Record<string, unknown>).type === 'text') {
          const b = block as { type: string; text: string }
          const parsed = parseMediaProtocol(b.text)
          mediaPaths.push(...parsed.mediaPaths)
          b.text = parsed.text // Strip MEDIA: lines from stored text
        }
      }

      // Merge MEDIA: paths with heuristic-detected paths, dedup
      const mediaAttachments = resolveFileAttachments(mediaPaths)
      const heuristicAttachments = resolveFileAttachments(extractFilePaths(blocks as Record<string, unknown>[]))
      const seenPaths = new Set(mediaAttachments.map(a => a.filePath))
      const detectedFiles = [...mediaAttachments, ...heuristicAttachments.filter(a => !seenPaths.has(a.filePath))]

      const uploadsDir = getUploadsDir()
      for (const att of detectedFiles) {
        try {
          // Copy to uploads dir so it's accessible via /api/upload/
          const destName = `agent_${Date.now()}_${att.name.replace(/[^a-zA-Z0-9._-]/g, '_')}`
          const destPath = path.join(uploadsDir, destName)
          fs.copyFileSync(att.filePath, destPath)
          if (att.isImage) {
            blocks.push({ type: 'image_attachment', url: `/api/upload/${destName}`, name: att.name })
          } else {
            blocks.push({ type: 'file_attachment', url: `/api/upload/${destName}`, name: att.name, size: att.size, mimeType: att.mimeType })
          }
          // Emit SSE event so frontend renders immediately
          await emit({ type: 'attachment', attachment: att.isImage
            ? { type: 'image_attachment', url: `/api/upload/${destName}`, name: att.name }
            : { type: 'file_attachment', url: `/api/upload/${destName}`, name: att.name, size: att.size, mimeType: att.mimeType }
          })
        } catch { /* skip unreadable files */ }
      }

      // 保存 assistant 消息
      assistantMsgId = crypto.randomUUID()
      messageRepo.create({
        id: assistantMsgId,
        sessionId: session.id,
        role: 'assistant',
        content: JSON.stringify(blocks),
      })

      // 更新标题（首条消息后）
      const msgCount = messageRepo.countBySession(session.id)
      if (msgCount <= 2) {
        const title = effectiveMessage.length > 50 ? effectiveMessage.slice(0, 47) + '...' : effectiveMessage
        const prefix = payload.name ? `[${payload.name}]` : '[extuser]'
        sessionRepo.updateTitle(session.id, `${prefix} ${title}`)
      } else {
        sessionRepo.touch(session.id)
      }

      await emit({
        type: 'done',
        messageId: assistantMsgId,
        userMessageId: userMsgId,
        inputTokens: result.inputTokens,
        outputTokens: result.outputTokens,
      })
    } catch (err) {
      // 保存部分响应
      const errorMessage = err instanceof Error ? err.message : 'Unknown error'
      await emit({ type: 'error', error: errorMessage })
    } finally {
      unregisterH5Stream(session.id)
      try {
        await writer.close()
      } catch {
        // already closed
      }
    }
  })()

  return new Response(readable, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache, no-transform',
      Connection: 'keep-alive',
      'X-Accel-Buffering': 'no',
      'X-Content-Type-Options': 'nosniff',
    },
  })
}
