"""
Supreme 2 MAX — MCP Tools

All ``@mcp.tool()`` decorated functions are registered here.

Tools
~~~~~
* ``analyze_project``      — analyse project structure, languages, frameworks.
* ``run_security_scan``    — run full / incremental / quick security scan.
* ``get_scan_results``     — retrieve & filter issues from a stored scan.
* ``generate_report``      — build an aggregated JSON report and persist it.
* ``analyze_findings``     — false-positive analysis and issue prioritisation.
"""

from __future__ import annotations

import asyncio
import copy
import io
import json
import os
import re
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

from supreme_max.mcp_server._server import mcp
from supreme_max.mcp_server.state import ScanStateManager
from supreme_max.mcp_server.security import (
    SecurityError,
    safe_validate_project_path,
    sanitize_analyzed_issues,
    sanitize_scan_results,
)
from supreme_max.core.pattern_analyzer import CodePatternAnalyzer


# ---------------------------------------------------------------------------
# Singleton state manager — created lazily so unit tests can monkey-patch.
# ---------------------------------------------------------------------------

_state_manager: Optional[ScanStateManager] = None


def _get_state_manager() -> ScanStateManager:
    global _state_manager
    if _state_manager is None:
        _state_manager = ScanStateManager()
    return _state_manager


# ---------------------------------------------------------------------------
# Scanner class name -> user-friendly tool/scanner identifier
# ---------------------------------------------------------------------------

_SCANNER_DISPLAY_NAMES: Dict[str, str] = {
    "PythonScanner": "bandit",
    "JavaScriptScanner": "eslint",
    "TypeScriptScanner": "typescript_scanner",
    "GoScanner": "golangci_lint",
    "RustScanner": "cargo_audit",
    "RubyScanner": "rubocop",
    "PHPScanner": "phpstan",
    "JavaScanner": "spotbugs",
    "KotlinScanner": "detekt",
    "ScalaScanner": "scalastyle",
    "CSharpScanner": "csharp_scanner",
    "CppScanner": "cppcheck",
    "SwiftScanner": "swiftlint",
    "PerlScanner": "perlcritic",
    "BashScanner": "shellcheck",
    "PowerShellScanner": "psscriptanalyzer",
    "BatScanner": "bat_scanner",
    "LuaScanner": "luacheck",
    "RScanner": "r_scanner",
    "ElixirScanner": "elixir_scanner",
    "HaskellScanner": "hlint",
    "ClojureScanner": "clj_kondo",
    "DartScanner": "dart_scanner",
    "GroovyScanner": "groovy_scanner",
    "SolidityScanner": "slither",
    "ZigScanner": "zig_scanner",
    "YAMLScanner": "yaml_scanner",
    "JSONScanner": "json_scanner",
    "TOMLScanner": "toml_scanner",
    "XMLScanner": "xml_scanner",
    "EnvScanner": "env_scanner",
    "TerraformScanner": "tflint",
    "DockerScanner": "hadolint",
    "HTMLScanner": "htmlhint",
    "CSSScanner": "stylelint",
    "SQLScanner": "sqlfluff",
    "GraphQLScanner": "graphql_scanner",
    "ProtobufScanner": "protobuf_scanner",
    "MarkdownScanner": "markdownlint",
    # AI / built-in scanners
    "MCPConfigScanner": "mcp_config_scanner",
    "MCPServerScanner": "mcp_server_scanner",
    "AIContextScanner": "ai_context_scanner",
    "AgentMemoryScanner": "agent_memory_scanner",
    "RAGSecurityScanner": "rag_security_scanner",
    "A2AScanner": "a2a_scanner",
    "PromptLeakageScanner": "prompt_leakage_scanner",
    "ToolCallbackScanner": "tool_callback_scanner",
    "OWASPLLMScanner": "owasp_llm_scanner",
    "ModelAttackScanner": "model_attack_scanner",
    "MultiAgentScanner": "multi_agent_scanner",
    "LLMOpsScanner": "llmops_scanner",
    # External integrations
    "GitLeaksScanner": "gitleaks",
    "TrivyScanner": "trivy",
    "SemgrepScanner": "semgrep",
}

# Reverse map: user-friendly name -> internal scanner class name
_DISPLAY_TO_CLASS: Dict[str, str] = {
    v: k for k, v in _SCANNER_DISPLAY_NAMES.items()
}


# ---------------------------------------------------------------------------
# Shared helpers
# ---------------------------------------------------------------------------

