"""
Test suite for error-handling.md documentation examples.
Tests what's actually implemented in ADK and identifies missing features.
"""

import pytest
import asyncio
import time
from datetime import datetime
from unittest.mock import Mock, AsyncMock
from typing import Dict, Any

# ADK imports - test what's actually available
from adk.errors import (
    AdkError,
    AdkLLMError,
    AdkSessionError,
    AdkConfigError,
    AdkCircuitBreakerError,
    AdkErrorType,
    AdkErrorSeverity,
    create_adk_timeout_error,
    create_adk_rate_limit_error,
    create_adk_quota_error,
    create_adk_content_filter_error,
    classify_error,
    should_retry_error,
    calculate_retry_delay,
    with_adk_retry,
    with_adk_timeout,
    CircuitBreaker,
    create_circuit_breaker,
    AdkErrorHandler,
    create_adk_error_handler
)

def test_adk_error_hierarchy():
    """Test ADK error hierarchy from docs."""
    
    # Test base AdkError
    base_error = AdkError(
        message="Base error",
        error_type=AdkErrorType.UNKNOWN,
        severity=AdkErrorSeverity.MEDIUM,
        retryable=True,
        metadata={"test": "data"}
    )
    
    assert base_error.message == "Base error"
    assert base_error.error_type == AdkErrorType.UNKNOWN
    assert base_error.severity == AdkErrorSeverity.MEDIUM
    assert base_error.retryable == True
    assert base_error.metadata["test"] == "data"
    assert isinstance(base_error.timestamp, datetime)

def test_adk_llm_error():
    """Test AdkLLMError from docs."""
    
    llm_error = AdkLLMError(
        message="LLM service timeout",
        provider="openai",
        model="gpt-4",
        error_type=AdkErrorType.TIMEOUT,
        severity=AdkErrorSeverity.HIGH,
        retryable=True
    )
    
    assert llm_error.message == "LLM service timeout"
    assert llm_error.provider == "openai"
    assert llm_error.model == "gpt-4"
    assert llm_error.metadata["provider"] == "openai"
    assert llm_error.metadata["model"] == "gpt-4"
    assert llm_error.error_type == AdkErrorType.TIMEOUT

def test_adk_session_error():
    """Test AdkSessionError from docs."""
    
    session_error = AdkSessionError(
        message="Session expired",
        session_id="sess_123",
        user_id="user_456",
        error_type=AdkErrorType.AUTHENTICATION
    )
    
    assert session_error.message == "Session expired"
    assert session_error.session_id == "sess_123"
    assert session_error.user_id == "user_456"
    assert session_error.metadata["session_id"] == "sess_123"
    assert session_error.metadata["user_id"] == "user_456"

def test_error_factory_functions():
    """Test error factory functions from docs."""
    
    # Test timeout error
    timeout_error = create_adk_timeout_error(
        timeout_seconds=30.0,
        operation="LLM request",
        provider="openai"
    )
    assert "timed out after 30.0s" in timeout_error.message
    assert timeout_error.error_type == AdkErrorType.TIMEOUT
    assert timeout_error.retryable == True
    
    # Test rate limit error
    rate_limit_error = create_adk_rate_limit_error(
        retry_after=60,
        provider="anthropic"
    )
    assert "retry after 60s" in rate_limit_error.message
    assert rate_limit_error.error_type == AdkErrorType.RATE_LIMIT
    assert rate_limit_error.metadata["retry_after"] == 60
    
    # Test quota error
    quota_error = create_adk_quota_error(provider="openai")
    assert "quota exceeded" in quota_error.message.lower()
    assert quota_error.error_type == AdkErrorType.QUOTA_EXCEEDED
    assert quota_error.retryable == False
    
    # Test content filter error
    content_error = create_adk_content_filter_error(
        content="inappropriate content",
        provider="openai"
    )
    assert "content filtered" in content_error.message.lower()
    assert content_error.error_type == AdkErrorType.CONTENT_FILTER

