"""子智能体特有工具实现（search_workspace / read_mediafile）。

这些工具在主进程中没有同名工具，因此由子智能体管理器本地实现。
search_workspace 优先复用主进程的 run_command 调用 rg，回退到 Python 遍历；
read_mediafile 直接读取项目内媒体文件并返回 base64。
"""

from __future__ import annotations

import base64
import fnmatch
import json
import mimetypes
import re
import shlex
import shutil
from pathlib import Path
from typing import Any, Dict, List, Optional


async def handle_search_workspace(
    project_path: Path,
    terminal: Any,
    arguments: Dict[str, Any],
) -> Dict[str, Any]:
    """search_workspace 实现。"""
    mode = arguments.get("mode")
    query = arguments.get("query") or ""
    root = arguments.get("root") or "."
    use_regex = bool(arguments.get("use_regex", False))
    case_sensitive = bool(arguments.get("case_sensitive", False))
    max_results = int(arguments.get("max_results", 20))
    max_matches_per_file = int(arguments.get("max_matches_per_file", 3))
    include_glob = arguments.get("include_glob") or ["**/*"]
    exclude_glob = arguments.get("exclude_glob") or []
    max_file_size = arguments.get("max_file_size")

    if mode not in {"file", "content"}:
        return {"success": False, "error": "search_workspace mode 必须是 file 或 content"}

    root_path = (project_path / root).resolve()
    try:
        root_path.relative_to(project_path)
    except Exception:
        return {"success": False, "error": "搜索根目录必须位于项目目录内"}

    if mode == "file":
        return _search_workspace_file(project_path, root_path, query, use_regex, case_sensitive, max_results, include_glob, exclude_glob)

    rg_result = await _search_workspace_content_rg(
        project_path, root_path, query, use_regex, case_sensitive, max_results, max_matches_per_file,
        include_glob, exclude_glob, max_file_size, terminal
    )
    if rg_result is not None:
        return rg_result

    return _search_workspace_content_python(
        project_path, root_path, query, use_regex, case_sensitive, max_results, max_matches_per_file,
        include_glob, exclude_glob, max_file_size
    )


def _search_workspace_file(
    project_path: Path,
    root_path: Path,
    query: str,
    use_regex: bool,
    case_sensitive: bool,
    max_results: int,
    include_glob: List[str],
    exclude_glob: List[str],
) -> Dict[str, Any]:
    try:
        pattern = re.compile(query, 0 if case_sensitive else re.IGNORECASE) if use_regex else None
        needle = query if case_sensitive else query.lower()
        matches = []
        for item in _iter_files(project_path, root_path, include_glob, exclude_glob):
            name = item.name
            hay = name if case_sensitive else name.lower()
            ok = pattern.search(name) if pattern else needle in hay
            if ok:
                matches.append(str(item.relative_to(project_path)))
                if len(matches) >= max_results:
                    break
        return {
            "success": True,
            "mode": "file",
            "root": str(root_path.relative_to(project_path)),
            "query": query,
            "matches": matches,
        }
    except Exception as exc:
        return {"success": False, "error": str(exc)}


async def _search_workspace_content_rg(
    project_path: Path,
    root_path: Path,
    query: str,
    use_regex: bool,
    case_sensitive: bool,
    max_results: int,
    max_matches_per_file: int,
    include_glob: List[str],
    exclude_glob: List[str],
    max_file_size: Optional[int],
    terminal: Any,
) -> Optional[Dict[str, Any]]:
    if not shutil.which("rg"):
        return None
    try:
        args = ["rg", "--line-number", "--no-heading", "--color=never"]
        if not use_regex:
            args.append("--fixed-strings")
        if not case_sensitive:
            args.append("-i")
        if max_matches_per_file:
            args.append(f"--max-count={max_matches_per_file}")
        if max_file_size:
            args.append(f"--max-filesize={max_file_size}")
        for g in include_glob:
            args.extend(["-g", g])
        for g in exclude_glob:
            args.extend(["-g", f"!{g}"])
        args.extend([query, str(root_path)])

        result_text = await terminal.handle_tool_call("run_command", {"command": shlex.join(args), "timeout": 60})
        parsed = json.loads(result_text) if isinstance(result_text, str) else result_text
        if not parsed.get("success"):
            return None

        files_map: Dict[str, List[Dict[str, Any]]] = {}
        for line in (parsed.get("output") or "").splitlines():
            if not line.strip():
                continue
            parts = line.split(":", 2)
            if len(parts) < 3:
                continue
            file_path, line_num, snippet = parts[0], parts[1], parts[2]
            try:
                line_no = int(line_num)
            except ValueError:
                continue
            files_map.setdefault(file_path, [])
            if len(files_map[file_path]) < max_matches_per_file:
                files_map[file_path].append({"line": line_no, "snippet": snippet.strip()})
            if len(files_map) >= max_results and len(files_map[file_path]) >= max_matches_per_file:
                break

        results = [{"file": f, "matches": ms} for f, ms in list(files_map.items())[:max_results]]
        return {
            "success": True,
            "mode": "content",
            "root": str(root_path.relative_to(project_path)),
            "query": query,
            "results": results,
        }
    except Exception:
        return None