def _estimate_scan_time(total_files: int, num_scanners: int) -> str:
    """Return a human-readable scan time estimate string."""
    base_seconds = total_files * num_scanners * 0.05
    if base_seconds < 10:
        return "5-15 seconds"
    if base_seconds < 30:
        return "15-30 seconds"
    if base_seconds < 60:
        return "30-45 seconds"
    if base_seconds < 120:
        return "1-2 minutes"
    if base_seconds < 300:
        return "2-5 minutes"
    return "5+ minutes"


def _scanner_names(class_names: set) -> list:
    """Convert internal scanner class names to user-friendly identifiers."""
    return sorted(
        _SCANNER_DISPLAY_NAMES.get(name, name) for name in class_names
    )


def _capitalize_language(lang: str) -> str:
    """Capitalise language names to match the expected output style."""
    special = {
        "python": "Python",
        "javascript": "JavaScript",
        "typescript": "TypeScript",
        "csharp": "C#",
        "cpp": "C++",
        "c": "C",
        "objectivec": "Objective-C",
        "bash": "Bash",
        "powershell": "PowerShell",
        "graphql": "GraphQL",
        "protobuf": "Protobuf",
        "yaml": "YAML",
        "json": "JSON",
        "toml": "TOML",
        "xml": "XML",
        "html": "HTML",
        "css": "CSS",
        "sql": "SQL",
        "php": "PHP",
        "ini": "INI",
        "env": "ENV",
        "markdown": "Markdown",
        "config": "Config",
        "properties": "Properties",
    }
    return special.get(lang, lang.capitalize())


# ===================================================================
# Tool 1 — analyze_project
# ===================================================================

@mcp.tool()
async def analyze_project(
    project_path: str,
    include_dependencies: bool = False,
) -> Dict[str, Any]:
    """Analyze a project's structure, languages, frameworks, and security patterns.

    Scans the project directory using CodePatternAnalyzer to detect programming
    languages, frameworks, security patterns, and AI/LLM usage.  Returns a
    structured summary with recommended scanners so that only the relevant
    subset of the 74 available scanners is executed.

    Args:
        project_path: Absolute or relative path to the project root directory.
        include_dependencies: When True, include dependency directories
            (node_modules, venv, etc.) in the analysis.  Default is False.

    Returns:
        A dictionary with project_summary, recommended_scanners,
        security_context, and scan_time_estimate.
    """
    # Validate input (security: path traversal & sandbox check)
    try:
        resolved = safe_validate_project_path(project_path)
    except SecurityError as exc:
        return {"error": str(exc)}
    if not resolved.exists():
        return {"error": f"Path not found: {project_path}"}
    if not resolved.is_dir():
        return {"error": f"Path is not a directory: {project_path}"}

    # Run CodePatternAnalyzer
    analyzer = CodePatternAnalyzer()

    if include_dependencies:
        original_skip = CodePatternAnalyzer.SKIP_DIRECTORIES
        CodePatternAnalyzer.SKIP_DIRECTORIES = set()
        try:
            analysis = analyzer.analyze_repo(resolved)
        finally:
            CodePatternAnalyzer.SKIP_DIRECTORIES = original_skip
    else:
        analysis = analyzer.analyze_repo(resolved)

    # Build response
    languages_map: Dict[str, int] = {
        _capitalize_language(lang): count
        for lang, count in analysis.languages.items()
    }

    recommended = _scanner_names(analysis.recommended_scanners)
    file_extensions: Dict[str, int] = dict(analysis.file_extensions)
    sc = analysis.security_context

    return {
        "project_summary": {
            "total_files": analysis.total_files,
            "languages": languages_map,
            "frameworks": sorted(analysis.frameworks),
            "file_extensions": file_extensions,
        },
        "recommended_scanners": recommended,
        "security_context": {
            "uses_orm": sc.uses_orm,
            "has_input_validation": sc.has_input_validation,
            "has_ci_config": sc.has_ci_config,
            "has_ai_patterns": sc.has_ai_patterns,
            "has_mcp_config": sc.has_mcp_config,
        },
        "scan_time_estimate": _estimate_scan_time(
            analysis.total_files, len(recommended),
        ),
    }


# ===================================================================
# Tool 2 — run_security_scan
# ===================================================================

_VALID_MODES = {"full", "incremental", "quick"}
_VALID_SEVERITIES = {"all", "critical", "high", "medium", "low"}


