"""
LangChain Tools 定义
"""

import subprocess
import re
from pathlib import Path

from langchain.tools import tool, ToolRuntime
from typing import List, Optional, Dict, Any

from .skill_loader import SkillLoader
from .stream.utils import resolve_path
from .context import SkillAgentContext
from .director_tools import DIRECTOR_TOOLS


@tool
def load_skill(skill_name: str, runtime: ToolRuntime[SkillAgentContext]) -> str:
    """Load a skill's detailed instructions. Automatically includes any dependent skills. Returns the SKILL.md instructions and a list of available references."""
    loader = runtime.context.skill_loader
    skill_content = loader.load_skill(skill_name)

    if not skill_content:
        skills = loader.scan_skills()
        if skills:
            available = [s.name for s in skills]
            return f"Skill '{skill_name}' not found. Available skills: {', '.join(available)}"
        else:
            return f"Skill '{skill_name}' not found. No skills are currently available."

    skill_path = skill_content.metadata.skill_path
    scripts_dir = skill_path / "scripts"

    # 构建依赖 Skills 部分
    dependencies_section = ""
    if skill_content.loaded_skills:
        dependencies_section = "\n## Dependent Skills\n\n"
        dependencies_section += "This skill depends on the following skills, which have been automatically loaded:\n\n"
        for dep_name, dep_content in skill_content.loaded_skills.items():
            dependencies_section += f"### {dep_name}\n\n"
            dependencies_section += f"{dep_content.instructions}\n\n"
            # 如果依赖 skill 也有 references，也列出来
            if dep_content.references:
                dependencies_section += f"**Available References for {dep_name}**: "
                dependencies_section += ", ".join(f"`{ref.name}`" for ref in dep_content.references)
                dependencies_section += "\n\n"
        dependencies_section += "---\n\n"

    # 构建 References 部分
    references_section = ""
    if skill_content.references:
        references_section = "\n## Available References\n\n"
        references_section += "This skill has the following reference documents available:\n\n"
        for ref in skill_content.references:
            references_section += f"{ref.to_prompt_line()}\n"
        references_section += "\n"
        references_section += "Use `load_reference(skill_name, reference_name)` to load a specific reference when needed.\n"

    path_info = f"""
## Skill Path Info

- **Skill Directory**: `{skill_path}`
- **Scripts Directory**: `{scripts_dir}`

**Important**: When running scripts, use absolute paths like:
```bash
uv run {scripts_dir}/script_name.py [args]
```
"""

    return f"""# Skill: {skill_name}

{dependencies_section}
## Instructions

{skill_content.instructions}
{path_info}
{references_section}
"""


@tool
def load_reference(skill_name: str, reference_name: str, runtime: ToolRuntime[SkillAgentContext]) -> str:
    """Load a reference document from a skill's references/ directory. Use this when the skill instructions indicate that additional reference material is needed for a specific task."""
    loader = runtime.context.skill_loader
    content = loader.load_reference(skill_name, reference_name)

    if not content:
        # 检查 skill 是否存在
        skill_content = loader.load_skill(skill_name)
        if not skill_content:
            return f"Skill '{skill_name}' not found."

        # 列出可用的 references
        if skill_content.references:
            available = [r.name for r in skill_content.references]
            return f"Reference '{reference_name}' not found in skill '{skill_name}'. Available references: {', '.join(available)}"
        else:
            return f"Skill '{skill_name}' has no references available."

    return f"""# Reference: {skill_name}/{reference_name}

{content}
"""


@tool
def bash(command: str, runtime: ToolRuntime[SkillAgentContext]) -> str:
    """Execute a shell command (bash on Unix/macOS, cmd.exe on Windows)."""
    cwd = str(runtime.context.working_directory)

    try:
        result = subprocess.run(
            command,
            shell=True,
            cwd=cwd,
            capture_output=True,
            text=True,
            timeout=300,
        )

        parts = []
        if result.returncode == 0:
            parts.append("[OK]")
        else:
            parts.append(f"[FAILED] Exit code: {result.returncode}")

        parts.append("")

        if result.stdout:
            parts.append(result.stdout.rstrip())

        if result.stderr:
            if result.stdout:
                parts.append("")
            parts.append("--- stderr ---")
            parts.append(result.stderr.rstrip())

        if not result.stdout and not result.stderr:
            parts.append("(no output)")

        return "\n".join(parts)

    except subprocess.TimeoutExpired:
        return "[FAILED] Command timed out after 300 seconds."
    except Exception as e:
        return f"[FAILED] {str(e)}"


@tool
def read_file(file_path: str, runtime: ToolRuntime[SkillAgentContext]) -> str:
    """Read the contents of a file."""
    path = resolve_path(file_path, runtime.context.working_directory)

    if not path.exists():
        return f"[Error] File not found: {file_path}"

    if not path.is_file():
        return f"[Error] Not a file: {file_path}"

    try:
        content = path.read_text(encoding="utf-8")
        lines = content.split("\n")
        numbered_lines = []
        for i, line in enumerate(lines[:2000], 1):
            numbered_lines.append(f"{i:4d}| {line}")

        if len(lines) > 2000:
            numbered_lines.append(f"... ({len(lines) - 2000} more lines)")

        return "\n".join(numbered_lines)

    except Exception as e:
        return f"[Error] Failed to read file: {str(e)}"


