/**
 * Native Sessions API Routes - Binary-Free Mode
 * 
 * These routes provide session management without requiring the OpenCode binary.
 * They're used when running in "Lite Mode" or when OpenCode is unavailable.
 */

import { FastifyInstance } from "fastify"
import { Logger } from "../../logger"
import { getSessionManager, Session, SessionMessage } from "../../storage/session-store"
import { CORE_TOOLS, executeTools, type ToolCall, type ToolResult } from "../../tools/executor"
import { getMcpManager } from "../../mcp/client"
import { WorkspaceManager } from "../../workspaces/manager"
import { OpenCodeZenClient, ChatMessage } from "../../integrations/opencode-zen"
import { EventBus } from "../../events/bus"

interface NativeSessionsDeps {
    logger: Logger
    workspaceManager: WorkspaceManager
    dataDir: string
    eventBus?: EventBus
}

// Maximum tool execution loops to prevent infinite loops
const MAX_TOOL_LOOPS = 10

export function registerNativeSessionsRoutes(app: FastifyInstance, deps: NativeSessionsDeps) {
    const logger = deps.logger.child({ component: "native-sessions" })
    const sessionManager = getSessionManager(deps.dataDir)

    // List all sessions for a workspace
    app.get<{ Params: { workspaceId: string } }>("/api/native/workspaces/:workspaceId/sessions", async (request, reply) => {
        try {
            const sessions = await sessionManager.listSessions(request.params.workspaceId)
            return { sessions }
        } catch (error) {
            logger.error({ error }, "Failed to list sessions")
            reply.code(500)
            return { error: "Failed to list sessions" }
        }
    })

    // Create a new session
    app.post<{
        Params: { workspaceId: string }
        Body: { title?: string; parentId?: string; model?: { providerId: string; modelId: string }; agent?: string }
    }>("/api/native/workspaces/:workspaceId/sessions", async (request, reply) => {
        try {
            const session = await sessionManager.createSession(request.params.workspaceId, request.body)

            // Emit session created event (using any for custom event type)
            if (deps.eventBus) {
                deps.eventBus.publish({
                    type: "native.session.created",
                    workspaceId: request.params.workspaceId,
                    session
                } as any)
            }

            reply.code(201)
            return { session }
        } catch (error) {
            logger.error({ error }, "Failed to create session")
            reply.code(500)
            return { error: "Failed to create session" }
        }
    })

    // Get a specific session
    app.get<{ Params: { workspaceId: string; sessionId: string } }>("/api/native/workspaces/:workspaceId/sessions/:sessionId", async (request, reply) => {
        try {
            const session = await sessionManager.getSession(request.params.workspaceId, request.params.sessionId)
            if (!session) {
                reply.code(404)
                return { error: "Session not found" }
            }
            return { session }
        } catch (error) {
            logger.error({ error }, "Failed to get session")
            reply.code(500)
            return { error: "Failed to get session" }
        }
    })

    // Update a session
    app.patch<{
        Params: { workspaceId: string; sessionId: string }
        Body: Partial<Session>
    }>("/api/native/workspaces/:workspaceId/sessions/:sessionId", async (request, reply) => {
        try {
            const session = await sessionManager.updateSession(
                request.params.workspaceId,
                request.params.sessionId,
                request.body
            )
            if (!session) {
                reply.code(404)
                return { error: "Session not found" }
            }
            return { session }
        } catch (error) {
            logger.error({ error }, "Failed to update session")
            reply.code(500)
            return { error: "Failed to update session" }
        }
    })

    // Fork a session
    app.post<{
        Params: { workspaceId: string; sessionId: string }
    }>("/api/native/workspaces/:workspaceId/sessions/:sessionId/fork", async (request, reply) => {
        try {
            const session = await sessionManager.forkSession(
                request.params.workspaceId,
                request.params.sessionId
            )
            return { session }
        } catch (error) {
            logger.error({ error }, "Failed to fork session")
            reply.code(500)
            return { error: "Failed to fork session" }
        }
    })

    // Revert a session
    app.post<{
        Params: { workspaceId: string; sessionId: string }
        Body: { messageId?: string }
    }>("/api/native/workspaces/:workspaceId/sessions/:sessionId/revert", async (request, reply) => {
        try {
            const session = await sessionManager.revert(
                request.params.workspaceId,
                request.params.sessionId,
                request.body.messageId
            )
            return { session }
        } catch (error) {
            logger.error({ error }, "Failed to revert session")
            reply.code(500)
            return { error: "Failed to revert session" }
        }
    })

    // Delete a session
    app.delete<{ Params: { workspaceId: string; sessionId: string } }>("/api/native/workspaces/:workspaceId/sessions/:sessionId", async (request, reply) => {
        try {
            const deleted = await sessionManager.deleteSession(request.params.workspaceId, request.params.sessionId)
            if (!deleted) {
                reply.code(404)
                return { error: "Session not found" }
            }
            reply.code(204)
            return
        } catch (error) {
            logger.error({ error }, "Failed to delete session")
            reply.code(500)
            return { error: "Failed to delete session" }
        }
    })

    // Import sessions from SDK mode - for migration when switching to native mode
    app.post<{
        Params: { workspaceId: string }
        Body: {
            sessions: Array<{
                id: string
                title?: string
                parentId?: string | null
                createdAt?: number
                updatedAt?: number
                model?: { providerId: string; modelId: string }
                agent?: string
                messages?: Array<{
                    id: string
                    role: "user" | "assistant" | "system" | "tool"
                    content?: string
                    createdAt?: number
                }>
            }>
        }
    }>("/api/native/workspaces/:workspaceId/sessions/import", async (request, reply) => {
        try {
            const result = await sessionManager.importSessions(
                request.params.workspaceId,
                request.body.sessions
            )
            logger.info({ workspaceId: request.params.workspaceId, ...result }, "Sessions imported from SDK mode")
            return { success: true, ...result }
        } catch (error) {
            logger.error({ error }, "Failed to import sessions")
            reply.code(500)
            return { error: "Failed to import sessions" }
        }
    })


    // Get messages for a session
    app.get<{ Params: { workspaceId: string; sessionId: string } }>("/api/native/workspaces/:workspaceId/sessions/:sessionId/messages", async (request, reply) => {
        try {
            const messages = await sessionManager.getSessionMessages(
                request.params.workspaceId,
                request.params.sessionId
            )
            return { messages }
        } catch (error) {
            logger.error({ error }, "Failed to get messages")
            reply.code(500)
            return { error: "Failed to get messages" }
        }
    })

    // Append messages to a session (client-side persistence)
    app.post<{
        Params: { workspaceId: string; sessionId: string }
        Body: {
            messages: Array<{
                id?: string
                role: "user" | "assistant" | "system" | "tool"
                content?: string
                createdAt?: number
                updatedAt?: number
                status?: "pending" | "streaming" | "completed" | "error"
            }>
        }
    }>("/api/native/workspaces/:workspaceId/sessions/:sessionId/messages", async (request, reply) => {
        const { workspaceId, sessionId } = request.params
        const payload = request.body?.messages
        if (!Array.isArray(payload)) {
            reply.code(400)
            return { error: "messages array is required" }
        }

        try {
            const results: SessionMessage[] = []
            for (const entry of payload) {
                if (!entry || typeof entry.role !== "string") {
                    continue
                }
                const saved = await sessionManager.addMessage(workspaceId, sessionId, {
                    id: entry.id,
                    role: entry.role,
                    content: entry.content,
                    createdAt: entry.createdAt,
                    updatedAt: entry.updatedAt,
                    status: entry.status,
                })
                results.push(saved)
            }
            return { messages: results }
        } catch (error) {
            logger.error({ error }, "Failed to append messages")
            reply.code(500)
            return { error: "Failed to append messages" }
        }
    })

    // Add a message (user prompt) and get streaming response
    app.post<{
        Params: { workspaceId: string; sessionId: string }
        Body: {
            content: string
            provider: "qwen" | "zai" | "zen"
            model?: string
            accessToken?: string
            resourceUrl?: string
            enableTools?: boolean
            systemPrompt?: string
        }
    }>("/api/native/workspaces/:workspaceId/sessions/:sessionId/prompt", async (request, reply) => {
        const { workspaceId, sessionId } = request.params
        const { content, provider, model, accessToken, resourceUrl, enableTools = true, systemPrompt } = request.body

        try {
            // Add user message
            const userMessage = await sessionManager.addMessage(workspaceId, sessionId, {
                role: "user",
                content,
                status: "completed",
            })

            // Get workspace path
            const workspace = deps.workspaceManager.get(workspaceId)
            const workspacePath = workspace?.path ?? process.cwd()

            // Get all messages for context
            const allMessages = await sessionManager.getSessionMessages(workspaceId, sessionId)

            // Build chat messages array
            const chatMessages: ChatMessage[] = []

            // Add system prompt if provided
            if (systemPrompt) {
                chatMessages.push({ role: "system", content: systemPrompt })
            }

            // Add conversation history
            for (const m of allMessages) {
                if (m.role === "user" || m.role === "assistant" || m.role === "system") {
                    chatMessages.push({ role: m.role, content: m.content ?? "" })
                }
            }

            // Load MCP tools
            let allTools = [...CORE_TOOLS]
            if (enableTools) {
                try {
                    const mcpManager = getMcpManager()
                    await mcpManager.loadConfig(workspacePath)
                    const mcpTools = await mcpManager.getToolsAsOpenAIFormat()
                    allTools = [...CORE_TOOLS, ...mcpTools]
                } catch (mcpError) {
                    logger.warn({ error: mcpError }, "Failed to load MCP tools")
                }
            }

            // Create streaming response
            reply.raw.writeHead(200, {
                'Content-Type': 'text/event-stream',
                'Cache-Control': 'no-cache',
                'Connection': 'keep-alive',
                'X-Accel-Buffering': 'no',
            })

            // Create assistant message placeholder
            const assistantMessage = await sessionManager.addMessage(workspaceId, sessionId, {
                role: "assistant",
                content: "",
                status: "streaming",
            })

            let fullContent = ""

            try {
                // Route to the appropriate provider
                fullContent = await streamWithProvider({
                    provider,
                    model,
                    accessToken,
                    resourceUrl,
                    messages: chatMessages,
                    tools: enableTools ? allTools : [],
                    workspacePath,
                    rawResponse: reply.raw,
                    logger,
                })
            } catch (streamError) {
                logger.error({ error: streamError }, "Stream error")
                reply.raw.write(`data: ${JSON.stringify({ error: String(streamError) })}\n\n`)
            }

            // Update assistant message with full content
            await sessionManager.updateMessage(workspaceId, assistantMessage.id, {
                content: fullContent,
                status: "completed",
            })

            // Emit message event (using any for custom event type)
            if (deps.eventBus) {
                deps.eventBus.publish({
                    type: "native.message.completed",
                    workspaceId,
                    sessionId,
                    messageId: assistantMessage.id,
                } as any)
            }

            reply.raw.write('data: [DONE]\n\n')
            reply.raw.end()
        } catch (error) {
            logger.error({ error }, "Failed to process prompt")
            if (!reply.sent) {
                reply.code(500)
                return { error: "Failed to process prompt" }
            }
        }
    })

    // SSE endpoint for session events
    app.get<{ Params: { workspaceId: string } }>("/api/native/workspaces/:workspaceId/events", async (request, reply) => {
        reply.raw.writeHead(200, {
            'Content-Type': 'text/event-stream',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'X-Accel-Buffering': 'no',
        })

        // Send initial ping
        reply.raw.write(`data: ${JSON.stringify({ type: "ping" })}\n\n`)

        // Keep connection alive
        const keepAlive = setInterval(() => {
            reply.raw.write(`data: ${JSON.stringify({ type: "ping" })}\n\n`)
        }, 30000)

        // Handle client disconnect
        request.raw.on("close", () => {
            clearInterval(keepAlive)
        })
    })

    logger.info("Native sessions routes registered (Binary-Free Mode)")
}