def _filter_issues_by_severity(
    issues: List[Dict[str, Any]], severity_filter: str
) -> List[Dict[str, Any]]:
    """Keep only issues at or above *severity_filter*."""
    if severity_filter == "all":
        return issues

    ranking = {"critical": 4, "high": 3, "medium": 2, "low": 1, "info": 0}
    threshold = ranking.get(severity_filter, 0)

    return [
        issue
        for issue in issues
        if ranking.get(issue.get("severity", "").lower(), 0) >= threshold
    ]


def _build_issue_entry(
    issue: Dict[str, Any],
    file_path: str,
    scanner_name: str,
    issue_index: int,
) -> Dict[str, Any]:
    """Normalise a raw issue dict into the output schema shape."""
    return {
        "id": f"{scanner_name}-{issue_index}",
        "severity": issue.get("severity", "MEDIUM"),
        "scanner": scanner_name,
        "rule_id": issue.get("rule_id") or issue.get("code") or "",
        "file": file_path,
        "line": issue.get("line") or issue.get("line_number") or 0,
        "message": (
            issue.get("message")
            or issue.get("issue_text")
            or issue.get("issue")
            or str(issue)
        ),
        "code_snippet": issue.get("code") or "",
        "remediation": issue.get("remediation", ""),
    }


@mcp.tool()
async def run_security_scan(
    project_path: str,
    scanners: Optional[List[str]] = None,
    mode: str = "full",
    severity_filter: str = "all",
    exclude_paths: Optional[List[str]] = None,
    parallel_workers: Optional[int] = None,
) -> Dict[str, Any]:
    """Run a security scan on a project and return structured results.

    Executes the SupremeMaxParallelScanner autonomously, discovers files,
    runs all (or selected) scanners in parallel across multiple CPU cores,
    de-duplicates findings and persists the results via ScanStateManager.

    Args:
        project_path: Absolute or relative path to the project root.
        scanners: Optional list of scanner names to run (e.g. ["bandit",
            "eslint"]).  When omitted every applicable scanner is used.
        mode: Scan mode — "full" (all files), "incremental" (only changed
            files via cache) or "quick" (changed files only, no cache write).
        severity_filter: Minimum severity to include in results — "all",
            "critical", "high", "medium" or "low".
        exclude_paths: Additional directory/file patterns to skip.
        parallel_workers: Number of worker processes.  ``None`` = auto-detect
            CPU cores.

    Returns:
        A dictionary with scan_id, summary, issues, and report_path.
    """
    # ------------------------------------------------------------------
    # 1. Validate inputs (security: path traversal & sandbox check)
    # ------------------------------------------------------------------
    try:
        resolved = safe_validate_project_path(project_path)
    except SecurityError as exc:
        return {"error": str(exc)}
    if not resolved.exists():
        return {"error": f"Path not found: {project_path}"}
    if not resolved.is_dir():
        return {"error": f"Path is not a directory: {project_path}"}

    mode = mode.lower()
    if mode not in _VALID_MODES:
        return {"error": f"Invalid mode '{mode}'. Must be one of: {', '.join(sorted(_VALID_MODES))}"}

    severity_filter = severity_filter.lower()
    if severity_filter not in _VALID_SEVERITIES:
        return {"error": f"Invalid severity_filter '{severity_filter}'. Must be one of: {', '.join(sorted(_VALID_SEVERITIES))}"}

    # ------------------------------------------------------------------
    # 2. Initialise the parallel scanner
    # ------------------------------------------------------------------
    from supreme_max.core.parallel import SupremeMaxParallelScanner

    # Signal subprocess context — forces sequential scanning to avoid
    # multiprocessing issues on Windows
    os.environ['SUPREME2L_SUBPROCESS'] = '1'

    quick_mode = mode in ("quick", "incremental")
    use_cache = mode != "full"

    scanner = SupremeMaxParallelScanner(
        project_root=resolved,
        workers=parallel_workers,
        use_cache=use_cache,
        quick_mode=quick_mode,
    )

    # Honour extra exclude_paths from the caller
    if exclude_paths:
        for ep in exclude_paths:
            if ep not in scanner.config.exclude_paths:
                scanner.config.exclude_paths.append(ep)

    # ------------------------------------------------------------------
    # 3. Discover files & run scan in a thread (CPU-bound)
    #    Redirect stdout to avoid corrupting the JSON-RPC channel
    # ------------------------------------------------------------------
    wall_start = time.monotonic()

    def _do_scan():
        _saved_stdout = sys.stdout
        sys.stdout = io.StringIO()  # swallow all output silently
        try:
            files = scanner.find_scannable_files()
            if not files:
                return [], files
            results = scanner.scan_parallel(files)
            if scanner.cache:
                scanner.cache.save()
            return results, files
        finally:
            sys.stdout = _saved_stdout

    results, files = await asyncio.to_thread(_do_scan)

    wall_seconds = time.monotonic() - wall_start

    if not files:
        return {
            "scan_id": None,
            "summary": {
                "files_scanned": 0,
                "issues_found": 0,
                "scan_time": "0.00s",
                "scanners_used": [],
            },
            "issues": [],
            "report_path": None,
            "message": "No scannable files found.",
        }

    # ------------------------------------------------------------------
    # 4. Collect & normalise findings
    # ------------------------------------------------------------------
    all_issues: List[Dict[str, Any]] = []
    scanners_used: set[str] = set()
    files_cached = 0
    issue_counter = 0

    for res in results:
        if res.cached:
            files_cached += 1
            continue

        scanner_label = res.scanner or "unknown"
        if scanner_label != "cached":
            scanners_used.add(scanner_label)

        for raw_issue in res.issues:
            issue_counter += 1
            entry = _build_issue_entry(
                raw_issue if isinstance(raw_issue, dict) else raw_issue.to_dict(),
                res.file,
                scanner_label,
                issue_counter,
            )
            all_issues.append(entry)

    # Apply severity filter
    filtered_issues = _filter_issues_by_severity(all_issues, severity_filter)

    # ------------------------------------------------------------------
    # 5. Generate JSON report
    # ------------------------------------------------------------------
    from supreme_max.core.reporter import SupremeMaxReportGenerator

    report_dir = resolved / ".supreme2l" / "reports"
    report_dir.mkdir(parents=True, exist_ok=True)

    reporter_findings = []
    for issue in all_issues:  # use unfiltered for persistent report
        reporter_findings.append({
            "scanner": issue["scanner"],
            "file": issue["file"],
            "line": issue["line"],
            "severity": issue["severity"],
            "confidence": "HIGH",
            "issue": issue["message"],
            "cwe": issue.get("rule_id", ""),
            "code": issue.get("code_snippet", ""),
        })

    scan_results_for_reporter = {
        "findings": reporter_findings,
        "files_scanned": len(results) - files_cached,
        "total_lines_scanned": 0,
    }

    generator = SupremeMaxReportGenerator(report_dir)
    timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
    json_report_path = generator.generate_json_report(
        scan_results_for_reporter,
        report_dir / f"supreme2l-scan-{timestamp}.json",
    )

    # ------------------------------------------------------------------
    # 6. Persist via ScanStateManager
    # ------------------------------------------------------------------
    state = _get_state_manager()

    files_scanned_count = len(results) - files_cached
    sorted_scanners = sorted(scanners_used)

    scan_data: Dict[str, Any] = {
        "project_path": str(resolved),
        "mode": mode,
        # Top-level fields for ScanStateManager compatibility
        "scanners_used": sorted_scanners,
        "files_scanned": files_scanned_count,
        "issues_found": len(filtered_issues),
        "scan_time": round(wall_seconds, 2),
        # Summary dict for richer consumers
        "summary": {
            "files_scanned": files_scanned_count,
            "files_cached": files_cached,
            "issues_found": len(filtered_issues),
            "scan_time": f"{wall_seconds:.2f}s",
            "scanners_used": sorted_scanners,
        },
        "issues": filtered_issues,
        "report_path": str(json_report_path),
    }

    scan_id = state.store_scan(scan_data)

    # ------------------------------------------------------------------
    # 7. Sanitize secrets & return response
    # ------------------------------------------------------------------
    sanitize_scan_results(filtered_issues)

    return {
        "scan_id": scan_id,
        "summary": scan_data["summary"],
        "issues": filtered_issues,
        "report_path": str(json_report_path),
    }


