"""
Comprehensive tests for GuardianLayer - Production-ready test suite
"""
import pytest
import tempfile
import os
from pathlib import Path

# Import the main class
from src.guardian import GuardianLayer
from src.LoopDetector import LoopDetector
from src.health_monitor import HealthMonitor
from src.mcp_facade import MCPFacade
from src.providers import SQLiteStorageProvider, InMemoryCacheProvider


@pytest.fixture
def temp_db():
    """Create a temporary database for testing"""
    with tempfile.NamedTemporaryFile(suffix='.db', delete=False) as f:
        db_path = f.name
    yield db_path
    # Cleanup
    if os.path.exists(db_path):
        os.unlink(db_path)


@pytest.fixture
def memory_guardian():
    """GuardianLayer with in-memory storage (fastest for unit tests)"""
    return GuardianLayer(db_path=":memory:")


@pytest.fixture
def persistent_guardian(temp_db):
    """GuardianLayer with persistent storage for integration tests"""
    return GuardianLayer(db_path=temp_db)


class TestGuardianLayer:
    """Test suite for main GuardianLayer functionality"""
    
    def test_initialization(self, memory_guardian):
        """Test GuardianLayer initializes correctly"""
        assert memory_guardian is not None
        assert hasattr(memory_guardian, 'loop_detector')
        assert hasattr(memory_guardian, 'health_monitor')
        assert hasattr(memory_guardian, 'mcp_facade')
        assert hasattr(memory_guardian, 'experience')
    
    def test_basic_check_allowed(self, memory_guardian):
        """Test that valid tool calls are allowed"""
        # Register a test tool first
        tools = [{
            "name": "test_search",
            "description": "Search tool",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "query": {"type": "string"}
                },
                "required": ["query"]
            }
        }]
        memory_guardian.register_mcp_tools(tools)
        
        call = {"tool": "test_search", "arguments": {"query": "test"}}
        result = memory_guardian.check(call)
        
        assert result["allowed"] == True
        assert result["reason"] is None
        assert result["suggestion"] is None
        assert "health_score" in result
        assert "advice" in result
        assert result["is_probe"] == False
    
    def test_loop_detection_basic(self, memory_guardian):
        """Test that immediate repetition is blocked"""
        # Register a test tool
        tools = [{
            "name": "test_tool",
            "description": "Test tool",
            "inputSchema": {
                "type": "object",
                "properties": {},
                "required": []
            }
        }]
        memory_guardian.register_mcp_tools(tools)
        
        call = {"tool": "test_tool", "arguments": {}}
        
        # First call should be allowed
        result1 = memory_guardian.check(call)
        assert result1["allowed"] == True
        
        # Second identical call should be blocked (loop)
        result2 = memory_guardian.check(call)
        assert result2["allowed"] == False
        assert "Loop detected" in result2["reason"]
    
    def test_invalid_tool_blocked(self, memory_guardian):
        """Test that unregistered tools are blocked"""
        call = {"tool": "unknown_tool", "arguments": {}}
        result = memory_guardian.check(call)
        
        assert result["allowed"] == False
        assert "Unknown tool" in result["reason"]
    
    def test_missing_required_params(self, memory_guardian):
        """Test that missing required parameters are blocked"""
        # Register a tool with required params
        tools = [{
            "name": "test_tool",
            "description": "Test tool",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "query": {"type": "string"}
                },
                "required": ["query"]
            }
        }]
        memory_guardian.register_mcp_tools(tools)
        
        # Call without required param
        call = {"tool": "test_tool", "arguments": {}}
        result = memory_guardian.check(call)
        
        assert result["allowed"] == False
        assert "Missing required parameter" in result["reason"]
        assert "query" in result["suggestion"]
    
    def test_health_monitoring(self, memory_guardian):
        """Test health monitoring functionality"""
        tools = [{
            "name": "health_test_tool",
            "description": "Health test tool",
            "inputSchema": {
                "type": "object",
                "properties": {},
                "required": []
            }
        }]
        memory_guardian.register_mcp_tools(tools)
        
        call = {"tool": "health_test_tool", "arguments": {}}
        
        # First call should be healthy
        result = memory_guardian.check(call)
        assert result["health_score"] == 100
        
        # Simulate multiple failures to trigger circuit breaker
        for _ in range(6):  # More than failure_threshold
            memory_guardian.report_result(call, success=False, error="Test error")
        
        # Health score should now be low
        result = memory_guardian.check(call)
        assert result["health_score"] < 50


class TestLoopDetector:
    """Test suite for LoopDetector"""
    
    def test_initialization(self):
        """Test LoopDetector initializes correctly"""
        detector = LoopDetector(max_history=5, max_repeats=3)
        assert detector.max_history == 5
        assert detector.max_repeats == 3
    
    def test_immediate_repeat_detection(self):
        """Test immediate repetition detection"""
        detector = LoopDetector()
        call = {"tool": "test", "args": {"query": "test"}}
        
        # First call - should be allowed
        is_loop, reason = detector.check(call)
        assert is_loop == False
        assert reason is None
        
        # Second identical call - should be blocked
        is_loop, reason = detector.check(call)
        assert is_loop == True
        assert reason == "IMMEDIATE_REPEAT"
    
    def test_short_cycle_detection(self):
        """Test A -> B -> A cycle detection"""
        detector = LoopDetector()
        call_a = {"tool": "A", "args": {}}
        call_b = {"tool": "B", "args": {}}
        
        # A -> B -> A pattern
        detector.check(call_a)
        detector.check(call_b)
        is_loop, reason = detector.check(call_a)
        
        assert is_loop == True
        assert reason == "SHORT_CYCLE"
    
    def test_excessive_repetition(self):
        """Test excessive repetition detection"""
        detector = LoopDetector(max_repeats=2)
        call = {"tool": "repeat_test", "args": {}}
        
        # Allow 2 times
        detector.check(call)  # 1st
        detector.check(call)  # 2nd (different calls in between)
        
        # 3rd time should trigger excessive repetition
        # Simulate this by directly manipulating count
        call_hash = detector._compute_hash(call)
        detector.repeat_counts[call_hash] = 3
        
        is_loop, reason = detector.check(call)
        assert is_loop == True
        assert "REPEATED_3_TIMES" in reason
    
    def test_metrics_collection(self):
        """Test that metrics are collected correctly"""
        detector = LoopDetector()
        call = {"tool": "test", "args": {}}
        
        detector.check(call)  # Allowed
        detector.check(call)  # Blocked (loop)
        
        metrics = detector.get_metrics()
        assert metrics["total_checks"] == 2
        assert metrics["loops_detected"] == 1
        assert metrics["breakdown"]["immediate_repeats"] == 1


