"""Tests for error classes and error handling.

Tests cover:
- MetaApiError creation and serialization
- Error code mapping
- MCP error hierarchy
"""
from __future__ import annotations


from src.errors import (
    MetaApiError,
    MetaApiRateLimitError,
    MetaApiAuthError,
    MetaApiPermissionError,
    MetaErrorContext,
    build_error_context,
    map_meta_error,
    MCPError,
    ValidationError,
    AuthenticationError,
    AuthorizationError,
    RateLimitError,
    ResourceNotFoundError,
    EngineError,
    TimeoutError,
)


# =============================================================================
# MetaErrorContext Tests
# =============================================================================

class TestMetaErrorContext:
    """Tests for MetaErrorContext dataclass."""

    def test_context_creation_minimal(self):
        """Test context with only message."""
        ctx = MetaErrorContext(message="Test error")
        assert ctx.message == "Test error"
        assert ctx.type is None
        assert ctx.code is None

    def test_context_creation_full(self):
        """Test context with all fields."""
        ctx = MetaErrorContext(
            message="Test error",
            type="OAuthException",
            code=100,
            error_subcode=1234,
            fbtrace_id="abc123",
            is_transient=False,
            error_user_title="Error",
            error_user_msg="Something went wrong",
            raw={"error": {}},
        )
        
        assert ctx.message == "Test error"
        assert ctx.type == "OAuthException"
        assert ctx.code == 100
        assert ctx.error_subcode == 1234
        assert ctx.fbtrace_id == "abc123"
        assert ctx.is_transient is False


# =============================================================================
# Build Error Context Tests
# =============================================================================

class TestBuildErrorContext:
    """Tests for build_error_context function."""

    def test_build_from_error_wrapper(self):
        """Test building context from wrapped error payload."""
        payload = {
            "error": {
                "message": "Invalid parameter",
                "type": "OAuthException",
                "code": 100,
            }
        }
        
        ctx = build_error_context(payload)
        
        assert ctx.message == "Invalid parameter"
        assert ctx.type == "OAuthException"
        assert ctx.code == 100

    def test_build_from_flat_payload(self):
        """Test building context from flat error payload."""
        payload = {
            "message": "Direct error",
            "code": 200,
        }
        
        ctx = build_error_context(payload)
        
        assert ctx.message == "Direct error"
        assert ctx.code == 200

    def test_build_with_missing_message(self):
        """Test default message when missing."""
        payload = {"error": {"code": 100}}
        
        ctx = build_error_context(payload)
        
        assert ctx.message == "Unknown Meta API error"

    def test_raw_payload_stored(self):
        """Test raw payload is stored in context."""
        payload = {"error": {"message": "Test"}}
        
        ctx = build_error_context(payload)
        
        assert ctx.raw == payload


# =============================================================================
# MetaApiError Tests
# =============================================================================

class TestMetaApiError:
    """Tests for MetaApiError class."""

    def test_error_creation(self):
        """Test creating MetaApiError."""
        ctx = MetaErrorContext(message="Test error", code=100)
        error = MetaApiError(context=ctx, status_code=400)
        
        assert str(error) == "Test error"
        assert error.status_code == 400
        assert error.context.code == 100

    def test_to_dict(self):
        """Test serialization to dictionary."""
        ctx = MetaErrorContext(
            message="Test error",
            type="OAuthException",
            code=100,
            error_subcode=1234,
            fbtrace_id="trace123",
        )
        error = MetaApiError(context=ctx, status_code=400)
        
        result = error.to_dict()
        
        assert result["message"] == "Test error"
        assert result["type"] == "OAuthException"
        assert result["code"] == 100
        assert result["error_subcode"] == 1234
        assert result["fbtrace_id"] == "trace123"
        assert result["status_code"] == 400


# =============================================================================
# Error Mapping Tests
# =============================================================================