# ===================================================================
# Tool 3 — get_scan_results
# ===================================================================

@mcp.tool()
async def get_scan_results(
    scan_id: str = "latest",
    filters: Optional[Dict[str, Any]] = None,
    severity: Optional[str] = None,
    file_pattern: Optional[str] = None,
    scanner: Optional[str] = None,
    limit: int = 100,
) -> Dict[str, Any]:
    """Return filtered issues from a previously stored scan.

    Parameters
    ----------
    scan_id:
        UUID of the scan **or** the literal ``"latest"``.
    filters:
        Optional dict with any combination of:

        * ``severity``     — list of severity strings (case-insensitive).
        * ``file_pattern`` — regex applied to ``issue["file"]``.
        * ``scanner``      — exact scanner name.
        * ``rule_id``      — exact rule id.
    severity:
        Comma-separated severity levels (e.g. "critical,high").
        Shortcut alternative to ``filters.severity``.
    file_pattern:
        Regex pattern to filter by file path.
        Shortcut alternative to ``filters.file_pattern``.
    scanner:
        Exact scanner name to filter by.
        Shortcut alternative to ``filters.scanner``.
    limit:
        Max issues returned (applied *after* filtering).  Default 100.

    Returns
    -------
    dict
        ``{"scan_id": "…", "issues": […]}`` on success,
        ``{"error": "…"}`` when the scan is not found.
    """
    state = _get_state_manager()
    scan_result = state.get_scan(scan_id)

    if scan_result is None:
        return {"error": f"Scan '{scan_id}' not found"}

    # Work on a *shallow copy* of the issues list — never mutate the original.
    issues: List[Dict[str, Any]] = copy.deepcopy(
        scan_result.get("issues", [])
    )

    # Resolve the real scan_id when "latest" was requested.
    resolved_id = scan_id
    if scan_id == "latest":
        meta = state.get_scan_metadata("latest")
        if meta:
            resolved_id = meta["scan_id"]

    # ── Build effective filters from both `filters` dict and shortcut params ──
    effective_filters: Dict[str, Any] = dict(filters) if filters else {}
    if severity and "severity" not in effective_filters:
        effective_filters["severity"] = [s.strip() for s in severity.split(",")]
    if file_pattern and "file_pattern" not in effective_filters:
        effective_filters["file_pattern"] = file_pattern
    if scanner and "scanner" not in effective_filters:
        effective_filters["scanner"] = scanner

    # ── Apply filters ─────────────────────────────────────────────
    if effective_filters:
        # severity — list of allowed levels, compared upper-cased.
        severity_filter: Optional[List[str]] = effective_filters.get("severity")
        if severity_filter:
            allowed = {s.upper() for s in severity_filter}
            issues = [
                i for i in issues
                if str(i.get("severity", "")).upper() in allowed
            ]

        # file_pattern — regex matched against issue["file"].
        fp: Optional[str] = effective_filters.get("file_pattern")
        if fp:
            try:
                compiled = re.compile(fp)
                issues = [
                    i for i in issues
                    if compiled.search(str(i.get("file", "")))
                ]
            except re.error:
                pass  # Silently ignore invalid regex.

        # scanner — exact match.
        scanner_filter: Optional[str] = effective_filters.get("scanner")
        if scanner_filter:
            issues = [
                i for i in issues
                if i.get("scanner") == scanner_filter
            ]

        # rule_id — exact match.
        rule_id_filter: Optional[str] = effective_filters.get("rule_id")
        if rule_id_filter:
            issues = [
                i for i in issues
                if i.get("rule_id") == rule_id_filter
            ]

    # ── Limit ─────────────────────────────────────────────────────
    issues = issues[:limit]

    return {
        "scan_id": resolved_id,
        "issues": issues,
    }


