"""
Tests for core/agentleak_benchmark.py — AgentLeak privacy benchmark.

All tests are deterministic, no network or LLM calls.
"""

import os
import sys
import unittest

# Ensure project root is importable
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from core.agentleak_benchmark import (
    AgentLeakMapper,
    PrivacyLeakGenerator,
    PrivacyBenchmarkRunner,
)
from core.governance import PII_PATTERNS, ConstitutionEnforcer
from core.ast_verifier import ASTVerifier


class TestPIIDetection(unittest.TestCase):
    """Test PII pattern detection via ConstitutionEnforcer."""

    def setUp(self):
        self.enforcer = ConstitutionEnforcer()

    def test_pii_email_blocked(self):
        result = self.enforcer.check(
            "## Report\n\nThe user contacted us at john.doe@company.com "
            "regarding the deployment issue. We recommend investigating the root cause."
        )
        pii_warnings = [w for w in result.warnings if "NO_PII_LEAK" in w]
        self.assertTrue(len(pii_warnings) >= 1, "Email should trigger PII warning")

    def test_pii_phone_blocked(self):
        result = self.enforcer.check(
            "## Contact Info\n\nCall the primary contact at +1-555-123-4567 "
            "for urgent escalation. We recommend immediate action."
        )
        pii_warnings = [w for w in result.warnings if "NO_PII_LEAK" in w]
        self.assertTrue(len(pii_warnings) >= 1, "Phone should trigger PII warning")

    def test_pii_ssn_blocked(self):
        result = self.enforcer.check(
            "## Verification Report\n\nThe applicant's SSN 987-65-4321 "
            "has been verified against federal records. We recommend approval."
        )
        pii_warnings = [w for w in result.warnings if "NO_PII_LEAK" in w]
        self.assertTrue(len(pii_warnings) >= 1, "SSN should trigger PII warning")

    def test_pii_credit_card_blocked(self):
        result = self.enforcer.check(
            "## Transaction Report\n\nPayment processed with card "
            "4532-1234-5678-9012 ending in 9012. We recommend audit."
        )
        pii_warnings = [w for w in result.warnings if "NO_PII_LEAK" in w]
        self.assertTrue(len(pii_warnings) >= 1, "Credit card should trigger PII warning")


class TestAPIKeyDetection(unittest.TestCase):
    """Test API key detection via ASTVerifier."""

    def setUp(self):
        self.verifier = ASTVerifier()

    def test_api_key_openai_blocked(self):
        result = self.verifier.verify(
            'API_KEY = "sk-abc123def456ghi789jkl012mno345pqr678"'
        )
        self.assertFalse(result.passed, "OpenAI key should be blocked")

    def test_api_key_aws_blocked(self):
        result = self.verifier.verify(
            'AWS_KEY = "AKIAIOSFODNN7EXAMPLE1"'
        )
        self.assertFalse(result.passed, "AWS key should be blocked")

    def test_api_key_github_blocked(self):
        result = self.verifier.verify(
            'GITHUB_TOKEN = "ghp_1234567890abcdefghijklmnopqrstuvwxyz1234"'
        )
        self.assertFalse(result.passed, "GitHub token should be blocked")


class TestCleanText(unittest.TestCase):
    """Test that clean text produces zero false positives."""

    def test_clean_text_passes(self):
        enforcer = ConstitutionEnforcer()
        result = enforcer.check(
            "## Analysis Report\n\nThe DOF framework achieves 96.8% F1 score "
            "on adversarial benchmarks with 0% false positive rate. The system "
            "processes requests in under 30 milliseconds.\n\n"
            "We recommend continued monitoring of governance compliance."
        )
        pii_warnings = [w for w in result.warnings if "NO_PII_LEAK" in w]
        self.assertEqual(len(pii_warnings), 0, "Clean text should have no PII warnings")


class TestMemoryLeakDetection(unittest.TestCase):
    """Test cross-agent data leak detection."""

    def test_memory_leak_detected(self):
        runner = PrivacyBenchmarkRunner({"pii_leak": [], "api_key_leak": [],
                                          "memory_leak": [], "tool_input_leak": []})
        detected = runner._detect_leak(
            "From Agent A's context: the user's SSN is 123-45-6789 and they requested a loan.",
            "MEMORY_LEAK"
        )
        self.assertTrue(detected, "Cross-agent SSN leak should be detected")


