from __future__ import annotations

import re
from typing import Dict, List, Optional

from bastion.analyzers.base import BaseAnalyzer
from bastion.models import Capability, Evidence

CATEGORY_DESCRIPTIONS = {
    "system_execution": "System command execution capability detected in SKILL.md",
    "sandbox_bypass": "Sandbox bypass configuration detected in SKILL.md",
    "network_access": "Network access capability detected in SKILL.md",
    "secret_access": "Secret/environment variable access detected in SKILL.md",
    "file_system_access": "File system access capability detected in SKILL.md",
}

SHELL_LANG_PATTERN = re.compile(r"^```(bash|sh|cmd|powershell|shell|zsh)\s*$", re.IGNORECASE)
CODE_BLOCK_START = re.compile(r"^```")
CODE_BLOCK_END = re.compile(r"^```\s*$")

SYSTEM_EXEC_PATTERNS = [
    (re.compile(r"\bos\.system\b"), "os.system"),
    (re.compile(r"\bsubprocess\b"), "subprocess"),
    (re.compile(r"\bchild_process\b"), "child_process"),
    (re.compile(r"\bexec\s*\("), "exec_call"),
    (re.compile(r"\beval\s*\("), "eval_call"),
    (re.compile(r"\bcurl\b.*\|\s*(bash|sh)\b"), "curl_pipe_shell"),
    (re.compile(r"\bwget\b.*\|\s*(bash|sh)\b"), "wget_pipe_shell"),
    (re.compile(r"\bpip\s+install\b"), "pip_install"),
    (re.compile(r"\brm\s+-rf\b"), "rm_rf"),
    (re.compile(r"\bchmod\b"), "chmod"),
    (re.compile(r"\bchown\b"), "chown"),
    (re.compile(r"\bsudo\b"), "sudo"),
]

SANDBOX_BYPASS_PATTERNS = [
    (re.compile(r"--yolo\b"), "yolo_flag"),
    (re.compile(r"\belevated\s*:\s*true\b", re.IGNORECASE), "elevated_true"),
    (re.compile(r"--full-auto\b"), "full_auto_flag"),
    (re.compile(r"--no-sandbox\b"), "no_sandbox_flag"),
    (re.compile(r"--disable-sandbox\b"), "disable_sandbox_flag"),
    (re.compile(r"\bsandbox\s*:\s*false\b", re.IGNORECASE), "sandbox_false"),
    (re.compile(r'security\s*:\s*["\']?deny["\']?', re.IGNORECASE), "security_deny"),
    (re.compile(r"\bLOCALHOST_TRUST_ENABLED\b"), "localhost_trust_enabled"),
]

NETWORK_ACCESS_PATTERNS = [
    (re.compile(r"https?://[^\s\)\"'`>\]]+"), "external_url"),
    (re.compile(r"\bcurl\b"), "curl_command"),
    (re.compile(r"\bwget\b"), "wget_command"),
    (re.compile(r"\bfetch\s*\("), "fetch_call"),
    (re.compile(r"\brequests\.(get|post|put|delete|patch)\b"), "requests_call"),
    (re.compile(r"\bwebhook\b", re.IGNORECASE), "webhook_reference"),
]

SECRET_ACCESS_PATTERNS = [
    (re.compile(r"\bprocess\.env\b"), "process_env"),
    (re.compile(r"\bos\.environ\b"), "os_environ"),
    (re.compile(r"\$ENV\b"), "env_var_ref"),
    (re.compile(r"\$\{[A-Z_]+\}"), "env_var_interpolation"),
    (re.compile(r"\bOPENAI_API_KEY\b"), "openai_api_key"),
    (re.compile(r"\bAPI_KEY\b"), "api_key"),
    (re.compile(r"\bSECRET\b"), "secret_reference"),
    (re.compile(r"\bTOKEN\b"), "token_reference"),
    (re.compile(r"\bPASSWORD\b"), "password_reference"),
    (re.compile(r"\.env\b"), "dotenv_file"),
    (re.compile(r"\bcredentials?\b", re.IGNORECASE), "credentials_reference"),
    (re.compile(r"\bauth[_\s]?token\b", re.IGNORECASE), "auth_token_reference"),
]

FILE_SYSTEM_PATTERNS = [
    (re.compile(r"\bcat\s+"), "cat_command"),
    (re.compile(r"\bls\s+"), "ls_command"),
    (re.compile(r"\bfind\s+"), "find_command"),
    (re.compile(r"\bgrep\s+"), "grep_command"),
    (re.compile(r"~/\.ssh\b"), "ssh_dir"),
    (re.compile(r"~/\.aws\b"), "aws_dir"),
    (re.compile(r"/etc/passwd\b"), "etc_passwd"),
    (re.compile(r"~/\.config\b"), "config_dir"),
    (re.compile(r"\bfs\.readFile\b"), "fs_readFile"),
    (re.compile(r"\bopen\s*\("), "open_call"),
    (re.compile(r"\bpathlib\b"), "pathlib_reference"),
]

