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

from brox.model import Component, Property, generate_bom_ref, hash_content, hash_content_short


RISK_PATTERNS = [
    r"ignore\s+previous",
    r"bypass\s+safety",
    r"exfiltrate",
    r"reveal\s+secrets",
]


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

    file_components = detect_prompt_files(repo_path)
    components.extend(file_components)

    inline_components = detect_inline_prompts(repo_path)
    components.extend(inline_components)

    return components, []


def detect_prompt_files(repo_path: Path) -> List[Component]:
    components = []

    extensions = [".prompt", ".jinja", ".jinja2", ".md"]
    priority_dirs = ["prompts", "agents", "system", ".prompts"]

    prompt_files = []

    for dir_name in priority_dirs:
        for ext in extensions:
            prompt_files.extend(repo_path.rglob(f"{dir_name}/**/*{ext}"))

    for ext in [".prompt", ".jinja", ".jinja2"]:
        prompt_files.extend(repo_path.rglob(f"*{ext}"))

    prompt_files = list(set(prompt_files))

    for prompt_file in prompt_files:
        if any(
            part in prompt_file.parts
            for part in ["node_modules", ".git", "venv", "__pycache__", "dist", "build"]
        ):
            continue

        try:
            component = parse_prompt_file(prompt_file, repo_path)
            if component:
                components.append(component)
        except Exception as e:
            print(f"Warning: Failed to parse prompt file {prompt_file}: {e}")

    return components


def parse_prompt_file(prompt_file: Path, repo_root: Path) -> Component:
    rel_path = str(prompt_file.relative_to(repo_root))

    with open(prompt_file, encoding="utf-8", errors="ignore") as f:
        content = f.read()

    content_hash = hash_content(content)
    prompt_type = determine_prompt_type(prompt_file.name, content)
    bom_ref = generate_bom_ref("prompt", "file", rel_path)

    properties = [
        Property(name="brox.ai.asset.kind", value="prompt"),
        Property(name="brox.ai.prompt.type", value=prompt_type),
        Property(name="brox.ai.prompt.source", value="file"),
        Property(name="brox.location.file", value=rel_path),
        Property(name="brox.location.line_start", value="1"),
        Property(name="brox.hash.content", value=f"sha256:{content_hash}"),
    ]

    risk_signals = detect_risk_signals(content)
    if risk_signals:
        properties.append(
            Property(name="brox.ai.prompt.risk_signals", value=",".join(risk_signals))
        )

    component = Component(
        type="data", name=f"prompt:{rel_path}", version="1", bom_ref=bom_ref, properties=properties
    )

    return component


def detect_inline_prompts(repo_path: Path) -> List[Component]:
    components = []

    code_patterns = ["*.py", "*.js", "*.ts", "*.jsx", "*.tsx"]

    code_files = []
    for pattern in code_patterns:
        code_files.extend(repo_path.rglob(pattern))

    for code_file in code_files:
        if any(
            part in code_file.parts
            for part in ["node_modules", ".git", "venv", "__pycache__", "dist", "build"]
        ):
            continue

        try:
            inline_components = parse_inline_prompts(code_file, repo_path)
            components.extend(inline_components)
        except Exception as e:
            print(f"Warning: Failed to parse inline prompts in {code_file}: {e}")

    return components


def parse_inline_prompts(code_file: Path, repo_root: Path) -> List[Component]:
    components = []

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

    rel_path = str(code_file.relative_to(repo_root))

    python_pattern = r'("""|\'\'\')(.+?)\1'
    js_pattern = r"`(.+?)`"

    if code_file.suffix == ".py":
        patterns = [python_pattern, js_pattern]
    else:
        patterns = [js_pattern]

    for pattern in patterns:
        matches = re.finditer(pattern, content, re.DOTALL)
        for match in matches:
            if "?" in pattern and match.lastindex >= 2:
                prompt_content = match.group(2)
            else:
                prompt_content = match.group(1)

            if len(prompt_content) < 200:
                continue

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

            llm_keywords = [
                "system",
                "developer",
                "instructions",
                "prompt",
                "openai",
                "anthropic",
                "llm",
                "chatgpt",
                "claude",
                "gpt",
                "completion",
                "generate",
            ]

            if not any(keyword in context for keyword in llm_keywords):
                continue

            line_num = content[: match.start()].count("\n") + 1

            prompt_type = "unknown"
            if any(keyword in context for keyword in ["system", "system_prompt"]):
                prompt_type = "system"
            elif any(keyword in context for keyword in ["developer", "developer_prompt"]):
                prompt_type = "developer"
            elif any(keyword in context for keyword in ["user", "user_prompt"]):
                prompt_type = "user"

            content_hash = hash_content(prompt_content)
            hash_short = hash_content_short(prompt_content, 12)

            bom_ref = generate_bom_ref("prompt", "inline", rel_path, str(line_num), hash_short)

            properties = [
                Property(name="brox.ai.asset.kind", value="prompt"),
                Property(name="brox.ai.prompt.type", value=prompt_type),
                Property(name="brox.ai.prompt.source", value="inline"),
                Property(name="brox.location.file", value=rel_path),
                Property(name="brox.location.line_start", value=str(line_num)),
                Property(name="brox.hash.content", value=f"sha256:{content_hash}"),
            ]

            risk_signals = detect_risk_signals(prompt_content)
            if risk_signals:
                properties.append(
                    Property(name="brox.ai.prompt.risk_signals", value=",".join(risk_signals))
                )

            component = Component(
                type="data",
                name=f"prompt:{rel_path}:{line_num}",
                version="1",
                bom_ref=bom_ref,
                properties=properties,
            )

            components.append(component)

    return components


def determine_prompt_type(filename: str, content: str) -> str:
    filename_lower = filename.lower()
    content_lower = content.lower()

    if "system" in filename_lower or "system_prompt" in content_lower[:500]:
        return "system"
    elif "developer" in filename_lower or "developer_prompt" in content_lower[:500]:
        return "developer"
    elif "user" in filename_lower or "user_prompt" in content_lower[:500]:
        return "user"
    else:
        return "unknown"


def detect_risk_signals(content: str) -> List[str]:
    signals = []
    content_lower = content.lower()

    for pattern in RISK_PATTERNS:
        if re.search(pattern, content_lower):
            signal = pattern.replace(r"\s+", " ").replace("\\", "")
            signals.append(signal)

    return signals