# ===================================================================
# Tool 4 — generate_report
# ===================================================================

_REPORTS_DIR = Path.home() / ".supreme2l" / "reports"

# Severity penalty weights for the security score.
_SEVERITY_PENALTIES: Dict[str, int] = {
    "CRITICAL": -30,
    "HIGH":     -15,
    "MEDIUM":    -5,
    "LOW":       -1,
}


def _compute_security_score(issues: List[Dict[str, Any]]) -> int:
    """Compute a 0-100 security score from a list of issues."""
    penalty = sum(
        _SEVERITY_PENALTIES.get(str(i.get("severity", "")).upper(), 0)
        for i in issues
    )
    return max(0, 100 + penalty)


def _risk_level(score: int) -> str:
    """Map a security score to a human-readable risk level."""
    if score >= 90:
        return "EXCELLENT"
    if score >= 75:
        return "GOOD"
    if score >= 50:
        return "MODERATE"
    if score >= 25:
        return "CONCERNING"
    return "CRITICAL"


def _aggregate_issues(issues: List[Dict[str, Any]]) -> Dict[str, Any]:
    """Build ``by_severity``, ``by_file``, and ``by_scanner`` aggregations."""
    by_severity: Dict[str, int] = {}
    by_file: Dict[str, int] = {}
    by_scanner: Dict[str, int] = {}

    for issue in issues:
        sev = str(issue.get("severity", "UNKNOWN")).upper()
        by_severity[sev] = by_severity.get(sev, 0) + 1

        fpath = str(issue.get("file", "unknown"))
        by_file[fpath] = by_file.get(fpath, 0) + 1

        scanner_name = str(issue.get("scanner", "unknown"))
        by_scanner[scanner_name] = by_scanner.get(scanner_name, 0) + 1

    return {
        "by_severity": by_severity,
        "by_file": by_file,
        "by_scanner": by_scanner,
    }