def test_error_classification():
    """Test error classification from docs."""
    
    # Test timeout classification
    timeout_ex = Exception("Request timed out")
    assert classify_error(timeout_ex) == AdkErrorType.TIMEOUT
    
    # Test rate limit classification
    rate_limit_ex = Exception("Rate limit exceeded")
    assert classify_error(rate_limit_ex) == AdkErrorType.RATE_LIMIT
    
    # Test authentication classification
    auth_ex = Exception("Authentication failed")
    assert classify_error(auth_ex) == AdkErrorType.AUTHENTICATION
    
    # Test unknown classification
    unknown_ex = Exception("Something went wrong")
    assert classify_error(unknown_ex) == AdkErrorType.UNKNOWN

def test_should_retry_logic():
    """Test retry logic from docs."""
    
    # Test retryable errors
    timeout_error = AdkError("timeout", error_type=AdkErrorType.TIMEOUT, retryable=True)
    assert should_retry_error(timeout_error) == True
    
    # Test non-retryable errors
    auth_error = AdkError("auth failed", error_type=AdkErrorType.AUTHENTICATION, retryable=False)
    assert should_retry_error(auth_error) == False
    
    # Test exception classification
    timeout_ex = Exception("connection timeout")
    assert should_retry_error(timeout_ex) == True
    
    auth_ex = Exception("unauthorized access")
    # Check what classify_error actually returns for this
    error_type = classify_error(auth_ex)
    # Authentication errors are not in the retryable list in should_retry_error
    assert should_retry_error(auth_ex) == False  # Authentication errors are not retryable

def test_retry_delay_calculation():
    """Test retry delay calculation from docs."""
    
    # Test exponential backoff
    delay1 = calculate_retry_delay(0, base_delay=1.0, exponential_base=2.0, jitter=False)
    delay2 = calculate_retry_delay(1, base_delay=1.0, exponential_base=2.0, jitter=False)
    delay3 = calculate_retry_delay(2, base_delay=1.0, exponential_base=2.0, jitter=False)
    
    assert delay1 == 1.0
    assert delay2 == 2.0
    assert delay3 == 4.0
    
    # Test max delay cap
    delay_capped = calculate_retry_delay(10, base_delay=1.0, max_delay=30.0, jitter=False)
    assert delay_capped == 30.0
    
    # Test jitter adds randomness
    delay_jitter1 = calculate_retry_delay(1, jitter=True)
    delay_jitter2 = calculate_retry_delay(1, jitter=True)
    # With jitter, delays should be different (with high probability)
    # We'll just test that jitter doesn't break the function
    assert delay_jitter1 > 0
    assert delay_jitter2 > 0

@pytest.mark.asyncio
async def test_with_adk_timeout():
    """Test timeout wrapper from docs."""
    
    async def fast_operation():
        await asyncio.sleep(0.1)
        return "success"
    
    async def slow_operation():
        await asyncio.sleep(2.0)
        return "success"
    
    # Test successful operation within timeout
    result = await with_adk_timeout(fast_operation(), 1.0, "fast_op")
    assert result == "success"
    
    # Test timeout
    with pytest.raises(AdkLLMError) as exc_info:
        await with_adk_timeout(slow_operation(), 0.5, "slow_op")
    
    error = exc_info.value
    assert error.error_type == AdkErrorType.TIMEOUT
    assert "timed out after 0.5s" in error.message