class TestMapMetaError:
    """Tests for map_meta_error function."""

    def test_rate_limit_error_code_1(self):
        """Test rate limit error code 1."""
        error = map_meta_error(429, {"error": {"message": "Rate limited", "code": 1}})
        assert isinstance(error, MetaApiRateLimitError)

    def test_rate_limit_error_code_4(self):
        """Test rate limit error code 4."""
        error = map_meta_error(429, {"error": {"message": "Rate limited", "code": 4}})
        assert isinstance(error, MetaApiRateLimitError)

    def test_rate_limit_error_code_17(self):
        """Test rate limit error code 17."""
        error = map_meta_error(429, {"error": {"message": "Rate limited", "code": 17}})
        assert isinstance(error, MetaApiRateLimitError)

    def test_auth_error_code_190(self):
        """Test auth error code 190 (invalid token)."""
        error = map_meta_error(401, {"error": {"message": "Invalid token", "code": 190}})
        assert isinstance(error, MetaApiAuthError)

    def test_auth_error_code_102(self):
        """Test auth error code 102 (session expired)."""
        error = map_meta_error(401, {"error": {"message": "Session expired", "code": 102}})
        assert isinstance(error, MetaApiAuthError)

    def test_permission_error_code_200(self):
        """Test permission error code 200."""
        error = map_meta_error(403, {"error": {"message": "Permission denied", "code": 200}})
        assert isinstance(error, MetaApiPermissionError)

    def test_transient_becomes_rate_limit(self):
        """Test transient errors are mapped to rate limit."""
        error = map_meta_error(500, {
            "error": {"message": "Transient error", "code": 9999, "is_transient": True}
        })
        assert isinstance(error, MetaApiRateLimitError)

    def test_unknown_code_returns_base_error(self):
        """Test unknown error codes return base MetaApiError."""
        error = map_meta_error(400, {"error": {"message": "Unknown", "code": 99999}})
        assert type(error) is MetaApiError


# =============================================================================
# MCP Error Hierarchy Tests
# =============================================================================

class TestMCPError:
    """Tests for base MCPError class."""

    def test_mcp_error_creation(self):
        """Test creating MCPError."""
        error = MCPError("Test message")
        
        assert error.message == "Test message"
        assert error.code == "MCPError"
        assert error.details == {}

    def test_mcp_error_with_code(self):
        """Test MCPError with custom code."""
        error = MCPError("Test", code="CUSTOM_CODE")
        assert error.code == "CUSTOM_CODE"

    def test_mcp_error_to_dict(self):
        """Test MCPError serialization."""
        error = MCPError("Test", code="CODE", details={"key": "value"})
        
        result = error.to_dict()
        
        assert result["message"] == "Test"
        assert result["code"] == "CODE"
        assert result["type"] == "MCPError"
        assert result["details"] == {"key": "value"}


class TestValidationError:
    """Tests for ValidationError."""

    def test_validation_error_with_field(self):
        """Test ValidationError with field name."""
        error = ValidationError("Invalid input", field="email")
        
        assert error.code == "VALIDATION_ERROR"
        assert error.details["field"] == "email"


class TestAuthenticationError:
    """Tests for AuthenticationError."""

    def test_default_message(self):
        """Test default authentication error message."""
        error = AuthenticationError()
        assert error.message == "Authentication failed"
        assert error.code == "AUTHENTICATION_ERROR"


class TestAuthorizationError:
    """Tests for AuthorizationError."""

    def test_with_resource(self):
        """Test authorization error with resource."""
        error = AuthorizationError("Access denied", resource="campaign_123")
        
        assert error.details["resource"] == "campaign_123"


class TestRateLimitError:
    """Tests for RateLimitError."""

    def test_with_retry_after(self):
        """Test rate limit error with retry-after."""
        error = RateLimitError(retry_after=60)
        
        assert error.details["retry_after"] == 60


class TestResourceNotFoundError:
    """Tests for ResourceNotFoundError."""

    def test_with_resource_info(self):
        """Test not found error with resource info."""
        error = ResourceNotFoundError(
            "Campaign not found",
            resource_type="campaign",
            resource_id="123"
        )
        
        assert error.details["resource_type"] == "campaign"
        assert error.details["resource_id"] == "123"


class TestEngineError:
    """Tests for EngineError."""

    def test_with_engine_info(self):
        """Test engine error with engine info."""
        error = EngineError(
            "Processing failed",
            engine="FraudDetection",
            operation="analyze_clicks"
        )
        
        assert error.details["engine"] == "FraudDetection"
        assert error.details["operation"] == "analyze_clicks"


class TestTimeoutError:
    """Tests for TimeoutError."""

    def test_with_timeout_value(self):
        """Test timeout error with timeout value."""
        error = TimeoutError(timeout=30.0)
        
        assert error.details["timeout"] == 30.0