/**
 * Stream chat with the appropriate provider
 */
async function streamWithProvider(opts: {
    provider: "qwen" | "zai" | "zen"
    model?: string
    accessToken?: string
    resourceUrl?: string
    messages: ChatMessage[]
    tools: any[]
    workspacePath: string
    rawResponse: any
    logger: Logger
}): Promise<string> {
    const { provider, model, accessToken, resourceUrl, messages, tools, workspacePath, rawResponse, logger } = opts

    let fullContent = ""
    let loopCount = 0
    let currentMessages = [...messages]

    // Tool execution loop
    while (loopCount < MAX_TOOL_LOOPS) {
        loopCount++

        let responseContent = ""
        let toolCalls: ToolCall[] = []

        // Route to the appropriate provider
        switch (provider) {
            case "zen":
                const zenResult = await streamWithZen(model, currentMessages, tools, rawResponse, logger)
                responseContent = zenResult.content
                toolCalls = zenResult.toolCalls
                break

            case "qwen":
                const qwenResult = await streamWithQwen(accessToken, resourceUrl, model, currentMessages, tools, rawResponse, logger)
                responseContent = qwenResult.content
                toolCalls = qwenResult.toolCalls
                break

            case "zai":
                const zaiResult = await streamWithZAI(accessToken, model, currentMessages, tools, rawResponse, logger)
                responseContent = zaiResult.content
                toolCalls = zaiResult.toolCalls
                break
        }

        fullContent += responseContent

        // If no tool calls, we're done
        if (toolCalls.length === 0) {
            break
        }

        // Execute tools
        logger.info({ toolCount: toolCalls.length }, "Executing tool calls")

        // Add assistant message with tool calls
        currentMessages.push({
            role: "assistant",
            content: responseContent,
            tool_calls: toolCalls.map(tc => ({
                id: tc.id,
                type: "function" as const,
                function: tc.function
            }))
        })

        // Execute each tool and add result
        const toolResults = await executeTools(workspacePath, toolCalls)

        for (let i = 0; i < toolCalls.length; i++) {
            const tc = toolCalls[i]
            const result = toolResults[i]

            // Emit tool execution event
            rawResponse.write(`data: ${JSON.stringify({
                type: "tool_execution",
                tool: tc.function.name,
                result: result?.content?.substring(0, 200) // Preview
            })}\n\n`)

            currentMessages.push({
                role: "tool",
                content: result?.content ?? "Tool execution failed",
                tool_call_id: tc.id
            })
        }
    }

    return fullContent
}

