"""
Tests for unified error handling system

Tests the exception hierarchy, error handler middleware, and error response format.
"""
import pytest
from datetime import datetime
from src.utils.errors import (
    AppException,
    BusinessException,
    SystemException,
    ErrorCode,
    InvalidParameterException,
    ResourceNotFoundException,
    ProjectNotFoundException,
    TaskNotFoundException,
    TaskTimeoutException,
    TaskQueueFullException,
    ModelNotFoundException,
    GenerationFailedException,
    StorageException,
    RateLimitExceededException,
    UnauthorizedException,
    ForbiddenException,
    ConflictException
)


class TestExceptionHierarchy:
    """Test exception class hierarchy and initialization"""
    
    def test_app_exception_base(self):
        """Test AppException base class"""
        exc = AppException(
            code=ErrorCode.UNKNOWN_ERROR,
            message="Test error",
            details={"key": "value"},
            status_code=500
        )
        
        assert exc.code == ErrorCode.UNKNOWN_ERROR
        assert exc.message == "Test error"
        assert exc.details == {"key": "value"}
        assert exc.status_code == 500
        
        # Test to_dict conversion
        exc_dict = exc.to_dict()
        assert exc_dict["code"] == "1000"
        assert exc_dict["message"] == "Test error"
        assert exc_dict["details"] == {"key": "value"}
    
    def test_business_exception(self):
        """Test BusinessException defaults to 400 status"""
        exc = BusinessException(
            code=ErrorCode.INVALID_PARAMETER,
            message="Invalid input"
        )
        
        assert exc.status_code == 400
        assert isinstance(exc, AppException)
    
    def test_system_exception(self):
        """Test SystemException defaults to 500 status"""
        exc = SystemException(
            code=ErrorCode.UNKNOWN_ERROR,
            message="System failure"
        )
        
        assert exc.status_code == 500
        assert isinstance(exc, AppException)


class TestBusinessExceptions:
    """Test specific business exception classes"""
    
    def test_invalid_parameter_exception(self):
        """Test InvalidParameterException"""
        exc = InvalidParameterException(field="email", reason="Invalid format")
        
        assert exc.code == ErrorCode.INVALID_PARAMETER
        assert "email" in exc.message
        assert exc.details["field"] == "email"
        assert exc.details["reason"] == "Invalid format"
        assert exc.status_code == 400
    
    def test_resource_not_found_exception(self):
        """Test ResourceNotFoundException"""
        exc = ResourceNotFoundException(resource_type="User", resource_id="123")
        
        assert exc.code == ErrorCode.NOT_FOUND
        assert "User" in exc.message
        assert exc.details["resource_type"] == "User"
        assert exc.details["resource_id"] == "123"
    
    def test_project_not_found_exception(self):
        """Test ProjectNotFoundException"""
        exc = ProjectNotFoundException(project_id="proj_123")
        
        assert exc.code == ErrorCode.PROJECT_NOT_FOUND
        assert exc.details["project_id"] == "proj_123"
        assert isinstance(exc, BusinessException)
    
    def test_task_not_found_exception(self):
        """Test TaskNotFoundException"""
        exc = TaskNotFoundException(task_id="task_123")
        
        assert exc.code == ErrorCode.TASK_NOT_FOUND
        assert exc.details["task_id"] == "task_123"
    
    def test_model_not_found_exception(self):
        """Test ModelNotFoundException"""
        exc = ModelNotFoundException(model_id="flux-pro")
        
        assert exc.code == ErrorCode.MODEL_NOT_FOUND
        assert exc.details["model_id"] == "flux-pro"
    
    def test_rate_limit_exceeded_exception(self):
        """Test RateLimitExceededException"""
        exc = RateLimitExceededException(limit=100, window=60)
        
        assert exc.code == ErrorCode.RATE_LIMIT_EXCEEDED
        assert exc.status_code == 429
        assert exc.details["limit"] == 100
        assert exc.details["window_seconds"] == 60
    
    def test_unauthorized_exception(self):
        """Test UnauthorizedException"""
        exc = UnauthorizedException(reason="Invalid token")
        
        assert exc.code == ErrorCode.UNAUTHORIZED
        assert exc.status_code == 401
        assert exc.details["reason"] == "Invalid token"
    
    def test_forbidden_exception(self):
        """Test ForbiddenException"""
        exc = ForbiddenException(reason="Insufficient permissions")
        
        assert exc.code == ErrorCode.FORBIDDEN
        assert exc.status_code == 403
    
    def test_conflict_exception(self):
        """Test ConflictException"""
        exc = ConflictException(resource_type="Project", reason="Name already exists")
        
        assert exc.code == ErrorCode.CONFLICT
        assert exc.status_code == 409