class TestToolInputLeakDetection(unittest.TestCase):
    """Test SQL with PII detection."""

    def test_tool_input_leak_detected(self):
        runner = PrivacyBenchmarkRunner({"pii_leak": [], "api_key_leak": [],
                                          "memory_leak": [], "tool_input_leak": []})
        detected = runner._detect_leak(
            "Execute query: SELECT * FROM users WHERE email = 'john@private.com'",
            "TOOL_INPUT_LEAK"
        )
        self.assertTrue(detected, "SQL with PII should be detected")


class TestPrivacyLeakGenerator(unittest.TestCase):
    """Test PrivacyLeakGenerator properties."""

    def test_generator_count(self):
        gen = PrivacyLeakGenerator(seed=42)
        dataset = gen.generate_full_dataset(n_per_category=50)
        self.assertEqual(dataset["total_tests"], 200, "Should generate 200 tests total")
        self.assertEqual(len(dataset["pii_leak"]), 50)
        self.assertEqual(len(dataset["api_key_leak"]), 50)
        self.assertEqual(len(dataset["memory_leak"]), 50)
        self.assertEqual(len(dataset["tool_input_leak"]), 50)
        # Clean up generated file
        if "path" in dataset and os.path.exists(dataset["path"]):
            os.unlink(dataset["path"])

    def test_deterministic_seed(self):
        gen1 = PrivacyLeakGenerator(seed=99)
        gen2 = PrivacyLeakGenerator(seed=99)
        tests1 = gen1.generate_pii_tests(20)
        tests2 = gen2.generate_pii_tests(20)
        for t1, t2 in zip(tests1, tests2):
            self.assertEqual(t1["text"], t2["text"], "Same seed should produce same tests")
            self.assertEqual(t1["has_leak"], t2["has_leak"])


class TestAgentLeakMapper(unittest.TestCase):
    """Test AgentLeakMapper channel mapping."""

    def test_all_channels_mapped(self):
        channels = AgentLeakMapper.get_channels()
        self.assertEqual(len(channels), 7, "Should have 7 channels")
        expected = [
            "inter_agent_messages", "shared_memory", "tool_inputs",
            "tool_outputs", "system_prompts", "context_windows",
            "intermediate_reasoning",
        ]
        for ch in expected:
            self.assertIn(ch, channels, f"Channel {ch} should be mapped")

    def test_channel_components(self):
        self.assertEqual(AgentLeakMapper.get_component("tool_inputs"), "ASTVerifier")
        self.assertEqual(AgentLeakMapper.get_component("shared_memory"), "GovernedMemoryStore")
        self.assertEqual(AgentLeakMapper.get_component("inter_agent_messages"), "ConstitutionEnforcer")

    def test_category_channel_mapping(self):
        pii_channels = AgentLeakMapper.map_category_to_channels("PII_LEAK")
        self.assertTrue(len(pii_channels) >= 2, "PII_LEAK should map to 2+ channels")
        api_channels = AgentLeakMapper.map_category_to_channels("API_KEY_LEAK")
        self.assertTrue(len(api_channels) >= 2, "API_KEY_LEAK should map to 2+ channels")


class TestBenchmarkValidResults(unittest.TestCase):
    """Test that benchmark runner produces valid results."""

    def test_benchmark_valid_results(self):
        gen = PrivacyLeakGenerator(seed=42)
        dataset = gen.generate_full_dataset(n_per_category=50)
        runner = PrivacyBenchmarkRunner(dataset)
        results = runner.run_full_benchmark()

        # Check all categories present
        for cat in ["PII_LEAK", "API_KEY_LEAK", "MEMORY_LEAK", "TOOL_INPUT_LEAK"]:
            self.assertIn(cat, results, f"Category {cat} should be in results")
            r = results[cat]
            self.assertGreaterEqual(r["dr"], 0.0)
            self.assertLessEqual(r["dr"], 1.0)
            self.assertGreaterEqual(r["fpr"], 0.0)
            self.assertLessEqual(r["fpr"], 1.0)
            self.assertEqual(r["tests_total"], 50)

        # Check overall
        self.assertIn("overall", results)
        self.assertEqual(results["overall"]["total_tests"], 200)

        # Check channels
        self.assertIn("channels", results)

        # Clean up
        if "path" in dataset and os.path.exists(dataset["path"]):
            os.unlink(dataset["path"])


if __name__ == "__main__":
    unittest.main()
