"""Tests for the SAFE-MCP Pentest Engine."""

from __future__ import annotations

import pytest

from navil.anomaly_detector import BehavioralAnomalyDetector
from navil.pentest import SCENARIOS, PentestEngine, PentestResult
from navil.policy_engine import PolicyEngine


@pytest.fixture
def detector() -> BehavioralAnomalyDetector:
    return BehavioralAnomalyDetector()


@pytest.fixture
def policy_engine() -> PolicyEngine:
    return PolicyEngine()


@pytest.fixture
def engine(detector: BehavioralAnomalyDetector, policy_engine: PolicyEngine) -> PentestEngine:
    return PentestEngine(detector, policy_engine)


class TestRunAll:
    """Test running all scenarios together."""

    def test_run_all_returns_11_results(self, engine: PentestEngine) -> None:
        report = engine.run_all()
        assert report["status"] == "completed"
        assert report["total_scenarios"] == 11
        assert len(report["results"]) == 11

    def test_detection_rate_calculation(self, engine: PentestEngine) -> None:
        report = engine.run_all()
        total = report["total_scenarios"]
        passed = report["passed"]
        expected_rate = round((passed / total) * 100, 1) if total > 0 else 0.0
        assert report["detection_rate"] == expected_rate

    def test_passed_plus_failed_equals_total(self, engine: PentestEngine) -> None:
        report = engine.run_all()
        assert report["passed"] + report["failed"] + report["partial"] == report["total_scenarios"]

    def test_all_scenarios_have_verdicts(self, engine: PentestEngine) -> None:
        report = engine.run_all()
        for result in report["results"]:
            assert result["verdict"] in ("PASS", "FAIL", "PARTIAL")


class TestSingleScenario:
    """Test running individual scenarios."""

    def test_single_scenario_run(self, engine: PentestEngine) -> None:
        result = engine.run_scenario("reconnaissance")
        assert isinstance(result, PentestResult)
        assert result.scenario == "reconnaissance"
        assert result.verdict in ("PASS", "FAIL", "PARTIAL")

    def test_unknown_scenario_raises(self, engine: PentestEngine) -> None:
        with pytest.raises(ValueError, match="Unknown scenario"):
            engine.run_scenario("nonexistent_attack")


class TestReconnaissanceScenario:
    def test_detected(self, engine: PentestEngine) -> None:
        result = engine.run_scenario("reconnaissance")
        assert result.detected is True
        assert result.verdict == "PASS"
        assert len(result.alerts_fired) >= 1
        assert result.alerts_fired[0]["anomaly_type"] == "RECONNAISSANCE"


class TestPersistenceScenario:
    def test_detected(self, engine: PentestEngine) -> None:
        result = engine.run_scenario("persistence")
        assert result.detected is True
        assert result.verdict == "PASS"
        assert len(result.alerts_fired) >= 1
        assert result.alerts_fired[0]["anomaly_type"] == "PERSISTENCE"


class TestDefenseEvasionScenario:
    def test_detected(self, engine: PentestEngine) -> None:
        result = engine.run_scenario("defense_evasion")
        assert result.detected is True
        assert result.verdict == "PASS"
        assert len(result.alerts_fired) >= 1
        assert result.alerts_fired[0]["anomaly_type"] == "DEFENSE_EVASION"


class TestLateralMovementScenario:
    def test_detected(self, engine: PentestEngine) -> None:
        result = engine.run_scenario("lateral_movement")
        assert result.detected is True
        assert result.verdict == "PASS"
        assert len(result.alerts_fired) >= 1
        assert result.alerts_fired[0]["anomaly_type"] == "LATERAL_MOVEMENT"


class TestC2BeaconingScenario:
    def test_detected(self, engine: PentestEngine) -> None:
        result = engine.run_scenario("c2_beaconing")
        assert result.detected is True
        assert result.verdict == "PASS"
        assert len(result.alerts_fired) >= 1
        assert result.alerts_fired[0]["anomaly_type"] == "COMMAND_AND_CONTROL"


class TestSupplyChainScenario:
    def test_detected(self, engine: PentestEngine) -> None:
        result = engine.run_scenario("supply_chain")
        assert result.detected is True
        assert result.verdict == "PASS"
        assert len(result.alerts_fired) >= 1
        assert result.alerts_fired[0]["anomaly_type"] == "SUPPLY_CHAIN"


class TestRugPullScenario:
    def test_detected(self, engine: PentestEngine) -> None:
        result = engine.run_scenario("rug_pull")
        assert result.detected is True
        assert result.verdict == "PASS"
        assert len(result.alerts_fired) >= 1
        assert result.alerts_fired[0]["anomaly_type"] == "RUG_PULL"


class TestDataExfiltrationScenario:
    def test_detected(self, engine: PentestEngine) -> None:
        result = engine.run_scenario("data_exfiltration")
        assert result.detected is True
        assert result.verdict == "PASS"
        assert len(result.alerts_fired) >= 1
        assert result.alerts_fired[0]["anomaly_type"] == "DATA_EXFILTRATION"


class TestPrivilegeEscalationScenario:
    def test_detected(self, engine: PentestEngine) -> None:
        result = engine.run_scenario("privilege_escalation")
        assert result.detected is True
        assert result.verdict == "PASS"
        assert len(result.alerts_fired) >= 1
        assert result.alerts_fired[0]["anomaly_type"] == "PRIVILEGE_ESCALATION"


class TestRateSpikeScenario:
    def test_detected(self, engine: PentestEngine) -> None:
        result = engine.run_scenario("rate_spike")
        assert result.detected is True
        assert result.verdict == "PASS"
        assert len(result.alerts_fired) >= 1
        assert result.alerts_fired[0]["anomaly_type"] == "RATE_SPIKE"


class TestPolicyBypassScenario:
    def test_detected(self, engine: PentestEngine) -> None:
        result = engine.run_scenario("policy_bypass")
        assert result.detected is True
        assert result.verdict == "PASS"
        assert len(result.alerts_fired) >= 1


class TestResultSerialization:
    def test_to_dict(self, engine: PentestEngine) -> None:
        result = engine.run_scenario("reconnaissance")
        d = result.to_dict()
        assert isinstance(d, dict)
        assert "scenario" in d
        assert "detected" in d
        assert "verdict" in d
        assert "attack_steps" in d
        assert isinstance(d["attack_steps"], list)

    def test_run_all_serializable(self, engine: PentestEngine) -> None:
        """Ensure run_all output is JSON-serializable."""
        import json

        report = engine.run_all()
        # Should not raise
        json.dumps(report)


class TestScenarioRegistry:
    def test_all_scenarios_have_methods(self, engine: PentestEngine) -> None:
        """Every registered scenario must have a corresponding _attack_ method."""
        for name in SCENARIOS:
            assert hasattr(engine, f"_attack_{name}"), f"Missing _attack_{name} method"

    def test_scenario_count(self) -> None:
        assert len(SCENARIOS) == 11
