"""Tests for the MCP Ecosystem Analyzer module."""

from __future__ import annotations

import json

import pytest

from mcpaudit.ecosystem_analyzer import (
    ConnectionType,
    EcosystemAnalysis,
    EcosystemIssue,
    EcosystemMetrics,
    IssueSeverity,
    MCPServerConfig,
    ServerConnection,
    TrustBoundary,
    TrustZone,
    analyze_ecosystem,
    analyze_ecosystem_from_dicts,
    format_ecosystem_json,
    format_ecosystem_text,
    generate_ecosystem_html,
)


# ===========================================================================
# TrustZone
# ===========================================================================

class TestTrustZone:
    def test_from_string_internal(self):
        assert TrustZone.from_string("internal") == TrustZone.INTERNAL

    def test_from_string_external(self):
        assert TrustZone.from_string("external") == TrustZone.EXTERNAL

    def test_from_string_privileged(self):
        assert TrustZone.from_string("privileged") == TrustZone.PRIVILEGED

    def test_from_string_sandbox(self):
        assert TrustZone.from_string("sandbox") == TrustZone.SANDBOX

    def test_from_string_unknown(self):
        assert TrustZone.from_string("unknown") == TrustZone.INTERNAL

    def test_from_string_case_insensitive(self):
        assert TrustZone.from_string("EXTERNAL") == TrustZone.EXTERNAL

    def test_values(self):
        assert TrustZone.INTERNAL.value == "internal"
        assert TrustZone.EXTERNAL.value == "external"
        assert TrustZone.PRIVILEGED.value == "privileged"
        assert TrustZone.SANDBOX.value == "sandbox"


# ===========================================================================
# ConnectionType
# ===========================================================================

class TestConnectionType:
    def test_values(self):
        assert ConnectionType.DATA_FLOW.value == "data_flow"
        assert ConnectionType.CONTROL.value == "control"
        assert ConnectionType.DELEGATION.value == "delegation"
        assert ConnectionType.SHARED_RESOURCE.value == "shared_resource"


# ===========================================================================
# IssueSeverity
# ===========================================================================

class TestIssueSeverity:
    def test_weight(self):
        assert IssueSeverity.CRITICAL.weight == 10
        assert IssueSeverity.HIGH.weight == 5
        assert IssueSeverity.MEDIUM.weight == 2
        assert IssueSeverity.LOW.weight == 1
        assert IssueSeverity.INFO.weight == 0


# ===========================================================================
# MCPServerConfig
# ===========================================================================

class TestMCPServerConfig:
    def test_create(self):
        s = MCPServerConfig(name="test-server")
        assert s.name == "test-server"
        assert s.tool_count == 0
        assert s.connection_count == 0

    def test_tool_count(self):
        s = MCPServerConfig(name="test", tools=["a", "b", "c"])
        assert s.tool_count == 3

    def test_connection_count(self):
        s = MCPServerConfig(name="test", dependencies=["a", "b"])
        assert s.connection_count == 2

    def test_to_dict(self):
        s = MCPServerConfig(
            name="test", path="/srv", transport="sse",
            trust_zone=TrustZone.EXTERNAL,
            tools=["read", "write"],
        )
        d = s.to_dict()
        assert d["name"] == "test"
        assert d["trust_zone"] == "external"
        assert d["transport"] == "sse"
        assert len(d["tools"]) == 2

    def test_from_dict(self):
        d = {
            "name": "test",
            "trust_zone": "privileged",
            "tools": ["exec"],
            "dependencies": ["other"],
        }
        s = MCPServerConfig.from_dict(d)
        assert s.name == "test"
        assert s.trust_zone == TrustZone.PRIVILEGED
        assert s.tool_count == 1

    def test_from_dict_defaults(self):
        s = MCPServerConfig.from_dict({})
        assert s.name == ""
        assert s.trust_zone == TrustZone.INTERNAL
        assert s.transport == "stdio"


# ===========================================================================
# ServerConnection
# ===========================================================================

class TestServerConnection:
    def test_to_dict(self):
        c = ServerConnection(source="a", target="b")
        d = c.to_dict()
        assert d["source"] == "a"
        assert d["target"] == "b"
        assert d["connection_type"] == "data_flow"

    def test_bidirectional(self):
        c = ServerConnection(source="a", target="b", bidirectional=True)
        assert c.bidirectional


# ===========================================================================
# EcosystemIssue
# ===========================================================================

class TestEcosystemIssue:
    def test_to_dict(self):
        issue = EcosystemIssue(
            issue_id="ECO-001",
            title="Test issue",
            description="A test",
            severity=IssueSeverity.HIGH,
            affected_servers=["a", "b"],
        )
        d = issue.to_dict()
        assert d["issue_id"] == "ECO-001"
        assert d["severity"] == "HIGH"
        assert len(d["affected_servers"]) == 2


# ===========================================================================
# TrustBoundary
# ===========================================================================

