"""Tests for mcpaudit MCP Fingerprinting (fingerprint.py).

Covers: MCPFingerprint dataclass, framework detection (Python + TS),
transport detection, tool/resource extraction, directory scanning,
formatters, edge cases.
Minimum: 30 tests.
"""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from mcpaudit.fingerprint import (
    MCPFingerprint,
    FrameworkInfo,
    fingerprint_path,
    fingerprint_server,
    format_fingerprint_text,
    format_fingerprint_json,
    format_mcpfingerprint_text,
    format_mcpfingerprint_json,
    _detect_transport,
    _detect_capabilities,
    _detect_lang,
    _extract_tools,
    _extract_resources,
    _fw_name_to_mcpfp,
)


# ===================================================================
# MCPFingerprint dataclass
# ===================================================================

class TestMCPFingerprint:
    def test_default_values(self):
        fp = MCPFingerprint()
        assert fp.framework == "unknown"
        assert fp.language == "unknown"
        assert fp.transport == "unknown"
        assert fp.tool_count == 0
        assert fp.resource_count == 0
        assert fp.tools == []
        assert fp.resources == []

    def test_to_dict_keys(self):
        fp = MCPFingerprint(framework="fastmcp", language="python")
        d = fp.to_dict()
        assert d["framework"] == "fastmcp"
        assert d["language"] == "python"
        assert "tools" in d
        assert "resources" in d
        assert "transport" in d

    def test_to_dict_complete(self):
        fp = MCPFingerprint(
            framework="mcp-sdk",
            language="typescript",
            tools=["read_file", "write_file"],
            resources=["file://data"],
            transport="stdio",
            tool_count=2,
            resource_count=1,
        )
        d = fp.to_dict()
        assert d["tool_count"] == 2
        assert d["resource_count"] == 1
        assert len(d["tools"]) == 2


# ===================================================================
# Language detection
# ===================================================================

class TestLanguageDetection:
    def test_python_py(self, tmp_path: Path):
        assert _detect_lang(tmp_path / "server.py") == "python"

    def test_python_pyw(self, tmp_path: Path):
        assert _detect_lang(tmp_path / "server.pyw") == "python"

    def test_typescript_ts(self, tmp_path: Path):
        assert _detect_lang(tmp_path / "server.ts") == "typescript"

    def test_typescript_js(self, tmp_path: Path):
        assert _detect_lang(tmp_path / "server.js") == "typescript"

    def test_unknown_ext(self, tmp_path: Path):
        assert _detect_lang(tmp_path / "server.rb") == "unknown"


# ===================================================================
# Framework detection (via existing fingerprint_server)
# ===================================================================

class TestFrameworkDetection:
    def test_fastmcp_python(self):
        code = 'from fastmcp import FastMCP\napp = FastMCP("test")'
        info = fingerprint_server(code)
        assert info.name == "FastMCP"

    def test_mcp_sdk_python(self):
        code = "from mcp.server import Server\nserver = Server('test')"
        info = fingerprint_server(code)
        assert info.name == "mcp-python-sdk"

    def test_ts_sdk(self):
        code = 'import { McpServer } from "@modelcontextprotocol/sdk"'
        info = fingerprint_server(code)
        assert info.name == "@modelcontextprotocol/sdk"

    def test_custom_server(self):
        code = "print('hello world')"
        info = fingerprint_server(code)
        assert info.name == "custom"

    def test_framework_info_to_dict(self):
        info = fingerprint_server("from fastmcp import FastMCP\nFastMCP()")
        d = info.to_dict()
        assert "name" in d
        assert "confidence" in d


# ===================================================================
# Transport detection
# ===================================================================

class TestTransportDetection:
    def test_stdio(self):
        assert _detect_transport("stdio_server()") == "stdio"

    def test_sse(self):
        assert _detect_transport("SSEServerTransport()") == "sse"

    def test_http(self):
        assert _detect_transport("StreamableHTTP()") == "streamable-http"

    def test_unknown(self):
        assert _detect_transport("some_other_code()") == "unknown"

    def test_stdio_transport_ts(self):
        assert _detect_transport("new StdioServerTransport()") == "stdio"


# ===================================================================
# Tool and resource extraction
# ===================================================================

class TestToolExtraction:
    def test_python_decorator_tool(self):
        code = '@mcp.tool("read_file")\ndef read_file(): pass'
        tools = _extract_tools(code, "python")
        assert "read_file" in tools

    def test_python_tool_def(self):
        code = '@app.tool()\nasync def write_data(): pass'
        tools = _extract_tools(code, "python")
        assert "write_data" in tools

    def test_typescript_tool(self):
        code = 'server.tool("get_data", handler)'
        tools = _extract_tools(code, "typescript")
        assert "get_data" in tools

    def test_no_tools(self):
        tools = _extract_tools("def foo(): pass", "python")
        assert tools == []

    def test_python_resource(self):
        code = '@mcp.resource("file://data")\ndef get_data(): pass'
        resources = _extract_resources(code, "python")
        assert "file://data" in resources

    def test_typescript_resource(self):
        code = 'server.resource("config://settings", handler)'
        resources = _extract_resources(code, "typescript")
        assert "config://settings" in resources