@pytest.mark.asyncio
async def test_circuit_breaker():
    """Test circuit breaker implementation from docs."""
    
    # Create circuit breaker
    circuit_breaker = create_circuit_breaker(
        name="test-service",
        failure_threshold=3,
        recovery_timeout=1,  # 1 second for testing
        success_threshold=2
    )
    
    assert circuit_breaker.name == "test-service"
    assert circuit_breaker.failure_threshold == 3
    assert circuit_breaker.recovery_timeout == 1
    assert circuit_breaker.success_threshold == 2
    
    # Test successful calls
    async def successful_operation():
        return "success"
    
    result = await circuit_breaker.call(successful_operation)
    assert result == "success"
    assert circuit_breaker.state.state == "closed"
    
    # Test failure accumulation
    async def failing_operation():
        raise Exception("Service unavailable")
    
    # Accumulate failures to open circuit
    for i in range(3):
        with pytest.raises(Exception):
            await circuit_breaker.call(failing_operation)
    
    # Circuit should now be open
    assert circuit_breaker.state.state == "open"
    
    # Test circuit breaker rejection
    with pytest.raises(AdkCircuitBreakerError):
        await circuit_breaker.call(successful_operation)

def test_error_handler():
    """Test error handler from docs."""
    
    error_handler = create_adk_error_handler()
    assert isinstance(error_handler, AdkErrorHandler)
    
    # Test handling ADK error (should return as-is)
    adk_error = AdkLLMError("Test error")
    handled = error_handler.handle_error(adk_error)
    assert handled is adk_error
    
    # Test handling regular exception (should convert)
    regular_error = Exception("Regular error")
    handled = error_handler.handle_error(regular_error, {"context": "test"})
    
    assert isinstance(handled, AdkError)
    assert handled.message == "Regular error"
    assert handled.cause is regular_error
    assert handled.metadata["context"] == "test"

def test_error_serialization():
    """Test error serialization from docs."""
    
    error = AdkLLMError(
        message="Test error",
        provider="openai",
        model="gpt-4",
        error_type=AdkErrorType.TIMEOUT,
        severity=AdkErrorSeverity.HIGH,
        retryable=True,
        metadata={"request_id": "req_123"}
    )
    
    error_dict = error.to_dict()
    
    assert error_dict["type"] == "timeout"
    assert error_dict["severity"] == "high"
    assert error_dict["message"] == "Test error"
    assert error_dict["retryable"] == True
    assert error_dict["metadata"]["provider"] == "openai"
    assert error_dict["metadata"]["model"] == "gpt-4"
    assert error_dict["metadata"]["request_id"] == "req_123"
    assert "timestamp" in error_dict

# Test what's missing from the documentation but not implemented

def test_missing_features_documentation():
    """Document what features from error-handling.md are not implemented."""
    
    missing_features = [
        "create_circuit_breaker decorator syntax",
        "with_fallback decorator",
        "FallbackChain class",
        "ErrorMetrics class", 
        "ErrorLogger class",
        "ErrorAlerter class",
        "AutoRecoveryHandler class",
        "RecoveryManager class",
        "HealthChecker class",
        "ServiceStatus enum",
        "ErrorBudget class",
        "GracefulShutdownHandler class",
        "SessionErrorHandler decorator",
        "ToolErrorHandler decorator",
        "ErrorInjector for testing",
        "ChaosMonkey for chaos engineering"
    ]
    
    # This test documents what's missing - it always passes
    # but serves as documentation of the gap between docs and implementation
    print(f"\nMissing features from error-handling.md:")
    for feature in missing_features:
        print(f"  - {feature}")
    
    assert True  # Always pass, this is just documentation

@pytest.mark.asyncio 
async def test_retry_wrapper_functionality():
    """Test the retry wrapper functionality that is implemented."""
    
    call_count = 0
    
    async def flaky_operation():
        nonlocal call_count
        call_count += 1
        if call_count < 3:
            raise Exception("connection timeout")  # Use a retryable error type
        return "success"
    
    # Test retry wrapper - with_adk_retry wraps a function and returns a wrapper
    wrapped_func = await with_adk_retry(
        flaky_operation,
        max_retries=3,
        base_delay=0.01  # Very short delay for testing
    )
    
    result = await wrapped_func()
    assert result == "success"
    assert call_count == 3  # Should have been called 3 times

if __name__ == "__main__":
    pytest.main([__file__, "-v"])