class TestTrustBoundary:
    def test_risk_level_high(self):
        tb = TrustBoundary(
            zone_a=TrustZone.EXTERNAL,
            zone_b=TrustZone.PRIVILEGED,
        )
        assert tb.risk_level == "HIGH"

    def test_risk_level_high_sandbox_privileged(self):
        tb = TrustBoundary(
            zone_a=TrustZone.SANDBOX,
            zone_b=TrustZone.PRIVILEGED,
        )
        assert tb.risk_level == "HIGH"

    def test_risk_level_high_external_internal(self):
        tb = TrustBoundary(
            zone_a=TrustZone.EXTERNAL,
            zone_b=TrustZone.INTERNAL,
        )
        assert tb.risk_level == "HIGH"

    def test_risk_level_medium(self):
        tb = TrustBoundary(
            zone_a=TrustZone.INTERNAL,
            zone_b=TrustZone.SANDBOX,
        )
        assert tb.risk_level == "MEDIUM"

    def test_to_dict(self):
        tb = TrustBoundary(
            zone_a=TrustZone.INTERNAL,
            zone_b=TrustZone.EXTERNAL,
            crossing_connections=[ServerConnection("a", "b")],
        )
        d = tb.to_dict()
        assert d["zone_a"] == "internal"
        assert d["zone_b"] == "external"
        assert d["crossing_count"] == 1


# ===========================================================================
# EcosystemMetrics
# ===========================================================================

class TestEcosystemMetrics:
    def test_to_dict(self):
        m = EcosystemMetrics(
            total_servers=5,
            total_connections=10,
            health_score=85,
        )
        d = m.to_dict()
        assert d["total_servers"] == 5
        assert d["health_score"] == 85


# ===========================================================================
# EcosystemAnalysis
# ===========================================================================

class TestEcosystemAnalysis:
    def test_create_empty(self):
        a = EcosystemAnalysis()
        assert a.server_count == 0
        assert a.issue_count == 0
        assert a.timestamp != ""

    def test_properties(self):
        a = EcosystemAnalysis(
            issues=[
                EcosystemIssue("E1", "T1", "D1", IssueSeverity.CRITICAL, []),
                EcosystemIssue("E2", "T2", "D2", IssueSeverity.HIGH, []),
                EcosystemIssue("E3", "T3", "D3", IssueSeverity.LOW, []),
            ]
        )
        assert a.issue_count == 3
        assert len(a.critical_issues) == 1
        assert len(a.high_issues) == 1

    def test_get_server(self):
        a = EcosystemAnalysis(
            servers=[MCPServerConfig(name="s1"), MCPServerConfig(name="s2")]
        )
        assert a.get_server("s1") is not None
        assert a.get_server("s3") is None

    def test_to_dict(self):
        a = EcosystemAnalysis()
        d = a.to_dict()
        assert "servers" in d
        assert "connections" in d
        assert "metrics" in d


# ===========================================================================
# analyze_ecosystem
# ===========================================================================

