"""
Comprehensive MCP Server Integration Tests

Tests the full MCP server as an LLM client would use it:
  1. Server startup & registration verification
  2. Tool execution (analyze_project, run_security_scan, get_scan_results,
     generate_report, analyze_findings)
  3. Resource reads (scans, config, scanners)
  4. Prompt retrieval
  5. Full LLM workflow simulation (scan → results → analyze → report)
  6. Security validation (path traversal, secrets redaction)
  7. MCP stdio protocol wire test

Run:
    python -m pytest tests/test_mcp_full_integration.py -v -o "addopts="
"""

from __future__ import annotations

import asyncio
import json
import os
import shutil
import tempfile
from pathlib import Path
from typing import Any, Dict
from unittest.mock import patch

import pytest

# ---------------------------------------------------------------------------
# Import MCP server components once at module level
# ---------------------------------------------------------------------------
from supreme_max.mcp_server._server import mcp
import supreme_max.mcp_server.tools as tools_mod
import supreme_max.mcp_server.resources as res_mod
import supreme_max.mcp_server.prompts  # noqa: F401 — triggers decorator registration


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _run(coro):
    """Run an async coroutine synchronously.

    Uses the same event-loop strategy across all tests so that
    decorator registrations (which happen at import time) persist.
    """
    loop = asyncio.new_event_loop()
    try:
        return loop.run_until_complete(coro)
    finally:
        loop.close()


def _call_tool_result(raw) -> dict:
    """Extract the structured dict from FastMCP.call_tool return value.

    ``call_tool`` returns ``(content_blocks, structured_content)``
    where ``structured_content`` is ``{"result": <actual_dict>}``.
    """
    if isinstance(raw, tuple):
        _, structured = raw
        if isinstance(structured, dict) and "result" in structured:
            return structured["result"]
        return structured
    if isinstance(raw, dict):
        return raw
    # Fallback: try to parse from text blocks
    if isinstance(raw, (list, tuple)):
        for block in raw:
            text = getattr(block, "text", None) or str(block)
            try:
                return json.loads(text)
            except (json.JSONDecodeError, TypeError):
                continue
    raise TypeError(f"Unexpected call_tool return type: {type(raw)}")


def _read_resource_text(raw) -> str:
    """Extract text from FastMCP.read_resource return value."""
    text = ""
    for part in raw:
        text += getattr(part, "content", None) or str(part)
    return text


@pytest.fixture(autouse=True)
def _isolate_state(tmp_path):
    """Redirect all state/report dirs to a temp dir for test isolation."""
    db_path = tmp_path / "scans.db"
    results_dir = tmp_path / "results"
    reports_dir = tmp_path / "reports"
    results_dir.mkdir()
    reports_dir.mkdir()

    from supreme_max.mcp_server.state import ScanStateManager

    mgr = ScanStateManager(db_path=db_path, results_dir=results_dir)

    # Inject test state manager into both modules
    tools_mod._state_manager = mgr
    res_mod._state_manager = mgr

    # Redirect reports dir
    original_reports = tools_mod._REPORTS_DIR
    tools_mod._REPORTS_DIR = reports_dir

    yield mgr

    # Restore
    tools_mod._state_manager = None
    res_mod._state_manager = None
    tools_mod._REPORTS_DIR = original_reports


def _seed_scan(mgr, **overrides) -> str:
    """Insert a fake scan into the state manager and return scan_id."""
    scan = {
        "project_path": "/tmp/test-project",
        "timestamp": "2026-02-13T10:00:00Z",
        "scanners_used": ["PythonScanner", "JavaScriptScanner"],
        "files_scanned": 30,
        "issues_found": 4,
        "scan_time": 2.5,
        "issues": [
            {
                "severity": "CRITICAL", "message": "SQL injection in query",
                "file": "app/db.py", "scanner": "PythonScanner",
                "rule_id": "B608", "line": 10,
            },
            {
                "severity": "HIGH", "message": "XSS vulnerability",
                "file": "app/views.js", "scanner": "JavaScriptScanner",
                "rule_id": "XSS01", "line": 22,
            },
            {
                "severity": "MEDIUM", "message": "Weak hash algorithm",
                "file": "app/auth.py", "scanner": "PythonScanner",
                "rule_id": "B303", "line": 5,
            },
            {
                "severity": "LOW", "message": "Debug print statement",
                "file": "app/utils.py", "scanner": "PythonScanner",
                "rule_id": "T201", "line": 15,
            },
        ],
    }
    scan.update(overrides)
    return mgr.store_scan(scan)