class TestHealthMonitor:
    """Test suite for HealthMonitor"""
    
    def test_initialization(self):
        """Test HealthMonitor initializes correctly"""
        monitor = HealthMonitor(failure_threshold=3)
        assert monitor.failure_threshold == 3
    
    def test_healthy_tool_allowed(self):
        """Test that healthy tools are allowed"""
        monitor = HealthMonitor()
        result = monitor.check_tool("test_tool")
        
        assert result["allowed"] == True
        assert result["health_score"] == 100
        assert result["is_probe"] == False
    
    def test_circuit_breaker_opens(self):
        """Test that circuit breaker opens after failures"""
        monitor = HealthMonitor(failure_threshold=3, base_cooldown=0.1)  # Short cooldown for testing
        
        # Report failures to trigger circuit breaker
        for i in range(3):
            monitor.report_result("test_tool", success=False, error=f"Test error {i}")
        
        # Should now be blocked
        result = monitor.check_tool("test_tool")
        assert result["allowed"] == False
        assert "temporarily disabled" in result["advice"]
    
    def test_circuit_breaker_recovery(self):
        """Test circuit breaker recovery mechanism"""
        monitor = HealthMonitor(failure_threshold=2, base_cooldown=0.1)
        
        # Trigger circuit breaker
        for i in range(2):
            monitor.report_result("recovery_tool", success=False, error=f"Error {i}")
        
        # Should be blocked
        result = monitor.check_tool("recovery_tool")
        assert result["allowed"] == False
        
        # Wait for cooldown + small buffer
        import time
        time.sleep(0.2)
        
        # Should now allow probe
        result = monitor.check_tool("recovery_tool")
        assert result["allowed"] == True
        assert result["is_probe"] == True
        assert "recovering" in result["advice"]


class TestMCPFacade:
    """Test suite for MCPFacade"""
    
    def test_tool_registration(self):
        """Test tool registration from MCP response"""
        facade = MCPFacade()
        
        tools = [{
            "name": "test_tool",
            "description": "Test tool",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "param1": {"type": "string"}
                },
                "required": ["param1"]
            }
        }]
        
        count = facade.ingest_mcp_tools(tools)
        assert count == 1
        
        # Tool should be registered
        tool = facade.get_tool("test_tool")
        assert tool is not None
        assert tool.name == "test_tool"
        assert "param1" in tool.required_params
    
    def test_validation_success(self):
        """Test successful validation of correct calls"""
        facade = MCPFacade()
        
        # Register test tool
        tools = [{
            "name": "validate_tool",
            "description": "Validation test",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "required_param": {"type": "string"}
                },
                "required": ["required_param"]
            }
        }]
        facade.ingest_mcp_tools(tools)
        
        # Valid call
        call = {"tool": "validate_tool", "arguments": {"required_param": "test"}}
        result = facade.validate_call(call)
        
        assert result["valid"] == True
    
    def test_validation_failure(self):
        """Test validation failure for invalid calls"""
        facade = MCPFacade()
        
        # Register test tool
        tools = [{
            "name": "validate_tool",
            "description": "Validation test",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "required_param": {"type": "string"}
                },
                "required": ["required_param"]
            }
        }]
        facade.ingest_mcp_tools(tools)
        
        # Invalid call - missing required param
        call = {"tool": "validate_tool", "arguments": {}}
        result = facade.validate_call(call)
        
        assert result["valid"] == False
        assert "Missing required parameter" in result["reason"]
        assert "required_param" in result["suggestion"]


class TestIntegration:
    """Integration tests for complete workflows"""
    
    def test_complete_workflow(self, persistent_guardian):
        """Test complete workflow from registration to monitoring"""
        # Register tools
        tools = [{
            "name": "integration_tool",
            "description": "Integration test tool",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "query": {"type": "string"}
                },
                "required": ["query"]
            }
        }]
        persistent_guardian.register_mcp_tools(tools)
        
        # Make successful calls
        for i in range(3):
            call = {"tool": "integration_tool", "arguments": {"query": f"test_{i}"}}
            result = persistent_guardian.check(call)
            assert result["allowed"] == True
            
            # Report success
            persistent_guardian.report_result(call, success=True)
        
        # Check health is still good
        result = persistent_guardian.check(call)
        assert result["health_score"] == 100
        
        # Simulate failures
        for i in range(6):
            call = {"tool": "integration_tool", "arguments": {"query": "fail"}}
            result = persistent_guardian.check(call)
            if result["allowed"]:
                persistent_guardian.report_result(call, success=False, error=f"Error {i}")
        
        # Health should now be degraded
        result = persistent_guardian.check(call)
        assert result["health_score"] < 100