def _search_workspace_content_python(
    project_path: Path,
    root_path: Path,
    query: str,
    use_regex: bool,
    case_sensitive: bool,
    max_results: int,
    max_matches_per_file: int,
    include_glob: List[str],
    exclude_glob: List[str],
    max_file_size: Optional[int],
) -> Dict[str, Any]:
    try:
        pattern = re.compile(query, 0 if case_sensitive else re.IGNORECASE) if use_regex else None
        needle = query if case_sensitive else query.lower()
        results = []
        for file_path in _iter_files(project_path, root_path, include_glob, exclude_glob):
            if max_file_size:
                try:
                    if file_path.stat().st_size > max_file_size:
                        continue
                except Exception:
                    continue
            try:
                text = file_path.read_text(encoding="utf-8", errors="ignore")
            except Exception:
                continue
            lines = text.splitlines()
            matches = []
            for idx, line in enumerate(lines, 1):
                hay = line if case_sensitive else line.lower()
                ok = pattern.search(line) if pattern else needle in hay
                if ok:
                    matches.append({"line": idx, "snippet": line.strip()})
                    if len(matches) >= max_matches_per_file:
                        break
            if matches:
                results.append({"file": str(file_path.relative_to(project_path)), "matches": matches})
                if len(results) >= max_results:
                    break
        return {
            "success": True,
            "mode": "content",
            "root": str(root_path.relative_to(project_path)),
            "query": query,
            "results": results,
        }
    except Exception as exc:
        return {"success": False, "error": str(exc)}


def _iter_files(project_path: Path, root_path: Path, include_glob: List[str], exclude_glob: List[str]):
    """按 glob 规则遍历文件。"""
    for item in root_path.rglob("*"):
        if not item.is_file():
            continue
        try:
            rel = item.relative_to(project_path)
        except Exception:
            continue
        rel_str = str(rel)
        included = any(fnmatch.fnmatch(rel_str, g) for g in include_glob)
        if not included:
            continue
        if any(fnmatch.fnmatch(rel_str, g) for g in exclude_glob):
            continue
        yield item


async def handle_read_mediafile(project_path: Path, arguments: Dict[str, Any]) -> Dict[str, Any]:
    """read_mediafile 实现：直接读取项目内媒体文件并返回 base64。"""
    path = arguments.get("path")
    if not path:
        return {"success": False, "error": "path 不能为空"}
    try:
        abs_path = (project_path / path).resolve()
        abs_path.relative_to(project_path)
    except Exception:
        return {"success": False, "error": "非法路径，超出项目根目录"}

    if not abs_path.exists() or not abs_path.is_file():
        return {"success": False, "error": f"文件不存在: {path}"}

    mime, _ = mimetypes.guess_type(str(abs_path))
    if not mime or (not mime.startswith("image/") and not mime.startswith("video/")):
        return {"success": False, "error": "禁止的文件类型"}

    try:
        data = abs_path.read_bytes()
        b64 = base64.b64encode(data).decode("utf-8")
        file_type = "image" if mime.startswith("image/") else "video"
        return {"success": True, "path": path, "mime": mime, "type": file_type, "b64": b64}
    except Exception as exc:
        return {"success": False, "error": str(exc)}