# ===================================================================
# 1. SERVER REGISTRATION TESTS
# ===================================================================

class TestServerRegistration:
    """Verify all tools, prompts, and resources are registered."""

    def test_five_tools_registered(self):
        async def check():
            tools = await mcp.list_tools()
            names = {t.name for t in tools}
            assert names == {
                "analyze_project",
                "run_security_scan",
                "get_scan_results",
                "generate_report",
                "analyze_findings",
            }
        _run(check())

    def test_four_prompts_registered(self):
        async def check():
            prompts = await mcp.list_prompts()
            names = {p.name for p in prompts}
            assert names == {
                "security_scan_workflow",
                "pr_security_check",
                "false_positive_analysis",
                "vulnerability_remediation",
            }
        _run(check())

    def test_static_resources_registered(self):
        async def check():
            resources = await mcp.list_resources()
            uris = {str(r.uri) for r in resources}
            assert "supreme2l://scans/latest" in uris
            assert "supreme2l://config" in uris
            assert "supreme2l://scanners" in uris
        _run(check())

    def test_resource_templates_registered(self):
        async def check():
            templates = await mcp.list_resource_templates()
            uris = {str(t.uriTemplate) for t in templates}
            assert "supreme2l://scans/{scan_id}" in uris
            assert "supreme2l://scans/{scan_id}/report" in uris
        _run(check())

    def test_tools_have_descriptions(self):
        async def check():
            tools = await mcp.list_tools()
            for tool in tools:
                assert tool.description, f"Tool {tool.name} missing description"
                assert len(tool.description) > 20
        _run(check())

    def test_tools_have_input_schemas(self):
        async def check():
            tools = await mcp.list_tools()
            for tool in tools:
                assert tool.inputSchema is not None, \
                    f"Tool {tool.name} missing input schema"
        _run(check())


# ===================================================================
# 2. TOOL EXECUTION TESTS — get_scan_results
# ===================================================================