INSTALL_CMD_PATTERN = re.compile(r"\b(brew|apt|apt-get|npm|pip)\s+install\b")


class SkillMDAnalyzer(BaseAnalyzer):

    def analyze(self, file_path: str) -> List[Capability]:
        try:
            with open(file_path, "r", encoding="utf-8", errors="replace") as f:
                source = f.read()
        except (OSError, IOError):
            return []

        lines = source.splitlines()
        capabilities: Dict[str, Capability] = {}

        frontmatter = self._extract_frontmatter(source)
        if frontmatter:
            self._analyze_frontmatter(frontmatter, file_path, lines, capabilities)

        in_code_block = False
        is_shell_block = False

        for lineno_0, line in enumerate(lines):
            lineno = lineno_0 + 1

            if not in_code_block and SHELL_LANG_PATTERN.match(line.strip()):
                in_code_block = True
                is_shell_block = True
                self._add_evidence(
                    capabilities, "system_execution", "Critical",
                    file_path, lineno, line.rstrip(), "shell_code_block",
                )
                continue
            elif not in_code_block and CODE_BLOCK_START.match(line.strip()):
                in_code_block = True
                is_shell_block = False
                continue
            elif in_code_block and CODE_BLOCK_END.match(line.strip()):
                in_code_block = False
                is_shell_block = False
                continue

            stripped = line.rstrip()

            for pattern, name in SANDBOX_BYPASS_PATTERNS:
                if pattern.search(line):
                    self._add_evidence(
                        capabilities, "sandbox_bypass", "Critical",
                        file_path, lineno, stripped, name,
                    )

            for pattern, name in SYSTEM_EXEC_PATTERNS:
                if pattern.search(line):
                    self._add_evidence(
                        capabilities, "system_execution", "Critical",
                        file_path, lineno, stripped, name,
                    )

            for pattern, name in NETWORK_ACCESS_PATTERNS:
                if pattern.search(line):
                    self._add_evidence(
                        capabilities, "network_access", "High",
                        file_path, lineno, stripped, name,
                    )

            for pattern, name in SECRET_ACCESS_PATTERNS:
                if pattern.search(line):
                    self._add_evidence(
                        capabilities, "secret_access", "High",
                        file_path, lineno, stripped, name,
                    )

            for pattern, name in FILE_SYSTEM_PATTERNS:
                if pattern.search(line):
                    self._add_evidence(
                        capabilities, "file_system_access", "Medium",
                        file_path, lineno, stripped, name,
                    )

        return list(capabilities.values())

    def _add_evidence(
        self,
        capabilities: Dict[str, Capability],
        category: str,
        severity: str,
        file_path: str,
        lineno: int,
        snippet: str,
        pattern_name: str,
    ) -> None:
        evidence = Evidence(
            file=file_path,
            line=lineno,
            code_snippet=snippet,
            pattern_name=pattern_name,
        )
        if category not in capabilities:
            capabilities[category] = Capability(
                category=category,
                description=CATEGORY_DESCRIPTIONS.get(category, category),
                evidence=[evidence],
                severity=severity,
            )
        else:
            cap = capabilities[category]
            cap.evidence.append(evidence)
            severity_order = {"Low": 0, "Medium": 1, "High": 2, "Critical": 3}
            if severity_order.get(severity, 0) > severity_order.get(cap.severity, 0):
                cap.severity = severity

    def _extract_frontmatter(self, source: str) -> Optional[str]:
        if not source.startswith("---"):
            return None
        end = source.find("---", 3)
        if end == -1:
            return None
        return source[3:end]

    def _analyze_frontmatter(
        self,
        frontmatter: str,
        file_path: str,
        all_lines: List[str],
        capabilities: Dict[str, Capability],
    ) -> None:
        fm_lines = frontmatter.splitlines()
        in_install = False
        in_requires = False

        for i, fm_line in enumerate(fm_lines):
            lineno = i + 2
            stripped = fm_line.strip()

            if "install" in fm_line.lower() and ":" in fm_line:
                in_install = True
                in_requires = False
                continue
            if "requires" in fm_line.lower() and ":" in fm_line:
                in_requires = True
                in_install = False
                continue
            if fm_line and not fm_line[0].isspace() and ":" in fm_line:
                in_install = False
                in_requires = False

            if in_install and stripped.startswith("-"):
                cmd = stripped.lstrip("- ").strip()
                if INSTALL_CMD_PATTERN.search(cmd):
                    self._add_evidence(
                        capabilities, "system_execution", "Critical",
                        file_path, lineno, stripped, "frontmatter_install_cmd",
                    )

            if in_requires and stripped.startswith("-"):
                binary = stripped.lstrip("- ").strip()
                if binary:
                    self._add_evidence(
                        capabilities, "system_execution", "Medium",
                        file_path, lineno, stripped, "frontmatter_required_binary",
                    )

            for pattern, name in SANDBOX_BYPASS_PATTERNS:
                if pattern.search(fm_line):
                    self._add_evidence(
                        capabilities, "sandbox_bypass", "Critical",
                        file_path, lineno, stripped, f"frontmatter_{name}",
                    )