class TestAnalyzeEcosystem:
    def test_empty_servers(self):
        result = analyze_ecosystem([])
        assert result.server_count == 0
        assert result.metrics.total_servers == 0

    def test_single_server_orphan(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="solo", tools=["read", "write"]),
        ])
        assert result.server_count == 1
        assert result.metrics.orphan_count == 1
        # Should detect orphan issue
        orphan_issues = [i for i in result.issues if "Orphan" in i.title]
        assert len(orphan_issues) == 1

    def test_connected_servers(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="a", dependencies=["b"]),
            MCPServerConfig(name="b"),
        ])
        assert result.server_count == 2
        assert len(result.connections) >= 1

    def test_shared_resource_connection(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="a", resources=["db"]),
            MCPServerConfig(name="b", resources=["db"]),
        ])
        shared_conns = [
            c for c in result.connections
            if c.connection_type == ConnectionType.SHARED_RESOURCE
        ]
        assert len(shared_conns) >= 1

    def test_trust_boundary_detected(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="ext", trust_zone=TrustZone.EXTERNAL,
                            dependencies=["int"]),
            MCPServerConfig(name="int", trust_zone=TrustZone.INTERNAL),
        ])
        assert len(result.trust_boundaries) >= 1

    def test_spof_detected(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="hub", tools=["exec"]),
            MCPServerConfig(name="a", dependencies=["hub"]),
            MCPServerConfig(name="b", dependencies=["hub"]),
        ])
        spof_issues = [i for i in result.issues if "point of failure" in i.title.lower()]
        assert len(spof_issues) >= 1

    def test_over_connected_detected(self):
        servers = [MCPServerConfig(name="hub")]
        for i in range(6):
            servers.append(MCPServerConfig(name=f"s{i}", dependencies=["hub"]))
        result = analyze_ecosystem(servers)
        oc_issues = [i for i in result.issues if "Over-connected" in i.title]
        assert len(oc_issues) >= 1

    def test_privileged_excessive_tools(self):
        result = analyze_ecosystem([
            MCPServerConfig(
                name="priv",
                trust_zone=TrustZone.PRIVILEGED,
                tools=[f"tool_{i}" for i in range(15)],
            ),
        ])
        priv_issues = [i for i in result.issues if "excessive tools" in i.title.lower()]
        assert len(priv_issues) >= 1

    def test_external_to_privileged(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="ext", trust_zone=TrustZone.EXTERNAL,
                            dependencies=["priv"]),
            MCPServerConfig(name="priv", trust_zone=TrustZone.PRIVILEGED),
        ])
        ext_priv = [i for i in result.issues if "External server" in i.title]
        assert len(ext_priv) >= 1

    def test_metrics_computed(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="a", tools=["t1", "t2"], transport="stdio",
                            trust_zone=TrustZone.INTERNAL),
            MCPServerConfig(name="b", tools=["t3"], transport="sse",
                            trust_zone=TrustZone.EXTERNAL,
                            dependencies=["a"]),
        ])
        m = result.metrics
        assert m.total_servers == 2
        assert m.total_tools == 3
        assert "internal" in m.zone_distribution
        assert "external" in m.zone_distribution
        assert "stdio" in m.transport_distribution
        assert m.health_score <= 100

    def test_health_score_decreases_with_issues(self):
        # Many issues should reduce health
        servers = [
            MCPServerConfig(name="ext", trust_zone=TrustZone.EXTERNAL,
                            dependencies=["priv"]),
            MCPServerConfig(name="priv", trust_zone=TrustZone.PRIVILEGED,
                            tools=[f"t{i}" for i in range(15)]),
        ]
        result = analyze_ecosystem(servers)
        assert result.metrics.health_score < 100

    def test_from_dicts(self):
        result = analyze_ecosystem_from_dicts([
            {"name": "a", "tools": ["read"]},
            {"name": "b", "dependencies": ["a"]},
        ])
        assert result.server_count == 2

    def test_from_mixed_input(self):
        result = analyze_ecosystem([
            {"name": "a", "tools": ["read"]},
            MCPServerConfig(name="b", dependencies=["a"]),
        ])
        assert result.server_count == 2

    def test_high_risk_boundary_issue(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="ext", trust_zone=TrustZone.EXTERNAL,
                            dependencies=["priv"]),
            MCPServerConfig(name="priv", trust_zone=TrustZone.PRIVILEGED),
        ])
        boundary_issues = [
            i for i in result.issues if "trust boundary" in i.title.lower()
        ]
        assert len(boundary_issues) >= 1

    def test_no_issues_healthy(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="a", trust_zone=TrustZone.INTERNAL,
                            dependencies=["b"], tools=["read"]),
            MCPServerConfig(name="b", trust_zone=TrustZone.INTERNAL,
                            tools=["write"]),
        ])
        assert result.metrics.health_score >= 80


# ===========================================================================
# HTML report
# ===========================================================================

class TestGenerateEcosystemHtml:
    def test_basic_html(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="server-1", tools=["read"]),
        ])
        html = generate_ecosystem_html(result)
        assert "<!DOCTYPE html>" in html
        assert "server-1" in html
        assert "MCP Ecosystem Analysis" in html

    def test_html_with_issues(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="ext", trust_zone=TrustZone.EXTERNAL,
                            dependencies=["priv"]),
            MCPServerConfig(name="priv", trust_zone=TrustZone.PRIVILEGED,
                            tools=[f"t{i}" for i in range(15)]),
        ])
        html = generate_ecosystem_html(result)
        assert "Issues" in html
        assert "CRITICAL" in html or "HIGH" in html

    def test_html_empty_ecosystem(self):
        result = analyze_ecosystem([])
        html = generate_ecosystem_html(result)
        assert "<!DOCTYPE html>" in html

    def test_html_trust_boundaries(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="a", trust_zone=TrustZone.INTERNAL,
                            dependencies=["b"]),
            MCPServerConfig(name="b", trust_zone=TrustZone.EXTERNAL),
        ])
        html = generate_ecosystem_html(result)
        assert "Trust Boundaries" in html


# ===========================================================================
# Text and JSON formatters
# ===========================================================================

class TestFormatters:
    def test_format_text(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="a", tools=["read"]),
        ])
        text = format_ecosystem_text(result)
        assert "MCP Ecosystem Analysis" in text
        assert "a" in text

    def test_format_json(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="a"),
        ])
        j = format_ecosystem_json(result)
        data = json.loads(j)
        assert "servers" in data
        assert "metrics" in data

    def test_format_text_with_issues(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="ext", trust_zone=TrustZone.EXTERNAL,
                            dependencies=["priv"]),
            MCPServerConfig(name="priv", trust_zone=TrustZone.PRIVILEGED),
        ])
        text = format_ecosystem_text(result)
        assert "ISSUES" in text

    def test_format_text_with_boundaries(self):
        result = analyze_ecosystem([
            MCPServerConfig(name="a", trust_zone=TrustZone.INTERNAL,
                            dependencies=["b"]),
            MCPServerConfig(name="b", trust_zone=TrustZone.EXTERNAL),
        ])
        text = format_ecosystem_text(result)
        assert "TRUST BOUNDARIES" in text
