import re
from pathlib import Path
from typing import List, Tuple

from brox.model import Capability, Component, Property, generate_bom_ref


TOOL_PATTERNS = {
    "langchain": {
        "decorators": [
            r"@tool\s*\n\s*def\s+(\w+)",
            r"@langchain_tool\s*\n\s*def\s+(\w+)",
        ],
        "classes": [
            r"class\s+(\w+)\(.*BaseTool",
            r"class\s+(\w+)\(.*StructuredTool",
        ],
        "registrations": [
            r"Tool\s*\(\s*name\s*=\s*['\"](\w+)['\"]",
            r"StructuredTool\.from_function\s*\(\s*(\w+)",
        ],
    },
    "crewai": {
        "decorators": [
            r"@tool\s*\n\s*def\s+(\w+)",
            r"@crewai\.tool\s*\n\s*def\s+(\w+)",
        ],
        "classes": [
            r"class\s+(\w+)\(.*CrewAITool",
        ],
        "registrations": [
            r"Tool\s*\(\s*name\s*=\s*['\"](\w+)['\"]",
        ],
    },
    "autogen": {
        "decorators": [],
        "registrations": [
            r"register_function\s*\(\s*name\s*=\s*['\"](\w+)['\"]",
            r"register_for_execution\s*\(\s*name\s*=\s*['\"](\w+)['\"]",
            r"register_for_llm\s*\(\s*name\s*=\s*['\"](\w+)['\"]",
        ],
    },
    "semantic_kernel": {
        "decorators": [
            r"@kernel_function\s*\n\s*def\s+(\w+)",
            r"@sk_function\s*\n\s*def\s+(\w+)",
        ],
        "registrations": [
            r"kernel\.import_function\s*\(\s*['\"](\w+)['\"]",
        ],
    },
}


def detect_agent_tools(repo_path: Path) -> Tuple[List[Component], List[Capability]]:
    components = []
    capabilities = []

    code_files = list(repo_path.rglob("*.py"))

    detected_tools = {}

    for code_file in code_files:
        should_skip = False
        exclusions = ["node_modules", ".git", "venv", "__pycache__", "dist", "build"]
        for part in code_file.parts:
            if part in exclusions:
                should_skip = True
                break

        if should_skip:
            continue

        try:
            tools_in_file = detect_tools_in_file(code_file, repo_path)
            for tool_name, framework, evidence, capabilities_list in tools_in_file:
                key = (tool_name, framework)
                if key not in detected_tools:
                    detected_tools[key] = (evidence, capabilities_list)
                    component, caps = create_tool_component(
                        tool_name, framework, evidence, capabilities_list
                    )
                    components.append(component)
                    capabilities.extend(caps)
        except Exception as e:
            print(f"Warning: Failed to detect agent tools in {code_file}: {e}")

    return components, capabilities


def detect_tools_in_file(
    code_file: Path, repo_root: Path
) -> List[Tuple[str, str, str, List[str]]]:
    detected = []

    try:
        with open(code_file, encoding="utf-8", errors="ignore") as f:
            content = f.read()
    except Exception:
        return detected

    rel_path = str(code_file.relative_to(repo_root))

    for framework, patterns in TOOL_PATTERNS.items():
        for pattern in patterns["decorators"]:
            matches = re.finditer(pattern, content)
            for match in matches:
                tool_name = match.group(1)
                line_num = content[: match.start()].count("\n") + 1
                evidence = f"{rel_path}:{line_num}"

                caps = infer_tool_capabilities(content, tool_name, match.start())
                detected.append((tool_name, framework, evidence, caps))

        for pattern in patterns.get("classes", []):
            matches = re.finditer(pattern, content)
            for match in matches:
                tool_name = match.group(1)
                line_num = content[: match.start()].count("\n") + 1
                evidence = f"{rel_path}:{line_num}"

                caps = infer_tool_capabilities(content, tool_name, match.start())
                detected.append((tool_name, framework, evidence, caps))

        for pattern in patterns.get("registrations", []):
            matches = re.finditer(pattern, content)
            for match in matches:
                tool_name = match.group(1)
                line_num = content[: match.start()].count("\n") + 1
                evidence = f"{rel_path}:{line_num}"

                caps = infer_tool_capabilities(content, tool_name, match.start())
                detected.append((tool_name, framework, evidence, caps))

    return detected


def infer_tool_capabilities(content: str, tool_name: str, start_pos: int) -> List[str]:
    capabilities = []

    context_start = max(0, start_pos - 500)
    context_end = min(len(content), start_pos + 1000)
    context = content[context_start:context_end].lower()

    tool_name_lower = tool_name.lower()

    fs_keywords = ["file", "read", "write", "save", "load", "directory", "path"]
    has_fs_keyword = False
    for keyword in fs_keywords:
        if keyword in tool_name_lower:
            has_fs_keyword = True
            break

    if has_fs_keyword:
        if "write" in tool_name_lower or "save" in tool_name_lower:
            capabilities.append("filesystem.write")
        if "read" in tool_name_lower or "load" in tool_name_lower:
            capabilities.append("filesystem.read")

    fs_context_keywords = ["open(", "pathlib", "os.path", "shutil"]
    for keyword in fs_context_keywords:
        if keyword in context:
            capabilities.append("filesystem.read")
            break

    shell_keywords = ["shell", "exec", "command", "run", "subprocess", "bash"]
    for keyword in shell_keywords:
        if keyword in tool_name_lower:
            capabilities.append("exec.shell")
            break

    shell_context_keywords = ["subprocess", "os.system", "exec(", "popen"]
    for keyword in shell_context_keywords:
        if keyword in context:
            capabilities.append("exec.shell")
            break

    network_keywords = ["http", "api", "fetch", "request", "web", "url"]
    for keyword in network_keywords:
        if keyword in tool_name_lower:
            capabilities.append("network.egress")
            break

    network_context_keywords = ["requests.", "urllib.", "http", "fetch("]
    for keyword in network_context_keywords:
        if keyword in context:
            capabilities.append("network.egress")
            break

    db_keywords = ["db", "database", "sql", "query"]
    for keyword in db_keywords:
        if keyword in tool_name_lower:
            if "write" in tool_name_lower or "insert" in tool_name_lower:
                capabilities.append("db.write")
            else:
                capabilities.append("db.read")
            break

    db_context_keywords = ["cursor.", "execute(", "query(", "insert "]
    for keyword in db_context_keywords:
        if keyword in context:
            capabilities.append("db.read")
            break

    if not capabilities:
        capabilities.append("tool.unknown")

    return capabilities


def create_tool_component(
    tool_name: str, framework: str, evidence: str, capabilities_list: List[str]
) -> Tuple[Component, List[Capability]]:
    bom_ref = generate_bom_ref("agent_tool", framework, tool_name)

    properties = [
        Property(name="brox.ai.asset.kind", value="agent_tool"),
        Property(name="brox.agent.framework", value=framework),
        Property(name="brox.agent.tool.name", value=tool_name),
        Property(name="brox.location.file", value=evidence.split(":")[0]),
    ]

    component = Component(
        type="library",
        name=f"agent-tool:{framework}:{tool_name}",
        version="1",
        bom_ref=bom_ref,
        properties=properties,
    )

    capabilities = []
    unique_caps = set(capabilities_list)
    for cap_name in unique_caps:
        if cap_name != "tool.unknown":
            capabilities.append(
                Capability(
                    name=cap_name,
                    scope="*",
                    asset=bom_ref,
                    evidence=[evidence],
                )
            )

    return component, capabilities