/**
 * Stream with OpenCode Zen (free models)
 */
async function streamWithZen(
    model: string | undefined,
    messages: ChatMessage[],
    tools: any[],
    rawResponse: any,
    logger: Logger
): Promise<{ content: string; toolCalls: ToolCall[] }> {
    const zenClient = new OpenCodeZenClient()
    let content = ""
    const toolCalls: ToolCall[] = []

    try {
        const stream = zenClient.chatStream({
            model: model ?? "gpt-5-nano",
            messages,
            stream: true,
            tools: tools.length > 0 ? tools : undefined,
            tool_choice: tools.length > 0 ? "auto" : undefined,
        })

        for await (const chunk of stream) {
            const delta = chunk.choices?.[0]?.delta
            if (delta?.content) {
                content += delta.content
                rawResponse.write(`data: ${JSON.stringify({ choices: [{ delta: { content: delta.content } }] })}\n\n`)
            }

            // Handle tool calls (if model supports them)
            const deltaToolCalls = (delta as any)?.tool_calls
            if (deltaToolCalls) {
                for (const tc of deltaToolCalls) {
                    if (tc.function?.name) {
                        toolCalls.push({
                            id: tc.id,
                            type: "function",
                            function: {
                                name: tc.function.name,
                                arguments: tc.function.arguments ?? "{}"
                            }
                        })
                    }
                }
            }
        }
    } catch (error) {
        logger.error({ error }, "Zen streaming error")
        throw error
    }

    return { content, toolCalls }
}