@mcp.tool()
async def generate_report(scan_id: str = "latest") -> Dict[str, Any]:
    """Generate a full JSON report for a stored scan.

    The report is saved to ``~/.supreme2l/reports/`` with a timestamped
    filename and the path is recorded in the SQLite database.

    Parameters
    ----------
    scan_id:
        UUID of the scan **or** ``"latest"``.

    Returns
    -------
    dict
        ``{"report_path": "…", "format": "json", "message": "…"}``
        on success, ``{"error": "…"}`` on failure.
    """
    state = _get_state_manager()
    scan_result = state.get_scan(scan_id)

    if scan_result is None:
        return {"error": f"Scan '{scan_id}' not found"}

    # Resolve the real scan_id.
    resolved_id = scan_id
    if scan_id == "latest":
        meta = state.get_scan_metadata("latest")
        if meta:
            resolved_id = meta["scan_id"]

    issues: List[Dict[str, Any]] = scan_result.get("issues", [])

    security_score = _compute_security_score(issues)
    risk = _risk_level(security_score)
    aggregations = _aggregate_issues(issues)

    now = datetime.now(timezone.utc)

    report: Dict[str, Any] = {
        "metadata": {
            "scan_id": resolved_id,
            "project_path": scan_result.get("project_path", ""),
            "generated_at": now.isoformat(),
            "scanner_version": scan_result.get("scanner_version", "1.0.0"),
        },
        "summary": {
            "files_scanned": scan_result.get("files_scanned", 0),
            "issues_found": len(issues),
            "scan_time": scan_result.get("scan_time", 0.0),
            "scanners_used": scan_result.get("scanners_used", []),
        },
        "aggregations": aggregations,
        "security_score": security_score,
        "risk_level": risk,
        "issues": issues,
    }

    # Persist report -----------------------------------------------------------
    _REPORTS_DIR.mkdir(parents=True, exist_ok=True)
    filename = f"supreme2l-scan-{now.strftime('%Y%m%d-%H%M%S')}.json"
    report_path = _REPORTS_DIR / filename
    report_path.write_text(json.dumps(report, indent=2, default=str), encoding="utf-8")

    # Update metadata in SQLite ------------------------------------------------
    state.update_scan_report_path(resolved_id, str(report_path))

    return {
        "report_path": str(report_path),
        "format": "json",
        "message": "Report generated successfully",
    }


# ===================================================================
# Tool 5 — analyze_findings
# ===================================================================

_VALID_FP_LEVELS = {"aggressive", "moderate", "conservative"}
_VALID_PRIORITIZE = {"severity", "scanner", "file"}

# Confidence thresholds per fp_filter_level.
_FP_CONFIDENCE_THRESHOLDS: Dict[str, float] = {
    "aggressive": 0.50,
    "moderate": 0.70,
    "conservative": 0.90,
}

_SEVERITY_RANK: Dict[str, int] = {
    "CRITICAL": 5,
    "HIGH": 4,
    "MEDIUM": 3,
    "LOW": 2,
    "INFO": 1,
}


def _priority_score(issue: Dict[str, Any]) -> float:
    """Higher = more urgent.  Combines severity + FP confidence inverse."""
    sev = _SEVERITY_RANK.get(issue.get("severity", "MEDIUM").upper(), 3)
    fp_conf = issue.get("fp_analysis", {}).get("confidence", 0.0)
    return sev * 10 + (1 - fp_conf) * 5


