from __future__ import annotations

import os
from datetime import datetime, timezone
from typing import Optional

from mcp.server.fastmcp import FastMCP

from bastion import __version__
from bastion.analyzers import analyze_plugin, get_analyzer, _scan_for_hardcoded_secrets
from bastion.config import is_initialized, load_config, get_bastion_dir, get_cloud_url
from bastion.discovery import discover_plugins
from bastion.remediation import get_remediation, get_all_remediations
from bastion.policy import load_policy, is_acknowledged

mcp = FastMCP("bastion-security")

DETECTION_CATEGORIES = [
    {
        "category": "system_execution",
        "description": "Detects system command execution (e.g., subprocess, os.system, child_process)",
        "severity": "High",
    },
    {
        "category": "network_access",
        "description": "Detects network/HTTP requests to external servers",
        "severity": "Medium",
    },
    {
        "category": "secret_access",
        "description": "Detects access to environment variables, credentials, or secret stores",
        "severity": "High",
    },
    {
        "category": "dynamic_code",
        "description": "Detects dynamic code execution (eval, exec, Function constructor)",
        "severity": "High",
    },
    {
        "category": "obfuscation",
        "description": "Detects obfuscated or encoded code patterns",
        "severity": "Medium",
    },
    {
        "category": "file_system_access",
        "description": "Detects file system read/write operations",
        "severity": "Medium",
    },
    {
        "category": "sandbox_bypass",
        "description": "Detects attempts to bypass sandbox or security restrictions",
        "severity": "Critical",
    },
    {
        "category": "install_risk",
        "description": "Detects potentially dangerous install/setup scripts",
        "severity": "High",
    },
    {
        "category": "hardcoded_secret",
        "description": "Detects hardcoded secrets, API keys, tokens, and credentials in source code",
        "severity": "Critical",
    },
    {
        "category": "privilege_escalation",
        "description": "Detects dangerous combinations of capabilities that together grant elevated privileges",
        "severity": "Critical",
    },
    {
        "category": "dependency_risk",
        "description": "Detects supply-chain risks in dependency files (typosquats, URL installs, unpinned versions)",
        "severity": "High",
    },
]


@mcp.tool()
def scan_directory(path: str, openclaw: bool = False) -> dict:
    """Scan a directory for AI plugin/skill supply-chain security risks. Discovers plugins and analyzes them for dangerous capabilities."""
    try:
        now = datetime.now(timezone.utc).isoformat()
        plugins = discover_plugins(path, openclaw_mode=openclaw)

        risk_summary = {"Critical": 0, "High": 0, "Medium": 0, "Low": 0}
        plugin_results = []

        for plugin in plugins:
            result = analyze_plugin(plugin, now)
            if result.risk_level in risk_summary:
                risk_summary[result.risk_level] += 1

            plugin_results.append({
                "name": plugin.name,
                "path": plugin.path,
                "risk_level": result.risk_level,
                "capabilities": [
                    {
                        "category": c.category,
                        "severity": c.severity,
                        "remediation": get_remediation(c.category)["summary"],
                    }
                    for c in result.capabilities
                ],
                "dependencies": result.dependencies,
                "skills": result.skills,
            })

        has_critical = risk_summary.get("Critical", 0) > 0 or risk_summary.get("High", 0) > 0

        return {
            "total_plugins": len(plugins),
            "risk_summary": risk_summary,
            "plugins": plugin_results,
            "has_critical_findings": has_critical,
        }
    except Exception as e:
        return {"error": str(e)}


@mcp.tool()
def analyze_file(file_path: str) -> dict:
    """Analyze a single file for security risks, detecting dangerous capabilities and hardcoded secrets."""
    try:
        if not os.path.isfile(file_path):
            return {"error": f"File not found: {file_path}"}

        ext = os.path.splitext(file_path)[1].lower()
        capabilities = []

        analyzer_cls = get_analyzer(ext)
        if analyzer_cls:
            analyzer = analyzer_cls()
            capabilities.extend(analyzer.analyze(file_path))

        capabilities.extend(_scan_for_hardcoded_secrets(file_path))

        severity_order = {"Low": 0, "Medium": 1, "High": 2, "Critical": 3}
        risk_level = "Low"
        for cap in capabilities:
            if severity_order.get(cap.severity, 0) > severity_order.get(risk_level, 0):
                risk_level = cap.severity

        return {
            "file": file_path,
            "risk_level": risk_level,
            "total_capabilities": len(capabilities),
            "capabilities": [
                {
                    "category": c.category,
                    "description": c.description,
                    "severity": c.severity,
                    "evidence": [e.to_dict() for e in c.evidence],
                    "remediation": get_remediation(c.category)["summary"],
                }
                for c in capabilities
            ],
        }
    except Exception as e:
        return {"error": str(e)}


@mcp.tool()
def get_project_status() -> dict:
    """Return the current Bastion project status and configuration."""
    try:
        if not is_initialized():
            return {"error": "Bastion is not initialized. Run 'bastion init' first."}

        config = load_config()
        bastion_dir = get_bastion_dir()
        baseline_path = bastion_dir / "baseline.json"

        return {
            "project_id": config.get("project_id", ""),
            "version": config.get("version", __version__),
            "has_baseline": baseline_path.exists(),
            "cloud_connected": config.get("cloud_connected", False),
            "cloud_server": config.get("cloud_server", ""),
        }
    except Exception as e:
        return {"error": str(e)}


@mcp.tool()
def list_detection_categories() -> list:
    """Return all security detection categories that Bastion can identify, with descriptions and severity levels."""
    return DETECTION_CATEGORIES


@mcp.tool()
def check_trust_score(project_id: Optional[str] = None) -> dict:
    """Check the trust score from Bastion Cloud for a connected project."""
    try:
        if not is_initialized():
            return {"error": "Bastion is not initialized. Run 'bastion init' first."}

        config = load_config()

        if not config.get("cloud_connected"):
            return {"error": "Project is not connected to Bastion Cloud. Run 'bastion connect' first."}

        cloud_server = config.get("cloud_server", "").rstrip("/")
        public_id = project_id or config.get("cloud_public_id", config.get("project_id", ""))

        if not cloud_server or not public_id:
            return {"error": "Cloud configuration is incomplete. Run 'bastion connect' again."}

        import requests

        resp = requests.get(
            f"{cloud_server}/api/verify/{public_id}",
            timeout=30,
        )

        if resp.status_code == 200:
            data = resp.json()
            return {
                "trust_score": data.get("trust_score"),
                "verification_status": data.get("verification_status", ""),
                "last_scan_date": data.get("last_scan_date", ""),
            }
        else:
            return {"error": f"Cloud API returned status {resp.status_code}"}
    except Exception as e:
        return {"error": str(e)}


@mcp.tool()
def get_policy() -> dict:
    """Return the current security policy (acknowledged capabilities) for this project."""
    try:
        if not is_initialized():
            return {"error": "Bastion is not initialized. Run 'bastion init' first."}

        bastion_dir = get_bastion_dir()
        policy = load_policy(bastion_dir)
        acknowledged = policy.get("acknowledged", {})

        total = sum(len(entries) for entries in acknowledged.values())
        return {
            "total_acknowledgments": total,
            "acknowledged": acknowledged,
        }
    except Exception as e:
        return {"error": str(e)}


@mcp.tool()
def get_remediation_guidance(category: str) -> dict:
    """Get remediation guidance for a specific capability category (e.g., system_execution, network_access)."""
    return get_remediation(category)


def run_server():
    mcp.run()


if __name__ == "__main__":
    run_server()