# ===================================================================
# Capability detection
# ===================================================================

class TestCapabilities:
    def test_detects_tools_capability(self):
        caps = _detect_capabilities('@mcp.tool("test")')
        assert "tools" in caps

    def test_detects_resources_capability(self):
        caps = _detect_capabilities('@mcp.resource("test")')
        assert "resources" in caps

    def test_detects_logging(self):
        caps = _detect_capabilities("logger = logging.getLogger()")
        assert "logging" in caps

    def test_empty_code(self):
        caps = _detect_capabilities("")
        assert caps == []


# ===================================================================
# fingerprint_path (file-based)
# ===================================================================

class TestFingerprintPath:
    def test_nonexistent_path(self):
        fp = fingerprint_path("/nonexistent/path/server.py")
        assert len(fp.errors) > 0
        assert fp.framework == "unknown"

    def test_python_fastmcp_file(self, tmp_path: Path):
        server = tmp_path / "server.py"
        server.write_text(
            'from fastmcp import FastMCP\n'
            'app = FastMCP("test")\n'
            '@app.tool("greet")\n'
            'def greet(name: str): return f"Hello {name}"\n'
            'app.run(transport="stdio")\n',
            encoding="utf-8",
        )
        fp = fingerprint_path(server)
        assert fp.framework == "fastmcp"
        assert fp.language == "python"
        assert fp.transport == "stdio"
        assert fp.tool_count >= 1
        assert "greet" in fp.tools

    def test_typescript_mcp_sdk_file(self, tmp_path: Path):
        server = tmp_path / "server.ts"
        server.write_text(
            'import { McpServer } from "@modelcontextprotocol/sdk";\n'
            'const server = new McpServer({name: "test"});\n'
            'server.tool("fetch_data", async () => {});\n'
            'server.resource("config://app", async () => {});\n',
            encoding="utf-8",
        )
        fp = fingerprint_path(server)
        assert fp.framework == "mcp-sdk"
        assert fp.language == "typescript"
        assert "fetch_data" in fp.tools
        assert "config://app" in fp.resources

    def test_directory_scan(self, tmp_path: Path):
        (tmp_path / "server.py").write_text(
            'from fastmcp import FastMCP\n'
            'app = FastMCP("srv")\n'
            '@app.tool("tool_a")\ndef tool_a(): pass\n',
            encoding="utf-8",
        )
        (tmp_path / "helper.py").write_text(
            'def util(): pass\n',
            encoding="utf-8",
        )
        fp = fingerprint_path(tmp_path)
        assert fp.framework == "fastmcp"
        assert "tool_a" in fp.tools

    def test_empty_directory(self, tmp_path: Path):
        fp = fingerprint_path(tmp_path)
        assert fp.framework == "unknown"
        assert len(fp.errors) > 0


# ===================================================================
# Formatters
# ===================================================================

class TestFormatters:
    def test_text_format(self):
        fp = MCPFingerprint(
            framework="fastmcp",
            language="python",
            transport="stdio",
            tool_count=2,
            tools=["a", "b"],
            file_path="/test/server.py",
        )
        text = format_mcpfingerprint_text(fp)
        assert "fastmcp" in text
        assert "python" in text
        assert "stdio" in text

    def test_json_format(self):
        fp = MCPFingerprint(
            framework="mcp-sdk",
            language="typescript",
        )
        j = format_mcpfingerprint_json(fp)
        data = json.loads(j)
        assert data["framework"] == "mcp-sdk"
        assert data["language"] == "typescript"

    def test_framework_info_text_format(self):
        info = FrameworkInfo(name="FastMCP", confidence="HIGH")
        text = format_fingerprint_text(info)
        assert "FastMCP" in text
        assert "HIGH" in text

    def test_framework_info_json_format(self):
        info = FrameworkInfo(name="mcp-python-sdk", version_hint="1.2.0")
        j = format_fingerprint_json(info, filepath="/test.py")
        data = json.loads(j)
        assert data["name"] == "mcp-python-sdk"
        assert data["file"] == "/test.py"


# ===================================================================
# Helpers
# ===================================================================

class TestHelpers:
    def test_fw_name_mapping(self):
        assert _fw_name_to_mcpfp("FastMCP") == "fastmcp"
        assert _fw_name_to_mcpfp("mcp-python-sdk") == "mcp-sdk"
        assert _fw_name_to_mcpfp("@modelcontextprotocol/sdk") == "mcp-sdk"
        assert _fw_name_to_mcpfp("custom") == "custom"
        assert _fw_name_to_mcpfp("unknown") == "unknown"