def _issue_context_flags(
    issue: Dict[str, Any],
    test_dirs: set,
    has_validation: bool,
    security_patterns: set,
) -> Dict[str, bool]:
    """Derive contextual boolean flags for a single issue."""
    file_path = issue.get("file", "").lower()

    in_test = any(td.lower() in file_path for td in test_dirs) or any(
        marker in file_path
        for marker in ("test_", "/tests/", "/test/", "__tests__", "/spec/")
    )

    uses_safe = any(
        pat in file_path for pat in ("secure", "crypto", "auth")
    ) or any(
        sp.lower() in file_path for sp in security_patterns
    )

    return {
        "in_test_file": in_test,
        "has_validation": has_validation,
        "uses_safe_api": uses_safe,
    }


def _normalise_finding_for_fp_filter(issue: Dict[str, Any]) -> Dict[str, Any]:
    """Convert the MCP issue schema into the dict shape FalsePositiveFilter
    expects (keys: file, line, scanner, severity, issue, code)."""
    return {
        "file": issue.get("file", ""),
        "line": issue.get("line", 0),
        "scanner": issue.get("scanner", ""),
        "severity": issue.get("severity", "MEDIUM"),
        "issue": issue.get("message", ""),
        "code": issue.get("code_snippet", ""),
        "rule_id": issue.get("rule_id", ""),
    }