class TestGetScanResults:
    """Test the get_scan_results tool via FastMCP.call_tool."""

    def test_no_filters(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            raw = await mcp.call_tool("get_scan_results", {"scan_id": scan_id})
            result = _call_tool_result(raw)
            assert result["scan_id"] == scan_id
            assert len(result["issues"]) == 4
        _run(check())

    def test_filter_by_severity(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            raw = await mcp.call_tool("get_scan_results", {
                "scan_id": scan_id,
                "severity": "critical,high",
            })
            result = _call_tool_result(raw)
            assert len(result["issues"]) == 2
            sevs = {i["severity"] for i in result["issues"]}
            assert sevs <= {"CRITICAL", "HIGH"}
        _run(check())

    def test_filter_by_file_pattern(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            raw = await mcp.call_tool("get_scan_results", {
                "scan_id": scan_id,
                "file_pattern": r"\.js$",
            })
            result = _call_tool_result(raw)
            assert len(result["issues"]) == 1
            assert result["issues"][0]["file"].endswith(".js")
        _run(check())

    def test_filter_by_scanner(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            raw = await mcp.call_tool("get_scan_results", {
                "scan_id": scan_id,
                "scanner": "PythonScanner",
            })
            result = _call_tool_result(raw)
            assert len(result["issues"]) == 3
        _run(check())

    def test_limit(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            raw = await mcp.call_tool("get_scan_results", {
                "scan_id": scan_id,
                "limit": 2,
            })
            result = _call_tool_result(raw)
            assert len(result["issues"]) == 2
        _run(check())

    def test_latest_alias(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            raw = await mcp.call_tool("get_scan_results", {
                "scan_id": "latest",
            })
            result = _call_tool_result(raw)
            assert result["scan_id"] == scan_id
        _run(check())

    def test_not_found(self, _isolate_state):
        async def check():
            raw = await mcp.call_tool("get_scan_results", {
                "scan_id": "nonexistent",
            })
            result = _call_tool_result(raw)
            assert "error" in result
        _run(check())

    def test_combined_filters(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            raw = await mcp.call_tool("get_scan_results", {
                "scan_id": scan_id,
                "filters": {"severity": ["MEDIUM"], "scanner": "JavaScriptScanner"},
            })
            result = _call_tool_result(raw)
            # Only 1 MEDIUM issue from JavaScriptScanner (the CSRF one is not in our seed)
            # Our seed has no MEDIUM from JS, so 0 results
            for issue in result["issues"]:
                assert issue["severity"] == "MEDIUM"
                assert issue["scanner"] == "JavaScriptScanner"
        _run(check())


# ===================================================================
# 3. TOOL EXECUTION TESTS — generate_report
# ===================================================================

class TestGenerateReport:
    """Test the generate_report tool via FastMCP.call_tool."""

    def test_generates_report(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            raw = await mcp.call_tool("generate_report", {"scan_id": scan_id})
            result = _call_tool_result(raw)
            assert result["format"] == "json"
            assert result["message"] == "Report generated successfully"
            assert Path(result["report_path"]).is_file()
        _run(check())

    def test_report_content_structure(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            raw = await mcp.call_tool("generate_report", {"scan_id": scan_id})
            result = _call_tool_result(raw)
            report = json.loads(
                Path(result["report_path"]).read_text(encoding="utf-8")
            )
            assert "metadata" in report
            assert "summary" in report
            assert "aggregations" in report
            assert "security_score" in report
            assert "risk_level" in report
            assert "issues" in report
            assert report["metadata"]["scan_id"] == scan_id
        _run(check())

    def test_security_score_calculation(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            raw = await mcp.call_tool("generate_report", {"scan_id": scan_id})
            result = _call_tool_result(raw)
            report = json.loads(
                Path(result["report_path"]).read_text(encoding="utf-8")
            )
            # CRITICAL=-30, HIGH=-15, MEDIUM=-5, LOW=-1 → 100-30-15-5-1 = 49
            assert report["security_score"] == 49
            assert report["risk_level"] == "CONCERNING"
        _run(check())

    def test_aggregations(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            raw = await mcp.call_tool("generate_report", {"scan_id": scan_id})
            result = _call_tool_result(raw)
            report = json.loads(
                Path(result["report_path"]).read_text(encoding="utf-8")
            )
            agg = report["aggregations"]
            assert agg["by_severity"]["CRITICAL"] == 1
            assert agg["by_severity"]["HIGH"] == 1
            assert agg["by_severity"]["MEDIUM"] == 1
            assert agg["by_severity"]["LOW"] == 1
            assert agg["by_scanner"]["PythonScanner"] == 3
            assert agg["by_scanner"]["JavaScriptScanner"] == 1
        _run(check())

    def test_report_not_found(self, _isolate_state):
        async def check():
            raw = await mcp.call_tool("generate_report", {"scan_id": "nonexistent"})
            result = _call_tool_result(raw)
            assert "error" in result
        _run(check())

    def test_report_updates_db_metadata(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            raw = await mcp.call_tool("generate_report", {"scan_id": scan_id})
            result = _call_tool_result(raw)
            meta = mgr.get_scan_metadata(scan_id)
            assert meta["report_path"] == result["report_path"]
        _run(check())

    def test_report_with_latest_alias(self, _isolate_state):
        mgr = _isolate_state
        _seed_scan(mgr)

        async def check():
            raw = await mcp.call_tool("generate_report", {"scan_id": "latest"})
            result = _call_tool_result(raw)
            assert "error" not in result
            assert result["format"] == "json"
        _run(check())


# ===================================================================
# 4. TOOL EXECUTION TESTS — analyze_project
# ===================================================================

class TestAnalyzeProject:
    """Test the analyze_project tool."""

    def test_analyze_python_project(self, _isolate_state, tmp_path):
        (tmp_path / "main.py").write_text("print('hello')\n")
        (tmp_path / "utils.py").write_text("import os\n")

        async def check():
            raw = await mcp.call_tool("analyze_project", {
                "project_path": str(tmp_path),
            })
            result = _call_tool_result(raw)
            assert "project_summary" in result
            assert "recommended_scanners" in result
            assert "security_context" in result
            assert "scan_time_estimate" in result
            assert result["project_summary"]["total_files"] >= 1
        _run(check())

    def test_analyze_multi_language_project(self, _isolate_state, tmp_path):
        (tmp_path / "app.py").write_text("x = 1\n")
        (tmp_path / "app.js").write_text("const x = 1;\n")
        (tmp_path / "style.css").write_text("body { color: red; }\n")

        async def check():
            raw = await mcp.call_tool("analyze_project", {
                "project_path": str(tmp_path),
            })
            result = _call_tool_result(raw)
            assert result["project_summary"]["total_files"] >= 2
            assert isinstance(result["project_summary"]["languages"], dict)
        _run(check())

    def test_analyze_nonexistent_path(self, _isolate_state):
        async def check():
            raw = await mcp.call_tool("analyze_project", {
                "project_path": "/nonexistent/path/12345",
            })
            result = _call_tool_result(raw)
            assert "error" in result
        _run(check())

    def test_analyze_returns_security_context(self, _isolate_state, tmp_path):
        (tmp_path / "app.py").write_text("import django\n")

        async def check():
            raw = await mcp.call_tool("analyze_project", {
                "project_path": str(tmp_path),
            })
            result = _call_tool_result(raw)
            sc = result["security_context"]
            assert "uses_orm" in sc
            assert "has_input_validation" in sc
            assert "has_ci_config" in sc
            assert "has_ai_patterns" in sc
            assert "has_mcp_config" in sc
        _run(check())


# ===================================================================
# 5. TOOL EXECUTION TESTS — run_security_scan
# ===================================================================

class TestRunSecurityScan:
    """Test the run_security_scan tool."""

    def test_scan_directory(self, _isolate_state, tmp_path):
        (tmp_path / "app.py").write_text(
            'import os\npassword = "secret123"\n'
        )

        async def check():
            raw = await mcp.call_tool("run_security_scan", {
                "project_path": str(tmp_path),
                "mode": "full",
            })
            result = _call_tool_result(raw)
            # Either has scan results or a message about no files
            assert "scan_id" in result or "message" in result
            if "scan_id" in result and result["scan_id"] is not None:
                assert "summary" in result
                assert "issues" in result
        _run(check())

    def test_scan_invalid_mode(self, _isolate_state, tmp_path):
        async def check():
            raw = await mcp.call_tool("run_security_scan", {
                "project_path": str(tmp_path),
                "mode": "invalid_mode",
            })
            result = _call_tool_result(raw)
            assert "error" in result
            assert "invalid" in result["error"].lower()
        _run(check())

    def test_scan_invalid_severity_filter(self, _isolate_state, tmp_path):
        async def check():
            raw = await mcp.call_tool("run_security_scan", {
                "project_path": str(tmp_path),
                "severity_filter": "invalid",
            })
            result = _call_tool_result(raw)
            assert "error" in result
        _run(check())

    def test_scan_nonexistent_path(self, _isolate_state):
        async def check():
            raw = await mcp.call_tool("run_security_scan", {
                "project_path": "/nonexistent/path/xyz",
            })
            result = _call_tool_result(raw)
            assert "error" in result
        _run(check())

    def test_scan_quick_mode(self, _isolate_state, tmp_path):
        (tmp_path / "main.py").write_text("x = 1\n")

        async def check():
            raw = await mcp.call_tool("run_security_scan", {
                "project_path": str(tmp_path),
                "mode": "quick",
            })
            result = _call_tool_result(raw)
            # Should succeed (even with 0 results)
            assert "error" not in result
        _run(check())


# ===================================================================
# 6. RESOURCE READ TESTS
# ===================================================================

class TestResources:
    """Test MCP resources via FastMCP.read_resource."""

    def test_read_config(self, _isolate_state):
        async def check():
            raw = await mcp.read_resource("supreme2l://config")
            text = _read_resource_text(raw)
            assert len(text) > 0
            import yaml
            data = yaml.safe_load(text)
            assert isinstance(data, dict)
        _run(check())

    def test_read_scanners(self, _isolate_state):
        async def check():
            raw = await mcp.read_resource("supreme2l://scanners")
            text = _read_resource_text(raw)
            scanners = json.loads(text)
            assert isinstance(scanners, list)
            assert len(scanners) > 0
            for s in scanners[:3]:
                assert "name" in s
                assert "tool" in s
                assert "extensions" in s
                assert "available" in s
        _run(check())

    def test_read_latest_no_scans(self, _isolate_state):
        async def check():
            raw = await mcp.read_resource("supreme2l://scans/latest")
            text = _read_resource_text(raw)
            data = json.loads(text)
            assert "error" in data
        _run(check())

    def test_read_latest_with_scan(self, _isolate_state):
        mgr = _isolate_state
        _seed_scan(mgr)

        async def check():
            raw = await mcp.read_resource("supreme2l://scans/latest")
            text = _read_resource_text(raw)
            data = json.loads(text)
            assert "project_path" in data
            assert data["project_path"] == "/tmp/test-project"
        _run(check())

    def test_read_scan_by_id(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            raw = await mcp.read_resource(f"supreme2l://scans/{scan_id}")
            text = _read_resource_text(raw)
            data = json.loads(text)
            assert "issues" in data
            assert len(data["issues"]) == 4
        _run(check())

    def test_read_report_after_generation(self, _isolate_state):
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def check():
            # First generate the report
            raw = await mcp.call_tool("generate_report", {"scan_id": scan_id})
            _call_tool_result(raw)  # ensure no error

            # Then read the report via resource
            raw = await mcp.read_resource(
                f"supreme2l://scans/{scan_id}/report"
            )
            text = _read_resource_text(raw)
            report = json.loads(text)
            assert "security_score" in report
            assert "issues" in report
        _run(check())

    def test_scanners_have_expected_count(self, _isolate_state):
        """Verify a substantial number of scanners are registered."""
        async def check():
            raw = await mcp.read_resource("supreme2l://scanners")
            text = _read_resource_text(raw)
            scanners = json.loads(text)
            # The project claims 74+ analyzers
            assert len(scanners) > 10
        _run(check())


# ===================================================================
# 7. PROMPT RETRIEVAL TESTS
# ===================================================================

class TestPrompts:
    """Test MCP prompts via FastMCP.get_prompt."""

    def test_security_scan_workflow_content(self, _isolate_state):
        async def check():
            result = await mcp.get_prompt("security_scan_workflow")
            assert result.messages is not None
            text = result.messages[0].content.text
            assert "analyze_project" in text
            assert "run_security_scan" in text
            assert "analyze_findings" in text
            assert "generate_report" in text
        _run(check())

    def test_pr_security_check_content(self, _isolate_state):
        async def check():
            result = await mcp.get_prompt("pr_security_check")
            text = result.messages[0].content.text
            assert "quick" in text.lower()
            assert "CRITICAL" in text
        _run(check())

    def test_false_positive_analysis_content(self, _isolate_state):
        async def check():
            result = await mcp.get_prompt("false_positive_analysis")
            text = result.messages[0].content.text
            assert "confidence" in text
            assert "likely_false_positive" in text
        _run(check())

    def test_vulnerability_remediation_content(self, _isolate_state):
        async def check():
            result = await mcp.get_prompt("vulnerability_remediation")
            text = result.messages[0].content.text
            assert "SQL Injection" in text
            assert "XSS" in text
            assert "Hardcoded Secrets" in text
        _run(check())

    def test_all_prompts_return_non_empty_text(self, _isolate_state):
        async def check():
            prompt_names = [
                "security_scan_workflow",
                "pr_security_check",
                "false_positive_analysis",
                "vulnerability_remediation",
            ]
            for name in prompt_names:
                result = await mcp.get_prompt(name)
                assert len(result.messages) > 0
                text = result.messages[0].content.text
                assert len(text) > 100, f"Prompt {name} too short: {len(text)} chars"
        _run(check())


# ===================================================================
# 8. SECURITY VALIDATION TESTS
# ===================================================================

class TestSecurity:
    """Test security validation in MCP tools."""

    def test_path_traversal_blocked(self, _isolate_state):
        from supreme_max.mcp_server.security import (
            safe_validate_project_path, SecurityError, set_workspace_root,
        )
        import supreme_max.mcp_server.security as sec_mod
        saved = sec_mod._workspace_root

        set_workspace_root(Path("/safe/workspace"))
        try:
            with pytest.raises(SecurityError, match="traversal"):
                safe_validate_project_path("../../etc/passwd")
        finally:
            sec_mod._workspace_root = saved

    def test_null_byte_blocked(self, _isolate_state):
        from supreme_max.mcp_server.security import safe_validate_project_path, SecurityError

        with pytest.raises(SecurityError, match="null"):
            safe_validate_project_path("/tmp/test\x00evil")

    def test_long_path_blocked(self, _isolate_state):
        from supreme_max.mcp_server.security import safe_validate_project_path, SecurityError

        with pytest.raises(SecurityError, match="maximum length"):
            safe_validate_project_path("a" * 5000)

    def test_secrets_redaction_aws(self, _isolate_state):
        from supreme_max.mcp_server.security import sanitize_scan_results

        issues = [{
            "message": "Found AWS key AKIAIOSFODNN7EXAMPLE",
            "code_snippet": "key = 'AKIAIOSFODNN7EXAMPLE'",
            "rule_id": "hardcoded-secret",
            "severity": "CRITICAL",
        }]
        sanitize_scan_results(issues)
        assert "AKIAIOSFODNN7EXAMPLE" not in issues[0]["message"]
        assert issues[0]["code_snippet"] == "[REDACTED]"

    def test_secrets_redaction_github_token(self, _isolate_state):
        from supreme_max.mcp_server.security import sanitize_scan_results

        token = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij"
        issues = [{
            "message": f"Token: {token}",
            "code_snippet": f"token = '{token}'",
            "rule_id": "info",
            "severity": "HIGH",
        }]
        sanitize_scan_results(issues)
        assert token not in issues[0]["message"]
        assert token not in issues[0]["code_snippet"]

    def test_secrets_redaction_openai_key(self, _isolate_state):
        from supreme_max.mcp_server.security import sanitize_scan_results

        key = "sk-" + "a" * 48
        issues = [{
            "message": f"OpenAI key: {key}",
            "code_snippet": f"api_key = '{key}'",
            "rule_id": "info",
            "severity": "HIGH",
        }]
        sanitize_scan_results(issues)
        assert key not in issues[0]["message"]
        assert key not in issues[0]["code_snippet"]

    def test_analyzed_issues_sanitization(self, _isolate_state):
        from supreme_max.mcp_server.security import sanitize_analyzed_issues

        token = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij"
        analyzed = [{
            "issue": {
                "message": f"Found {token}",
                "code_snippet": f"t = '{token}'",
                "rule_id": "info",
                "severity": "HIGH",
            },
            "likely_false_positive": False,
        }]
        sanitize_analyzed_issues(analyzed)
        assert token not in analyzed[0]["issue"]["message"]

    def test_analyze_project_blocks_traversal(self, _isolate_state):
        """Verify analyze_project rejects path traversal via MCP tool call."""
        import supreme_max.mcp_server.security as sec_mod

        saved = sec_mod._workspace_root
        sec_mod._workspace_root = Path(tempfile.gettempdir()).resolve()
        try:
            async def check():
                raw = await mcp.call_tool("analyze_project", {
                    "project_path": "../../etc/passwd",
                })
                result = _call_tool_result(raw)
                assert "error" in result
            _run(check())
        finally:
            sec_mod._workspace_root = saved


# ===================================================================
# 9. CONFIG MODULE TESTS
# ===================================================================

class TestConfig:
    """Test MCP configuration management."""

    def test_default_config_values(self, _isolate_state, tmp_path):
        from supreme_max.mcp_server.config import MCPConfigManager

        config_path = tmp_path / "test_mcp_config.yml"
        mgr = MCPConfigManager(config_path=config_path)
        assert mgr.config.max_concurrent_scans == 1
        assert mgr.config.log_level == "INFO"
        assert mgr.config.scan_timeout == 3600
        assert config_path.is_file()

    def test_config_validation_clamps(self, _isolate_state):
        from supreme_max.mcp_server.config import MCPServerConfig, validate_config

        config = MCPServerConfig(
            max_concurrent_scans=999,
            scan_timeout=10,
            log_level="INVALID",
            rate_limit_requests=5,
        )
        validated = validate_config(config)
        assert validated.max_concurrent_scans == 10  # clamped to max
        assert validated.scan_timeout == 60            # clamped to min
        assert validated.log_level == "INFO"           # fallback
        assert validated.rate_limit_requests == 10     # clamped to min

    def test_config_from_yaml(self, _isolate_state, tmp_path):
        import yaml
        from supreme_max.mcp_server.config import MCPConfigManager

        config_path = tmp_path / "cfg.yml"
        config_path.write_text(yaml.dump({
            "log_level": "DEBUG",
            "max_concurrent_scans": 3,
        }))
        mgr = MCPConfigManager(config_path=config_path)
        assert mgr.config.log_level == "DEBUG"
        assert mgr.config.max_concurrent_scans == 3

    def test_config_reload(self, _isolate_state, tmp_path):
        import yaml
        from supreme_max.mcp_server.config import MCPConfigManager

        config_path = tmp_path / "cfg.yml"
        config_path.write_text(yaml.dump({"log_level": "INFO"}))
        mgr = MCPConfigManager(config_path=config_path)
        assert mgr.config.log_level == "INFO"

        config_path.write_text(yaml.dump({"log_level": "WARNING"}))
        mgr.reload()
        assert mgr.config.log_level == "WARNING"

    def test_config_save(self, _isolate_state, tmp_path):
        import yaml
        from supreme_max.mcp_server.config import MCPConfigManager, MCPServerConfig

        config_path = tmp_path / "cfg.yml"
        mgr = MCPConfigManager(config_path=config_path)
        custom = MCPServerConfig(log_level="ERROR", max_concurrent_scans=5)
        mgr.save_config(custom)

        data = yaml.safe_load(config_path.read_text())
        assert data["log_level"] == "ERROR"
        assert data["max_concurrent_scans"] == 5


# ===================================================================
# 10. FULL LLM WORKFLOW SIMULATION
# ===================================================================

class TestLLMWorkflow:
    """Simulate complete LLM interaction flows using only MCP tools,
    resources, and prompts — exactly as an LLM client would."""

    def test_complete_scan_workflow(self, _isolate_state, tmp_path):
        """
        Simulates the full security_scan_workflow prompt:
        1. Read prompt for guidance
        2. Analyze project structure
        3. Read available scanners
        4. Run security scan
        5. Get filtered scan results
        6. Generate report
        7. Read report via resource URI
        """
        (tmp_path / "app.py").write_text(
            'password = "admin123"\nquery = f"SELECT * FROM users WHERE name={name}"\n'
        )
        (tmp_path / "utils.py").write_text("import hashlib\nprint('debug')\n")

        mgr = _isolate_state

        async def workflow():
            # Step 1: Read the workflow prompt
            prompt = await mcp.get_prompt("security_scan_workflow")
            text = prompt.messages[0].content.text
            assert "analyze_project" in text

            # Step 2: Analyze the project
            raw = await mcp.call_tool("analyze_project", {
                "project_path": str(tmp_path),
            })
            analysis = _call_tool_result(raw)
            assert "error" not in analysis
            assert analysis["project_summary"]["total_files"] >= 1

            # Step 3: Read available scanners
            scanners_raw = await mcp.read_resource("supreme2l://scanners")
            scanners_text = _read_resource_text(scanners_raw)
            scanners_list = json.loads(scanners_text)
            assert len(scanners_list) > 0

            # Step 4: Run security scan
            raw = await mcp.call_tool("run_security_scan", {
                "project_path": str(tmp_path),
                "mode": "full",
            })
            scan_result = _call_tool_result(raw)
            assert "error" not in scan_result
            scan_id = scan_result.get("scan_id")

            # If no scannable files found, seed a scan for remaining tests
            if scan_id is None:
                scan_id = _seed_scan(mgr)

            # Step 5: Filter for critical issues
            raw = await mcp.call_tool("get_scan_results", {
                "scan_id": scan_id,
                "severity": "critical,high",
            })
            filtered = _call_tool_result(raw)
            assert "issues" in filtered

            # Step 6: Generate report
            raw = await mcp.call_tool("generate_report", {"scan_id": scan_id})
            report_result = _call_tool_result(raw)
            assert report_result["format"] == "json"

            # Step 7: Read scan data via resource
            scan_raw = await mcp.read_resource("supreme2l://scans/latest")
            scan_text = _read_resource_text(scan_raw)
            scan_data = json.loads(scan_text)
            assert "issues" in scan_data or "project_path" in scan_data

        _run(workflow())

    def test_pr_review_workflow(self, _isolate_state):
        """Simulate PR security check workflow using seeded data."""
        mgr = _isolate_state
        scan_id = _seed_scan(mgr)

        async def workflow():
            # Step 1: Read PR check prompt
            prompt = await mcp.get_prompt("pr_security_check")
            text = prompt.messages[0].content.text
            assert "quick" in text.lower()

            # Step 2: Get only critical/high issues (PR check focuses on these)
            raw = await mcp.call_tool("get_scan_results", {
                "scan_id": scan_id,
                "severity": "critical,high",
            })
            results = _call_tool_result(raw)
            assert len(results["issues"]) == 2  # 1 CRITICAL + 1 HIGH

            # Step 3: Generate report
            raw = await mcp.call_tool("generate_report", {"scan_id": scan_id})
            report = _call_tool_result(raw)
            assert report["format"] == "json"

            # Step 4: Read the report via resource for the PR comment
            report_raw = await mcp.read_resource(
                f"supreme2l://scans/{scan_id}/report"
            )
            report_text = _read_resource_text(report_raw)
            report_data = json.loads(report_text)
            assert report_data["security_score"] == 49

        _run(workflow())

    def test_config_read_workflow(self, _isolate_state):
        """LLM reads config and scanners to understand available tools."""
        async def workflow():
            # Read config
            config_raw = await mcp.read_resource("supreme2l://config")
            config_text = _read_resource_text(config_raw)
            assert len(config_text) > 0

            # Read scanners
            scanners_raw = await mcp.read_resource("supreme2l://scanners")
            scanners_text = _read_resource_text(scanners_raw)
            scanners = json.loads(scanners_text)

            # Verify scanner entries have expected structure
            for s in scanners[:5]:
                assert "name" in s
                assert "tool" in s
                assert "extensions" in s
                assert "available" in s  # bool: is the tool installed?

        _run(workflow())


# ===================================================================
# 11. MCP STDIO PROTOCOL WIRE TEST
# ===================================================================

class TestStdioProtocol:
    """Test that the MCP server can start and respond to JSON-RPC
    messages over stdio — the actual transport an LLM client uses."""

    def test_server_responds_to_initialize(self, _isolate_state):
        """Send an MCP initialize request via stdin and verify response."""
        import subprocess
        import sys

        init_request = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "initialize",
            "params": {
                "protocolVersion": "2024-11-05",
                "capabilities": {},
                "clientInfo": {
                    "name": "test-client",
                    "version": "1.0.0",
                },
            },
        }

        init_json = json.dumps(init_request)

        proc = subprocess.Popen(
            [sys.executable, "-m", "supreme2l.mcp_server"],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            cwd=str(Path(__file__).parent.parent),
        )

        try:
            stdout, stderr = proc.communicate(
                input=init_json.encode("utf-8"),
                timeout=30,
            )

            output = stdout.decode("utf-8", errors="replace")

            # Server should produce some output on stdout (the JSON-RPC response)
            assert len(output) > 0, (
                f"Server produced no stdout. "
                f"stderr: {stderr.decode('utf-8', errors='replace')[:500]}"
            )

            # Try to parse the JSON-RPC response
            for line in output.strip().split("\n"):
                line = line.strip()
                if not line:
                    continue
                try:
                    response = json.loads(line)
                    if "result" in response:
                        # Should contain serverInfo with the server name
                        r = response["result"]
                        assert "serverInfo" in r or "capabilities" in r
                        break
                except json.JSONDecodeError:
                    continue

        except subprocess.TimeoutExpired:
            proc.kill()
            proc.communicate()
            pytest.skip("Server did not respond within timeout")
        finally:
            if proc.poll() is None:
                proc.kill()
                proc.communicate()

    def test_server_advertises_tools_count(self, _isolate_state):
        """Verify server capabilities include tools."""
        import subprocess
        import sys

        init_request = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "initialize",
            "params": {
                "protocolVersion": "2024-11-05",
                "capabilities": {},
                "clientInfo": {"name": "test", "version": "1.0"},
            },
        }

        proc = subprocess.Popen(
            [sys.executable, "-m", "supreme2l.mcp_server"],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            cwd=str(Path(__file__).parent.parent),
        )

        try:
            stdout, _stderr = proc.communicate(
                input=json.dumps(init_request).encode(),
                timeout=30,
            )
            output = stdout.decode("utf-8", errors="replace")

            for line in output.strip().split("\n"):
                line = line.strip()
                if not line:
                    continue
                try:
                    response = json.loads(line)
                    if "result" in response:
                        caps = response["result"].get("capabilities", {})
                        # Server should advertise tool support
                        assert "tools" in caps or "resources" in caps or "prompts" in caps
                        break
                except json.JSONDecodeError:
                    continue
        except subprocess.TimeoutExpired:
            proc.kill()
            proc.communicate()
            pytest.skip("Server did not respond within timeout")
        finally:
            if proc.poll() is None:
                proc.kill()
                proc.communicate()
