package fuck.andes.agent.model

import java.net.URI
import org.json.JSONObject

/** 工具摘要不包含敏感参数；终端命令通过独立字段提供给用户核对。 */
internal class AgentTraceFormatter {
    fun summarizeArguments(toolCall: AgentModelClient.ToolCall): String =
        when (toolCall.name) {
            BROWSER_TOOL_NAME -> summarizeBrowserArguments(toolCall.argumentsJson)
            "open_uri" -> summarizeOpenUriArguments(toolCall.argumentsJson)
            "terminal" -> summarizeTerminalArguments(toolCall.argumentsJson)
            "run_command" -> "执行命令 · Android · root"
            "write_file" -> summarizeTextLength("写入文件", toolCall.argumentsJson, "content")
            "read_file" -> "读取文件"
            "list_directory" -> "列出目录"
            "input_text", "replace_text", "paste_text", "set_clipboard" ->
                summarizeTextLength("输入文本", toolCall.argumentsJson, "text")
            "search_apps" -> summarizeTextLength("搜索应用", toolCall.argumentsJson, "query")
            "observe_screen" -> summarizeObservationArguments(toolCall.argumentsJson)
            "memory_get" -> summarizeMemoryGetArguments(toolCall.argumentsJson)
            "memory_write" -> summarizeMemoryWriteArguments(toolCall.argumentsJson)
            else -> "参数已接收"
        }

    /** 原始命令只进入运行轨迹供用户核对；日志仍由 AgentEvent 记录长度。 */
    fun displayCommand(toolCall: AgentModelClient.ToolCall): String? =
        if (toolCall.name == "terminal" || toolCall.name == "run_command") {
            runCatching {
                JSONObject(toolCall.argumentsJson)
                    .optString("command")
                    .trim()
                    .takeIf { it.isNotBlank() && it.length <= MAX_DISPLAY_COMMAND_CHARS }
            }.getOrNull()
        } else {
            null
        }

    /** 外部 URI 摘要不记录 path、query、fragment 或用户信息。 */
    fun summarizeOpenUriArguments(argumentsJson: String): String =
        runCatching {
            val raw = JSONObject(argumentsJson).optString("uri").trim()
            val uri = URI(raw)
            val scheme = uri.scheme?.lowercase()?.take(24)
            val host = uri.host?.lowercase()?.take(160)
            listOfNotNull("交给外部应用", scheme, host).joinToString(" · ")
        }.getOrDefault("交给外部应用")

    /** browser_use 摘要只暴露动作和安全提取的 host。 */
    fun summarizeBrowserArguments(argumentsJson: String): String =
        runCatching {
            val arguments = JSONObject(argumentsJson)
            val action = arguments.optString("action").browserActionLabel()
            val host = safeHttpHost(arguments.optString("url"))
            listOfNotNull(action, host).joinToString(" · ")
        }.getOrElse { "浏览器操作" }

    private fun summarizeTerminalArguments(argumentsJson: String): String =
        runCatching {
            val arguments = JSONObject(argumentsJson)
            val action = arguments.optString("action").terminalActionLabel()
            val environment = arguments.optString("environment", "android")
                .terminalEnvironmentLabel()
            val identity = arguments.optString("identity", "root")
                .takeIf { it == "root" || it == "user" }
            buildList {
                add("终端")
                add(action)
                add(environment)
                identity?.let(::add)
                if (arguments.optBoolean("async", false)) add("后台")
            }.joinToString(" · ")
        }.getOrDefault("终端")

    private fun summarizeTextLength(
        label: String,
        argumentsJson: String,
        key: String,
    ): String =
        runCatching {
            val chars = JSONObject(argumentsJson).optString(key).length
            "$label · chars=$chars"
        }.getOrDefault(label)

    private fun summarizeObservationArguments(argumentsJson: String): String =
        runCatching {
            val options = AgentScreenObservationContract.resolve(JSONObject(argumentsJson))
            "观察屏幕 · screenshot=${options.includeScreenshot} · " +
                "ui_tree=${options.includeUiTree}"
        }.getOrDefault("观察屏幕")

    private fun summarizeMemoryGetArguments(argumentsJson: String): String =
        runCatching {
            val arguments = JSONObject(argumentsJson)
            if (arguments.optString("query").isNotBlank()) "检索记忆" else "读取记忆"
        }.getOrDefault("读取记忆")

    private fun summarizeMemoryWriteArguments(argumentsJson: String): String =
        runCatching {
            val arguments = JSONObject(argumentsJson)
            val mode = arguments.optString("mode").takeIf {
                it in setOf("replace_range", "append", "clear")
            } ?: "unknown"
            val content = arguments.optString("content")
            val lines = if (content.isEmpty()) 0 else content.count { it == '\n' } + 1
            "更新记忆 · mode=$mode · lines=$lines · bytes=${content.toByteArray(Charsets.UTF_8).size}"
        }.getOrDefault("更新记忆")

    fun summarizeResult(
        toolName: String,
        result: AgentModelClient.ToolResult,
    ): String =
        when (toolName) {
            BROWSER_TOOL_NAME -> summarizeBrowserResult(result)
            "memory_get", "memory_write" -> summarizeMemoryResult(result)
            else -> summarizeGenericResult(result)
        }

