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

from brox.model import Component, Property, generate_bom_ref


AGENT_FRAMEWORKS = {
    "langchain": {
        "import_patterns": [r"import\s+langchain", r"from\s+langchain\s+import"],
        "package_names": ["langchain", "langchain-core", "langchain-community"],
    },
    "llamaindex": {
        "import_patterns": [r"import\s+llama_index", r"from\s+llama_index\s+import"],
        "package_names": ["llama-index", "llama_index"],
    },
    "autogen": {
        "import_patterns": [r"import\s+autogen", r"from\s+autogen\s+import"],
        "package_names": ["pyautogen", "autogen"],
    },
    "crewai": {
        "import_patterns": [r"import\s+crewai", r"from\s+crewai\s+import"],
        "package_names": ["crewai"],
    },
    "semantic-kernel": {
        "import_patterns": [r"import\s+semantic_kernel", r"from\s+semantic_kernel\s+import"],
        "package_names": ["semantic-kernel"],
    },
    "haystack": {
        "import_patterns": [r"import\s+haystack", r"from\s+haystack\s+import"],
        "package_names": ["haystack-ai"],
    },
}


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

    code_patterns = ["*.py", "*.js", "*.ts", "*.jsx", "*.tsx"]
    code_files = []
    for pattern in code_patterns:
        code_files.extend(repo_path.rglob(pattern))

    detected_frameworks = set()

    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:
            frameworks_in_file = detect_frameworks_in_file(code_file, repo_path)
            for framework, evidence in frameworks_in_file:
                if framework not in detected_frameworks:
                    detected_frameworks.add(framework)
                    component = create_framework_component(framework, evidence)
                    components.append(component)
        except Exception as e:
            print(f"Warning: Failed to detect frameworks in {code_file}: {e}")

    return components, []


def detect_frameworks_in_file(code_file: Path, repo_root: Path) -> List[Tuple[str, 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, config in AGENT_FRAMEWORKS.items():
        for pattern in config["import_patterns"]:
            matches = list(re.finditer(pattern, content))
            if matches:
                line_num = content[: matches[0].start()].count("\n") + 1
                evidence = f"{rel_path}:{line_num}"
                detected.append((framework, evidence))
                break

    return detected


def create_framework_component(framework: str, evidence: str) -> Component:
    bom_ref = generate_bom_ref("agent_framework", framework)

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

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

    return component