/**
 * Stream with Qwen API
 */
async function streamWithQwen(
    accessToken: string | undefined,
    resourceUrl: string | undefined,
    model: string | undefined,
    messages: ChatMessage[],
    tools: any[],
    rawResponse: any,
    logger: Logger
): Promise<{ content: string; toolCalls: ToolCall[] }> {
    if (!accessToken) {
        throw new Error("Qwen access token required. Please authenticate with Qwen first.")
    }

    const baseUrl = resourceUrl ?? "https://chat.qwen.ai"
    let content = ""
    const toolCalls: ToolCall[] = []

    try {
        const response = await fetch(`${baseUrl}/api/v1/chat/completions`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "Authorization": `Bearer ${accessToken}`,
            },
            body: JSON.stringify({
                model: model ?? "qwen-plus-latest",
                messages,
                stream: true,
                tools: tools.length > 0 ? tools : undefined,
                tool_choice: tools.length > 0 ? "auto" : undefined,
            })
        })

        if (!response.ok) {
            const error = await response.text()
            throw new Error(`Qwen API error: ${response.status} - ${error}`)
        }

        const reader = response.body?.getReader()
        if (!reader) throw new Error("No response body")

        const decoder = new TextDecoder()
        let buffer = ""

        while (true) {
            const { done, value } = await reader.read()
            if (done) break

            buffer += decoder.decode(value, { stream: true })
            const lines = buffer.split("\n")
            buffer = lines.pop() ?? ""

            for (const line of lines) {
                if (line.startsWith("data: ")) {
                    const data = line.slice(6)
                    if (data === "[DONE]") continue

                    try {
                        const parsed = JSON.parse(data)
                        const delta = parsed.choices?.[0]?.delta

                        if (delta?.content) {
                            content += delta.content
                            rawResponse.write(`data: ${JSON.stringify({ choices: [{ delta: { content: delta.content } }] })}\n\n`)
                        }

                        if (delta?.tool_calls) {
                            for (const tc of delta.tool_calls) {
                                if (tc.function?.name) {
                                    toolCalls.push({
                                        id: tc.id ?? `call_${Date.now()}`,
                                        type: "function",
                                        function: {
                                            name: tc.function.name,
                                            arguments: tc.function.arguments ?? "{}"
                                        }
                                    })
                                }
                            }
                        }
                    } catch {
                        // Skip invalid JSON
                    }
                }
            }
        }
    } catch (error) {
        logger.error({ error }, "Qwen streaming error")
        throw error
    }

    return { content, toolCalls }
}