    private fun summarizeMemoryResult(result: AgentModelClient.ToolResult): String =
        runCatching {
            val json = JSONObject(result.content)
            buildString {
                append("ok=").append(json.optBoolean("ok", false))
                json.optString("code").takeIf(String::isNotBlank)?.let {
                    append(", code=").append(it)
                }
                if (json.has("line_count")) append(", lines=").append(json.optInt("line_count"))
                if (json.has("bytes")) append(", bytes=").append(json.optInt("bytes"))
            }
        }.getOrDefault("ok=false")

    private fun summarizeGenericResult(result: AgentModelClient.ToolResult): String =
        runCatching {
            val json = JSONObject(result.content)
            val apps = json.optJSONArray("apps")
            val candidates = json.optJSONArray("candidates")
            buildString {
                append("ok=${json.opt("ok")}")
                val code = json.optString("code")
                if (code.isNotBlank()) append(", code=").append(code)
                if (apps != null) append(", apps=").append(apps.length())
                if (candidates != null) append(", candidates=").append(candidates.length())
                append(", chars=").append(result.content.length)
                if (result.images.isNotEmpty()) append(", images=").append(result.images.size)
            }
        }.getOrElse {
            "chars=${result.content.length}"
        }

    private fun summarizeBrowserResult(result: AgentModelClient.ToolResult): String =
        runCatching {
            val json = JSONObject(result.content)
            val page = json.optJSONObject("page")
                ?: json.optJSONObject("page_info")
                ?: json.optJSONObject("pageInfo")
            val action = json.optString("action")
                .takeIf { it in BROWSER_ACTIONS }
                ?: "unknown"
            val host = sequenceOf(json, page)
                .filterNotNull()
                .flatMap { source ->
                    sequenceOf("url", "current_url", "currentUrl", "final_url", "finalUrl")
                        .map(source::optString)
                }
                .mapNotNull(::safeHttpHost)
                .firstOrNull()
            val title = sequenceOf(json, page)
                .filterNotNull()
                .map { it.opt("title") }
                .filterIsInstance<String>()
                .map(::sanitizeSummaryValue)
                .firstOrNull { it.isNotBlank() }
            val textChars = sequenceOf(json, page)
                .filterNotNull()
                .mapNotNull { source ->
                    source.firstNonNegativeInt("text_length", "textLength", "text_chars", "textChars")
                }
                .firstOrNull()
                ?: sequenceOf(json, page)
                    .filterNotNull()
                    .flatMap { source -> sequenceOf("text", "readable", "content").map(source::opt) }
                    .filterIsInstance<String>()
                    .map(String::length)
                    .firstOrNull()
            val elementCount = json.firstNonNegativeInt("element_count", "elementCount", "elements_count")
                ?: json.optJSONArray("elements")?.length()

            buildString {
                append("ok=").append(json.optBoolean("ok", false))
                append(", action=").append(action)
                if (host != null) append(", host=").append(host)
                if (title != null) append(", title=").append(title)
                if (textChars != null) append(", text_chars=").append(textChars)
                if (elementCount != null) append(", elements=").append(elementCount)
                append(", truncated=").append(json.optBoolean("truncated", false))
            }
        }.getOrElse {
            "ok=false, action=unknown, truncated=false"
        }

    private fun JSONObject.firstNonNegativeInt(vararg keys: String): Int? =
        keys.firstNotNullOfOrNull { key ->
            if (!has(key)) return@firstNotNullOfOrNull null
            optInt(key, -1).takeIf { it >= 0 }
        }

    private fun sanitizeSummaryValue(value: String): String =
        value.replace(Regex("\\s+"), " ")
            .trim()
            .replace(',', '，')
            .replace('=', '＝')
            .let { if (it.length <= 80) it else it.take(80) + "..." }

    private fun safeHttpHost(rawUrl: String): String? =
        rawUrl.trim()
            .takeIf(String::isNotEmpty)
            ?.let { value ->
                runCatching {
                    val uri = URI(value)
                    uri.host
                        ?.takeIf {
                            uri.scheme.equals("http", ignoreCase = true) ||
                                uri.scheme.equals("https", ignoreCase = true)
                        }
                        ?.lowercase()
                        ?.take(160)
                }.getOrNull()
            }

    private fun String.browserActionLabel(): String = when (this) {
        "navigate" -> "打开网页"
        "get_readable" -> "提取正文"
        "get_text" -> "读取文本"
        "find_elements" -> "查找元素"
        "click" -> "点击网页"
        "type" -> "输入内容"
        "scroll" -> "滚动网页"
        "screenshot" -> "网页截图"
        "get_page_info" -> "查看网页信息"
        "go_back" -> "网页后退"
        "go_forward" -> "网页前进"
        "reload" -> "刷新网页"
        "wait_for_selector" -> "等待网页元素"
        else -> "浏览器操作"
    }

    private fun String.terminalActionLabel(): String = when (this) {
        "open" -> "创建会话"
        "exec" -> "执行命令"
        "open_and_exec" -> "单次执行"
        "read_async_result" -> "读取后台输出"
        "close" -> "关闭终端"
        else -> "终端操作"
    }

    private fun String.terminalEnvironmentLabel(): String = when (this) {
        "linux" -> "Linux"
        else -> "Android"
    }

    private companion object {
        const val BROWSER_TOOL_NAME = "browser_use"
        const val MAX_DISPLAY_COMMAND_CHARS = 4_000
        val BROWSER_ACTIONS = setOf(
            "navigate",
            "get_readable",
            "get_text",
            "find_elements",
            "click",
            "type",
            "scroll",
            "screenshot",
            "get_page_info",
            "go_back",
            "go_forward",
            "reload",
            "wait_for_selector",
        )
    }
}
