"""
工具执行展示管理器
管理工具调用状态的渲染（Running / 完成摘要 / Diff 预览等）
"""

import os
import time
import difflib
from typing import Dict, Any, List
from dataclasses import dataclass, field

from rich.console import Group, RenderableType
from rich.text import Text

from interface.styles import Styles, Icons


@dataclass
class ToolExecution:
    """工具执行状态"""
    tool_id: str
    tool_name: str
    args: Dict[str, Any] = field(default_factory=dict)
    start_time: float = 0.0
    end_time: float = 0.0
    success: bool = True
    result: str = ""
    status: str = "running"  # running, success, failed


class ToolDisplayManager:
    """工具执行展示管理器"""

    def __init__(self):
        self.tools: Dict[str, ToolExecution] = {}
        self.completed_tools: List[ToolExecution] = []
        self._max_preview_lines = 10
        self._spinner_frame: int = 0

    def add_tool(self, tool_id: str, tool_name: str, args: Dict[str, Any] = None):
        self.tools[tool_id] = ToolExecution(
            tool_id=tool_id,
            tool_name=tool_name,
            args=args or {},
            start_time=time.time(),
            status="running"
        )

    def complete_tool(self, tool_id: str, success: bool, result: str = "", args: dict = None):
        if tool_id in self.tools:
            tool = self.tools.pop(tool_id)
            tool.end_time = time.time()
            tool.success = success
            tool.result = result
            tool.status = "success" if success else "failed"
            if args:
                tool.args = args
            self.completed_tools.append(tool)

    def render(self) -> RenderableType:
        items = []
        for tool in self.completed_tools:
            items.append(self._render_completed_tool(tool))
        for tool in self.tools.values():
            items.append(self._render_running_tool(tool))
        if items:
            return Group(*items)
        return Text("")

    def advance_spinner(self):
        """推进 spinner 帧（由外部定时器调用）"""
        self._spinner_frame = (self._spinner_frame + 1) % len(Icons.SPINNER_FRAMES)

    def _render_running_tool(self, tool: ToolExecution) -> Text:
        display_info = self._get_tool_display_info(tool.tool_name, tool.args)
        spinner_char = Icons.SPINNER_FRAMES[self._spinner_frame % len(Icons.SPINNER_FRAMES)]
        text = Text()
        text.append(f"{Icons.OPERATION} ", style=Styles.DIM)
        text.append(self._get_action_name(tool), style=Styles.TOOL_NAME)
        text.append("(", style=Styles.DIM)
        text.append(display_info, style=Styles.FILE_PATH)
        text.append(")", style=Styles.DIM)
        text.append("\n")
        text.append(f"  {Icons.RESULT}  ", style=Styles.DIM)
        text.append(f"{spinner_char} ", style=Styles.WARNING)
        text.append("Running...", style=Styles.DIM)
        return text

    def _render_completed_tool(self, tool: ToolExecution) -> Group:
        display_info = self._get_tool_display_info(tool.tool_name, tool.args)
        elapsed = tool.end_time - tool.start_time
        items = []

        header = Text()
        header.append(f"{Icons.OPERATION} ", style=Styles.SUCCESS)
        header.append(self._get_action_name(tool), style=Styles.TOOL_NAME)
        header.append("(", style=Styles.DIM)
        header.append(display_info, style=Styles.FILE_PATH)
        header.append(")", style=Styles.DIM)
        items.append(header)

        result_line = self._render_tool_result(tool, elapsed)
        items.append(result_line)

        detail_lines = self._render_tool_detail(tool)
        if detail_lines:
            items.extend(detail_lines)

        return Group(*items)

    def _get_action_name(self, tool: ToolExecution) -> str:
        name = tool.tool_name.lower()
        action_map = {
            "skill": "Load Skill", "write": "Write", "edit": "Edit",
            "read": "Read", "todowrite": "TodoWrite", "todoread": "TodoRead",
            "websearch": "WebSearch", "browser_use": "Browser", "task_report": "TaskReport",
        }
        return action_map.get(name, tool.tool_name.capitalize() if tool.tool_name else tool.tool_name)

    def _render_tool_result(self, tool: ToolExecution, elapsed: float) -> Text:
        text = Text()
        text.append(f"  {Icons.RESULT}  ", style=Styles.DIM)
        name = tool.tool_name.lower()

        if tool.success:
            text.append(f"{Icons.SUCCESS} ", style=Styles.SUCCESS)
        else:
            text.append(f"{Icons.FAILURE} ", style=Styles.ERROR)

        if name == "read":
            result = tool.result or ""
            lines = result.split('\n')
            total_lines = len(lines)
            start_line = tool.args.get("start_line", 1)
            end_line = tool.args.get("end_line") or (start_line + total_lines - 1)
            text.append(f"Read ", style=Styles.DIM)
            text.append(f"{total_lines}", style=Styles.NUMBER)
            text.append(f" lines ", style=Styles.DIM)
            text.append(f"L{start_line} - L{end_line}", style=Styles.DIM)
        elif name == "write":
            content = tool.args.get("content", "")
            total_lines = content.count('\n') + 1 if content else 0
            text.append(f"Created ", style=Styles.DIM)
            text.append(f"{total_lines}", style=Styles.NUMBER)
            text.append(f" lines", style=Styles.DIM)
        elif name == "edit":
            old_str = tool.args.get("old_string", "")
            new_str = tool.args.get("new_string", "")
            old_list = old_str.split('\n') if old_str else []
            new_list = new_str.split('\n') if new_str else []
            diff = list(difflib.unified_diff(old_list, new_list, lineterm=''))
            added = sum(1 for d in diff if d.startswith('+') and not d.startswith('+++'))
            removed = sum(1 for d in diff if d.startswith('-') and not d.startswith('---'))
            if added > 0:
                text.append(f"+{added}", style=Styles.SUCCESS)
                text.append(" ", style=Styles.DIM)
            if removed > 0:
                text.append(f"-{removed}", style=Styles.ERROR)
                text.append(" ", style=Styles.DIM)
            if added == 0 and removed == 0:
                text.append("~0", style=Styles.DIM)
                text.append(" ", style=Styles.DIM)
            text.append("lines", style=Styles.DIM)
        elif name == "bash":
            text.append("Exit code ", style=Styles.DIM)
            text.append("0" if tool.success else "1", style=Styles.NUMBER)
        elif name == "glob":
            files = [f for f in (tool.result or "").split('\n') if f.strip()]
            text.append("Found ", style=Styles.DIM)
            text.append(f"{len(files)}", style=Styles.NUMBER)
            text.append(" files", style=Styles.DIM)
        elif name == "grep":
            matches = [m for m in (tool.result or "").split('\n') if m.strip()]
            text.append("Found ", style=Styles.DIM)
            text.append(f"{len(matches)}", style=Styles.NUMBER)
            text.append(" matches", style=Styles.DIM)
        elif name == "websearch":
            text.append("Found ", style=Styles.DIM)
            text.append("results", style=Styles.DIM)
        elif name == "todowrite":
            todos = tool.args.get("todos", [])
            if todos:
                text.append(f"{len(todos)}", style=Styles.NUMBER)
                text.append(" tasks", style=Styles.DIM)
            else:
                text.append("No tasks", style=Styles.DIM)
        elif name == "browser_use":
            # 从结果中提取关键信息
            result = tool.result or ""
            if "✅" in result or "success" in result.lower():
                text.append("Success", style=Styles.SUCCESS)
            elif "❌" in result or "error" in result.lower() or "not found" in result.lower():
                text.append("Failed", style=Styles.ERROR)
            else:
                text.append("Done", style=Styles.DIM)
        elif name == "task_report":
            result = tool.result or ""
            action = tool.args.get("action", "")
            if action == "save":
                # 提取报告 ID
                import re
                match = re.search(r"报告 ID: ([a-z0-9]+)", result)
                if match:
                    text.append(f"Report {match.group(1)}", style=Styles.DIM)
                else:
                    text.append("Saved", style=Styles.SUCCESS)
            elif action == "update":
                text.append("Updated", style=Styles.SUCCESS)
            elif action == "list":
                # 提取报告数量
                import re
                match = re.search(r"共 (\d+) 条报告", result)
                if match:
                    text.append(f"{match.group(1)} reports", style=Styles.DIM)
                else:
                    text.append("Listed", style=Styles.DIM)
            elif action == "get":
                text.append("Retrieved", style=Styles.DIM)
            else:
                text.append("Done", style=Styles.DIM)
        else:
            text.append("Done" if tool.success else "Failed", style=Styles.DIM if tool.success else Styles.ERROR)

        text.append(f" ({elapsed:.1f}s)", style=Styles.DIM)
        return text

    def _render_tool_detail(self, tool: ToolExecution) -> List[Text]:
        name = tool.tool_name.lower()
        if name == "write":
            return self._render_write_preview(tool)
        elif name == "edit":
            return self._render_edit_diff(tool)
        elif name == "bash":
            return self._render_bash_output(tool)
        return []

    def _render_write_preview(self, tool: ToolExecution) -> List[Text]:
        content = tool.args.get("content", "")
        if not content:
            return []
        lines = content.split('\n')
        preview_lines = lines[:self._max_preview_lines]
        result = []
        for i, line in enumerate(preview_lines, 1):
            text = Text()
            text.append(f"  {i:>4} ", style=Styles.DIM)
            display_line = line[:80] + "..." if len(line) > 80 else line
            text.append(f"+ {display_line}", style=f"{Styles.ADDED} {Styles.ADDED_BG}")
            result.append(text)
        if len(lines) > self._max_preview_lines:
            text = Text()
            text.append(f"  {'':>4} ", style=Styles.DIM)
            text.append("  ...", style=Styles.DIM)
            result.append(text)
        return result

    def _render_edit_diff(self, tool: ToolExecution) -> List[Text]:
        old_str = tool.args.get("old_string", "")
        new_str = tool.args.get("new_string", "")
        if not old_str and not new_str:
            return []
        # 从服务端返回的 start_line 获取文件中的实际起始行号
        base_line = tool.args.get("start_line", 1)
        old_lines = old_str.split('\n')
        new_lines = new_str.split('\n')
        result = []
        diff = list(difflib.unified_diff(old_lines, new_lines, lineterm=''))
        diff_lines = [d for d in diff if not d.startswith(('---', '+++', '@@'))]
        old_num = base_line
        new_num = base_line
        shown = 0
        for diff_line in diff_lines[:self._max_preview_lines * 2]:
            if shown >= self._max_preview_lines:
                break
            text = Text()
            if diff_line.startswith('-'):
                content = diff_line[1:]
                display = content[:80] + "..." if len(content) > 80 else content
                text.append(f"  {old_num:>4} ", style=Styles.DIM)
                text.append(f"- {display}", style=f"{Styles.REMOVED} {Styles.REMOVED_BG}")
                old_num += 1
                shown += 1
            elif diff_line.startswith('+'):
                content = diff_line[1:]
                display = content[:80] + "..." if len(content) > 80 else content
                text.append(f"  {new_num:>4} ", style=Styles.DIM)
                text.append(f"+ {display}", style=f"{Styles.ADDED} {Styles.ADDED_BG}")
                new_num += 1
                shown += 1
            else:
                display = diff_line[:80] + "..." if len(diff_line) > 80 else diff_line
                text.append(f"  {new_num:>4} ", style=Styles.DIM)
                text.append(f"  {display}", style=Styles.DIM)
                old_num += 1
                new_num += 1
            result.append(text)
        if len(diff_lines) > self._max_preview_lines * 2:
            text = Text()
            text.append(f"  {'':>4} ", style=Styles.DIM)
            text.append("  ...", style=Styles.DIM)
            result.append(text)
        return result

    def _render_bash_output(self, tool: ToolExecution) -> List[Text]:
        content = tool.result or ""
        if not content:
            return []
        lines = content.split('\n')
        preview_lines = lines[:self._max_preview_lines]
        result = []
        for line in preview_lines:
            text = Text()
            text.append("       ", style=Styles.DIM)
            text.append(line, style=Styles.DIM)
            result.append(text)
        if len(lines) >= self._max_preview_lines:
            text = Text()
            text.append("       ", style=Styles.DIM)
            text.append("...", style=Styles.DIM)
            result.append(text)
        return result

    def _get_tool_display_info(self, tool_name: str, args: Dict[str, Any]) -> str:
        name = tool_name.lower()
        if name in ["read", "write", "edit"]:
            file_path = args.get("file_path", "")
            return self._to_relative_path(file_path) if file_path else ""
        elif name == "glob":
            pattern = args.get("pattern", "")
            path = args.get("path", "")
            if path and path != ".":
                return f"{self._to_relative_path(path)}/{pattern}"
            return pattern
        elif name == "grep":
            pattern = args.get("pattern", "")
            return f'"{pattern}"' if pattern else ""
        elif name == "bash":
            cmd = args.get("command", "")
            # 1. 多行命令转单行，在换行处显示转义符号（前后加空格）
            single_line_cmd = cmd.replace('\n', ' \\n ').replace('\r', ' \\r ')
            # 2. 超过 100 字符时显示截断（与之前行为一致）
            if len(single_line_cmd) > 100:
                return single_line_cmd[:100] + "..."
            return single_line_cmd
        elif name == "websearch":
            query = args.get("query", "")
            if len(query) > 40:
                return f'"{query[:40]}..."'
            return f'"{query}"' if query else ""
        elif name == "webfetch":
            url = args.get("url", "")
            return url[:50] + "..." if len(url) > 50 else url
        elif name == "todowrite":
            return "tasks"
        elif name == "skill":
            return args.get("skill", "unknown")
        elif name == "lsp":
            operation = args.get("operation", "")
            file_path = args.get("file_path", "")
            line = args.get("line", 1)
            character = args.get("character", 1)
            rel_path = self._to_relative_path(file_path) if file_path else ""
            if operation == "workspaceSymbol":
                return f'{operation}, query="{args.get("query", "")}"'
            elif operation == "documentSymbol":
                return f"{operation}, {rel_path}"
            else:
                return f"{operation}, {rel_path}:{line}:{character}"
        elif name == "browser_use":
            cmd = args.get("command", "")
            # 简化显示
            if cmd.startswith("open ") and len(cmd.split()) > 1:
                url = cmd.split(None, 1)[1]
                return url[:40] + "..." if len(url) > 40 else url
            elif cmd.startswith("run "):
                task = cmd[4:]  # 去掉 "run "
                return f'run "{task[:30]}..."' if len(task) > 30 else f'run "{task}"'
            return cmd[:40] + "..." if len(cmd) > 40 else cmd
        elif name == "task_report":
            action = args.get("action", "")
            report_id = args.get("report_id") or args.get("get_report_id", "")
            if action == "save":
                return "save report"
            elif action == "update":
                return f"update {report_id}" if report_id else "update report"
            elif action == "list":
                limit = args.get("limit", 10)
                return f"list (limit={limit})"
            elif action == "get":
                return f"get {report_id}" if report_id else "get report"
            return action or "unknown"
        return ""

    def _to_relative_path(self, file_path: str) -> str:
        try:
            cwd = os.getcwd()
            if file_path.startswith(cwd):
                rel = os.path.relpath(file_path, cwd)
                return rel if not rel.startswith("..") else file_path
            parts = file_path.split("/")
            if len(parts) > 3:
                return ".../" + "/".join(parts[-2:])
            return file_path
        except Exception:
            return file_path

    def clear_completed(self):
        self.completed_tools.clear()