@mcp.tool()
async def analyze_findings(
    scan_id: str,
    fp_filter_level: str = "moderate",
    prioritize_by: str = "severity",
) -> Dict[str, Any]:
    """Analyze scan findings to detect false positives and prioritize issues.

    Applies the FalsePositiveFilter with a security-context-aware pipeline
    (1000+ FP patterns, security wrapper detection, docstring exclusion,
    test file detection, language-specific rules) and enriches each issue
    with FP likelihood, confidence score, and contextual flags.

    Args:
        scan_id: The UUID returned by a previous ``run_security_scan`` call.
        fp_filter_level: Filtering aggressiveness — "aggressive" (maximum
            noise reduction, may hide real issues), "moderate" (balanced,
            default), or "conservative" (minimal filtering, more noise).
        prioritize_by: Sort key for returned issues — "severity" (default),
            "scanner", or "file".

    Returns:
        A dictionary with analyzed_issues, summary, and critical_issues.
    """
    # ------------------------------------------------------------------
    # 1. Validate inputs
    # ------------------------------------------------------------------
    fp_filter_level = fp_filter_level.lower()
    if fp_filter_level not in _VALID_FP_LEVELS:
        return {
            "error": f"Invalid fp_filter_level '{fp_filter_level}'. "
                     f"Must be one of: {', '.join(sorted(_VALID_FP_LEVELS))}",
        }

    prioritize_by = prioritize_by.lower()
    if prioritize_by not in _VALID_PRIORITIZE:
        return {
            "error": f"Invalid prioritize_by '{prioritize_by}'. "
                     f"Must be one of: {', '.join(sorted(_VALID_PRIORITIZE))}",
        }

    # ------------------------------------------------------------------
    # 2. Load persisted scan
    # ------------------------------------------------------------------
    state = _get_state_manager()
    scan_result = state.get_scan(scan_id)
    if not scan_result:
        return {"error": f"Scan not found: {scan_id}"}

    raw_issues: List[Dict[str, Any]] = scan_result.get("issues", [])
    if not raw_issues:
        return {
            "analyzed_issues": [],
            "summary": {
                "total_issues": 0,
                "likely_false_positives": 0,
                "high_priority_issues": 0,
                "fp_reduction_rate": "0%",
            },
            "critical_issues": [],
        }

    project_path = scan_result.get("project_path", "")
    resolved_root = Path(project_path) if project_path else Path.cwd()

    # ------------------------------------------------------------------
    # 3. Gather security context via CodePatternAnalyzer (read-only)
    # ------------------------------------------------------------------
    analyzer = CodePatternAnalyzer()

    async def _analyze_repo():
        return await asyncio.to_thread(analyzer.analyze_repo, resolved_root)

    analysis = await _analyze_repo()

    test_dirs: set = analysis.security_context.test_directories
    has_validation: bool = analysis.security_context.has_input_validation
    security_patterns: set = analysis.security_context.security_patterns

    # ------------------------------------------------------------------
    # 4. Run FalsePositiveFilter
    # ------------------------------------------------------------------
    from supreme_max.core.fp_filter import FalsePositiveFilter

    fp_filter = FalsePositiveFilter(source_root=resolved_root)
    confidence_threshold = _FP_CONFIDENCE_THRESHOLDS[fp_filter_level]

    # Convert MCP issues → format expected by FP filter
    compat_findings = [_normalise_finding_for_fp_filter(i) for i in raw_issues]

    def _run_fp():
        return fp_filter.filter_findings(compat_findings)

    retained, likely_fps = await asyncio.to_thread(_run_fp)

    # Use CodePatternAnalyzer's security-context FP check for extra context
    def _run_ctx():
        ctx_map = {}
        for f in compat_findings:
            ctx = analyzer.get_fp_context(analysis, {
                "rule_id": f.get("rule_id", ""),
                "message": f.get("issue", ""),
                "file": f.get("file", ""),
            })
            ctx_map[id(f)] = ctx
        return ctx_map

    ctx_results = await asyncio.to_thread(_run_ctx)

    # ------------------------------------------------------------------
    # 5. Re-classify using the chosen confidence threshold
    # ------------------------------------------------------------------
    final_retained: List[Dict[str, Any]] = []
    final_fps: List[Dict[str, Any]] = []

    for compat, original in zip(compat_findings, raw_issues):
        fp_info = compat.get("fp_analysis", {})
        confidence = fp_info.get("confidence", 0.0)
        is_fp = fp_info.get("is_likely_fp", False)

        classified_as_fp = is_fp and confidence >= confidence_threshold

        # Also incorporate CodePatternAnalyzer context
        ctx = ctx_results.get(id(compat), {})
        ctx_reduction = ctx.get("confidence_reduction", 0)
        if ctx_reduction >= 50 and not classified_as_fp:
            if confidence_threshold <= 0.70:  # aggressive or moderate
                classified_as_fp = True
                fp_info["reason"] = fp_info.get("reason") or "security_context"
                fp_info["explanation"] = (
                    fp_info.get("explanation", "")
                    + "; " + "; ".join(ctx.get("reasons", []))
                ).strip("; ")
                fp_info["confidence"] = max(confidence, ctx_reduction / 100)

        # Build enriched issue entry
        context_flags = _issue_context_flags(
            original, test_dirs, has_validation, security_patterns,
        )

        enriched: Dict[str, Any] = {
            "issue": original,
            "likely_false_positive": classified_as_fp,
            "fp_reason": fp_info.get("reason", ""),
            "confidence": round(fp_info.get("confidence", 0.0), 2),
            "priority_score": round(_priority_score({
                **original,
                "fp_analysis": fp_info,
            }), 2),
            "context": context_flags,
        }

        if classified_as_fp:
            final_fps.append(enriched)
        else:
            final_retained.append(enriched)

    # ------------------------------------------------------------------
    # 6. Sort / prioritize
    # ------------------------------------------------------------------
    def _sort_key(entry: Dict[str, Any]):
        if prioritize_by == "severity":
            return -entry["priority_score"]
        elif prioritize_by == "scanner":
            return (entry["issue"].get("scanner", ""), -entry["priority_score"])
        else:  # file
            return (entry["issue"].get("file", ""), -entry["priority_score"])

    final_retained.sort(key=_sort_key)
    final_fps.sort(key=_sort_key)

    all_analyzed = final_retained + final_fps

    # ------------------------------------------------------------------
    # 7. Pick top-10 critical issues (highest priority, NOT false positives)
    # ------------------------------------------------------------------
    critical_issues = [
        entry for entry in final_retained
        if entry["issue"].get("severity", "").upper() in ("CRITICAL", "HIGH")
    ][:10]

    # ------------------------------------------------------------------
    # 8. Build summary
    # ------------------------------------------------------------------
    total = len(raw_issues)
    fp_count = len(final_fps)
    high_priority = len([
        e for e in final_retained
        if _SEVERITY_RANK.get(e["issue"].get("severity", "").upper(), 0) >= 4
    ])
    fp_rate = f"{fp_count / total * 100:.0f}%" if total else "0%"

    # Sanitize secrets before returning to LLM context
    sanitize_analyzed_issues(all_analyzed)
    sanitize_analyzed_issues(critical_issues)

    return {
        "analyzed_issues": all_analyzed,
        "summary": {
            "total_issues": total,
            "likely_false_positives": fp_count,
            "high_priority_issues": high_priority,
            "fp_reduction_rate": fp_rate,
        },
        "critical_issues": critical_issues,
    }