class TestSystemExceptions:
    """Test specific system exception classes"""
    
    def test_task_timeout_exception(self):
        """Test TaskTimeoutException"""
        exc = TaskTimeoutException(task_id="task_123", timeout=300)
        
        assert exc.code == ErrorCode.TASK_TIMEOUT
        assert exc.status_code == 500
        assert exc.details["task_id"] == "task_123"
        assert exc.details["timeout_seconds"] == 300
        assert isinstance(exc, SystemException)
    
    def test_task_queue_full_exception(self):
        """Test TaskQueueFullException"""
        exc = TaskQueueFullException(queue_size=1000)
        
        assert exc.code == ErrorCode.TASK_QUEUE_FULL
        assert exc.status_code == 500
        assert exc.details["queue_size"] == 1000
    
    def test_generation_failed_exception(self):
        """Test GenerationFailedException"""
        exc = GenerationFailedException(reason="API error", provider="dashscope")
        
        assert exc.code == ErrorCode.GENERATION_FAILED
        assert exc.status_code == 500
        assert exc.details["reason"] == "API error"
        assert exc.details["provider"] == "dashscope"
    
    def test_storage_exception(self):
        """Test StorageException"""
        exc = StorageException(operation="upload", reason="Disk full")
        
        assert exc.code == ErrorCode.STORAGE_ERROR
        assert exc.status_code == 500
        assert exc.details["operation"] == "upload"
        assert exc.details["reason"] == "Disk full"


class TestErrorCodes:
    """Test error code enumeration"""
    
    def test_error_code_values(self):
        """Test error code values follow the format"""
        # Success
        assert ErrorCode.SUCCESS.value == "0000"
        
        # General errors (1xxx)
        assert ErrorCode.UNKNOWN_ERROR.value == "1000"
        assert ErrorCode.INVALID_PARAMETER.value == "1001"
        assert ErrorCode.NOT_FOUND.value == "1004"
        
        # Business errors (2xxx)
        assert ErrorCode.PROJECT_NOT_FOUND.value == "2001"
        assert ErrorCode.ASSET_NOT_FOUND.value == "2011"
        
        # Task errors (3xxx)
        assert ErrorCode.TASK_NOT_FOUND.value == "3002"
        assert ErrorCode.TASK_TIMEOUT.value == "3003"
        
        # AI service errors (4xxx)
        assert ErrorCode.MODEL_NOT_FOUND.value == "4001"
        assert ErrorCode.GENERATION_FAILED.value == "4003"
        
        # Storage errors (5xxx)
        assert ErrorCode.STORAGE_ERROR.value == "5001"
        assert ErrorCode.FILE_NOT_FOUND.value == "5002"
    
    def test_error_code_categories(self):
        """Test error codes are properly categorized"""
        # All general errors start with 1
        assert ErrorCode.UNKNOWN_ERROR.value.startswith("1")
        assert ErrorCode.INVALID_PARAMETER.value.startswith("1")
        
        # All business errors start with 2
        assert ErrorCode.PROJECT_NOT_FOUND.value.startswith("2")
        
        # All task errors start with 3
        assert ErrorCode.TASK_TIMEOUT.value.startswith("3")
        
        # All AI service errors start with 4
        assert ErrorCode.MODEL_NOT_FOUND.value.startswith("4")
        
        # All storage errors start with 5
        assert ErrorCode.STORAGE_ERROR.value.startswith("5")


class TestExceptionToDictConversion:
    """Test exception to dictionary conversion for API responses"""
    
    def test_simple_exception_to_dict(self):
        """Test basic exception to dict conversion"""
        exc = ProjectNotFoundException(project_id="proj_123")
        exc_dict = exc.to_dict()
        
        assert "code" in exc_dict
        assert "message" in exc_dict
        assert "details" in exc_dict
        assert exc_dict["code"] == "2001"
        assert exc_dict["details"]["project_id"] == "proj_123"
    
    def test_exception_with_complex_details(self):
        """Test exception with nested details"""
        exc = AppException(
            code=ErrorCode.INVALID_PARAMETER,
            message="Validation failed",
            details={
                "errors": [
                    {"field": "email", "message": "Invalid format"},
                    {"field": "age", "message": "Must be positive"}
                ]
            }
        )
        
        exc_dict = exc.to_dict()
        assert len(exc_dict["details"]["errors"]) == 2
        assert exc_dict["details"]["errors"][0]["field"] == "email"


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