@tool
def write_file(file_path: str, content: str, runtime: ToolRuntime[SkillAgentContext]) -> str:
    """Write content to a file."""
    path = resolve_path(file_path, runtime.context.working_directory)

    try:
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(content, encoding="utf-8")
        return f"[Success] File written: {path}"

    except Exception as e:
        return f"[Error] Failed to write file: {str(e)}"


@tool
def glob(pattern: str, runtime: ToolRuntime[SkillAgentContext]) -> str:
    """Find files matching a glob pattern."""
    cwd = runtime.context.working_directory
    try:
        matches = sorted(cwd.glob(pattern))
        if not matches:
            return f"No files matching pattern: {pattern}"

        max_results = 100
        result_lines = []
        for path in matches[:max_results]:
            try:
                rel_path = path.relative_to(cwd)
                result_lines.append(str(rel_path))
            except ValueError:
                result_lines.append(str(path))

        result = "\n".join(result_lines)
        if len(matches) > max_results:
            result += f"\n... and {len(matches) - max_results} more files"
        return f"[OK]\n\n{result}"
    except Exception as e:
        return f"[FAILED] {str(e)}"


@tool
def grep(pattern: str, path: str, runtime: ToolRuntime[SkillAgentContext]) -> str:
    """Search for a pattern in files."""
    cwd = runtime.context.working_directory
    search_path = resolve_path(path, cwd)

    try:
        regex = re.compile(pattern)
    except re.error as e:
        return f"[FAILED] Invalid regex pattern: {e}"

    results = []
    max_results = 50
    files_searched = 0

    try:
        if search_path.is_file():
            files = [search_path]
        else:
            files = []
            for p in search_path.rglob("*"):
                if p.is_file():
                    parts = p.parts
                    if any(part.startswith(".") or part in ("node_modules", "__pycache__", ".git", "venv", ".venv") for part in parts):
                        continue
                    files.append(p)

        for file_path in files:
            if len(results) >= max_results:
                break
            try:
                content = file_path.read_text(encoding="utf-8", errors="ignore")
                lines = content.split("\n")
                files_searched += 1

                for line_num, line in enumerate(lines, 1):
                    if regex.search(line):
                        try:
                            rel_path = file_path.relative_to(cwd)
                        except ValueError:
                            rel_path = file_path
                        results.append(f"{rel_path}:{line_num}: {line.strip()[:100]}")
                        if len(results) >= max_results:
                            break
            except Exception:
                continue

        if not results:
            return f"No matches found for pattern: {pattern} (searched {files_searched} files)"

        output = "\n".join(results)
        if len(results) >= max_results:
            output += f"\n... (truncated, showing first {max_results} matches)"
        return f"[OK]\n\n{output}"

    except Exception as e:
        return f"[FAILED] {str(e)}"


@tool
def edit(
    file_path: str,
    old_string: str,
    new_string: str,
    runtime: ToolRuntime[SkillAgentContext]
) -> str:
    """Edit a file by replacing text."""
    path = resolve_path(file_path, runtime.context.working_directory)

    if not path.exists():
        return f"[FAILED] File not found: {file_path}"
    if not path.is_file():
        return f"[FAILED] Not a file: {file_path}"

    try:
        content = path.read_text(encoding="utf-8")
        count = content.count(old_string)
        if count == 0:
            return f"[FAILED] String not found in file."
        if count > 1:
            return f"[FAILED] String appears {count} times in file."

        new_content = content.replace(old_string, new_string, 1)
        path.write_text(new_content, encoding="utf-8")
        old_lines = len(old_string.split("\n"))
        new_lines = len(new_string.split("\n"))
        return f"[OK]\n\nEdited {path.name}: replaced {old_lines} lines with {new_lines} lines"

    except Exception as e:
        return f"[FAILED] {str(e)}"


@tool
def list_dir(path: str, runtime: ToolRuntime[SkillAgentContext]) -> str:
    """List contents of a directory."""
    dir_path = resolve_path(path, runtime.context.working_directory)

    if not dir_path.exists():
        return f"[FAILED] Directory not found: {path}"
    if not dir_path.is_dir():
        return f"[FAILED] Not a directory: {path}"

    try:
        entries = sorted(dir_path.iterdir(), key=lambda p: (not p.is_dir(), p.name.lower()))
        result_lines = []
        for entry in entries[:100]:
            if entry.is_dir():
                result_lines.append(f"📁 {entry.name}/")
            else:
                size = entry.stat().st_size
                if size < 1024:
                    size_str = f"{size}B"
                elif size < 1024 * 1024:
                    size_str = f"{size // 1024}KB"
                else:
                    size_str = f"{size // (1024 * 1024)}MB"
                result_lines.append(f"   {entry.name} ({size_str})")

        if len(entries) > 100:
            result_lines.append(f"... and {len(entries) - 100} more entries")

        return f"[OK]\n\n{chr(10).join(result_lines)}"

    except Exception as e:
        return f"[FAILED] {str(e)}"


ALL_TOOLS = [
    load_skill,
    load_reference,  # 按需加载 skill 的 references/ 文档
    bash,
    read_file,
    write_file,
    glob,
    grep,
    edit,
    list_dir,
    # Director Tools (includes update_plan, add_inbox_task, add_annotation, update_context, write_to_canvas)
    *DIRECTOR_TOOLS,
]