/**
 * Stream with Z.AI API
 */
async function streamWithZAI(
    accessToken: string | undefined,
    model: string | undefined,
    messages: ChatMessage[],
    tools: any[],
    rawResponse: any,
    logger: Logger
): Promise<{ content: string; toolCalls: ToolCall[] }> {
    let content = ""
    const toolCalls: ToolCall[] = []

    const baseUrl = "https://api.z.ai/api"

    try {
        const headers: Record<string, string> = {
            "Content-Type": "application/json",
        }

        if (!accessToken) {
            throw new Error("Z.AI API key required. Please authenticate with Z.AI first.")
        }
        headers["Authorization"] = `Bearer ${accessToken}`

        const response = await fetch(`${baseUrl}/paas/v4/chat/completions`, {
            method: "POST",
            headers,
            body: JSON.stringify({
                model: model ?? "glm-4.7",
                messages,
                stream: true,
                tools: tools.length > 0 ? tools : undefined,
                tool_choice: tools.length > 0 ? "auto" : undefined,
            })
        })

        if (!response.ok) {
            const error = await response.text()
            throw new Error(`Z.AI API error: ${response.status} - ${error}`)
        }

        const reader = response.body?.getReader()
        if (!reader) throw new Error("No response body")

        const decoder = new TextDecoder()
        let buffer = ""

        while (true) {
            const { done, value } = await reader.read()
            if (done) break

            buffer += decoder.decode(value, { stream: true })
            const lines = buffer.split("\n")
            buffer = lines.pop() ?? ""

            for (const line of lines) {
                if (line.startsWith("data: ")) {
                    const data = line.slice(6)
                    if (data === "[DONE]") continue

                    try {
                        const parsed = JSON.parse(data)
                        const delta = parsed.choices?.[0]?.delta

                        if (delta?.content) {
                            content += delta.content
                            rawResponse.write(`data: ${JSON.stringify({ choices: [{ delta: { content: delta.content } }] })}\n\n`)
                        }

                        if (delta?.tool_calls) {
                            for (const tc of delta.tool_calls) {
                                if (tc.function?.name) {
                                    toolCalls.push({
                                        id: tc.id ?? `call_${Date.now()}`,
                                        type: "function",
                                        function: {
                                            name: tc.function.name,
                                            arguments: tc.function.arguments ?? "{}"
                                        }
                                    })
                                }
                            }
                        }
                    } catch {
                        // Skip invalid JSON
                    }
                }
            }
        }
    } catch (error) {
        logger.error({ error }, "Z.AI streaming error")
        throw error
    }

    return { content, toolCalls }
}
