"""Exception-specific handlers for fix generation.

Provides specialized handling strategies for different exception types,
improving fix quality by using type-specific context and patterns.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Type

from autodebug.detection.exception_classifier import ClassificationResult


@dataclass
class HandlerContext:
    """Context passed to exception handlers.

    Attributes:
        classification: Exception classification result
        source_code: Source code around the error
        stack_trace: Stack trace lines
        io_transcript: Optional I/O transcript
        variables: Optional captured variable values
    """

    classification: ClassificationResult
    source_code: str
    stack_trace: List[str]
    io_transcript: Optional[str] = None
    variables: Optional[Dict[str, Any]] = None


@dataclass
class HandlerResult:
    """Result from an exception handler.

    Attributes:
        recommended_strategies: Ordered list of fix strategies
        context_additions: Additional context for the LLM
        constraints: Specific constraints for this fix
        examples: Relevant few-shot examples
        priority_checks: Checks to run first
    """

    recommended_strategies: List[str] = field(default_factory=list)
    context_additions: Dict[str, Any] = field(default_factory=dict)
    constraints: List[str] = field(default_factory=list)
    examples: List[Dict] = field(default_factory=list)
    priority_checks: List[str] = field(default_factory=list)


class ExceptionHandler(ABC):
    """Base class for exception-specific handlers."""

    # Exception types this handler handles
    handles: List[str] = []

    @abstractmethod
    def handle(self, context: HandlerContext) -> HandlerResult:
        """Process the exception and return handling guidance.

        Args:
            context: Handler context with exception details

        Returns:
            HandlerResult with strategies and context
        """
        pass

    def can_handle(self, exception_type: str) -> bool:
        """Check if this handler can handle the exception type.

        Args:
            exception_type: The exception type name

        Returns:
            True if this handler applies
        """
        return exception_type in self.handles


class KeyErrorHandler(ExceptionHandler):
    """Handler for KeyError exceptions."""

    handles = ["KeyError"]

    def handle(self, context: HandlerContext) -> HandlerResult:
        """Handle KeyError with dictionary-specific guidance."""
        result = HandlerResult()

        semantic = context.classification.semantic_info
        missing_key = semantic.get("missing_key")

        # Recommend strategies based on context
        if missing_key:
            result.recommended_strategies = [
                "use_get_default",
                "validate_keys_early",
                "use_defaultdict",
            ]

            result.context_additions = {
                "missing_key": missing_key,
                "suggestion": f"Use .get('{missing_key}') or check key existence",
            }

            result.constraints = [
                "Do not use bare except to catch KeyError",
                "Provide a meaningful default or error message",
            ]

        # Check if it's likely from API response
        if context.io_transcript and "response" in context.io_transcript.lower():
            result.recommended_strategies.insert(0, "validate_keys_early")
            result.priority_checks.append("check_api_response_schema")

        return result


class TypeErrorHandler(ExceptionHandler):
    """Handler for TypeError exceptions."""

    handles = ["TypeError"]

    def handle(self, context: HandlerContext) -> HandlerResult:
        """Handle TypeError with type-specific guidance."""
        result = HandlerResult()
        semantic = context.classification.semantic_info

        # Check for None-related TypeError
        if semantic.get("object_type") == "NoneType":
            result.recommended_strategies = [
                "add_none_check",
                "add_type_check",
                "fix_type_conversion",
            ]
            result.constraints = [
                "Prefer fixing the source of None over adding checks",
                "Use explicit None checks, not truthy checks for valid falsy values",
            ]
            result.priority_checks.append("trace_none_source")

        # Check for argument mismatch
        elif "argument" in context.classification.exception_message.lower():
            result.recommended_strategies = [
                "fix_type_conversion",
                "add_type_check",
                "add_error_handling",
            ]

        # Check for unsupported operand
        elif "unsupported operand" in context.classification.exception_message.lower():
            result.recommended_strategies = [
                "add_type_check",
                "fix_type_conversion",
                "add_error_handling",
            ]

        return result


class AttributeErrorHandler(ExceptionHandler):
    """Handler for AttributeError exceptions."""

    handles = ["AttributeError"]

    def handle(self, context: HandlerContext) -> HandlerResult:
        """Handle AttributeError with attribute-specific guidance."""
        result = HandlerResult()
        semantic = context.classification.semantic_info

        missing_attr = semantic.get("missing_attribute")
        object_type = semantic.get("object_type")

        if object_type == "NoneType":
            # This is a None access issue
            result.recommended_strategies = [
                "add_none_check",
                "check_object_type",
                "add_error_handling",
            ]
            result.constraints = [
                "Find where None was assigned and fix that",
                "Don't just add None checks without understanding the source",
            ]

        elif missing_attr:
            # Check for typo
            result.recommended_strategies = [
                "fix_attribute_name",
                "check_object_type",
                "add_error_handling",
            ]
            result.context_additions = {
                "missing_attribute": missing_attr,
                "object_type": object_type,
            }
            result.priority_checks.append("find_similar_attributes")

        return result


class ValueErrorHandler(ExceptionHandler):
    """Handler for ValueError exceptions."""

    handles = ["ValueError"]

    def handle(self, context: HandlerContext) -> HandlerResult:
        """Handle ValueError with value-specific guidance."""
        result = HandlerResult()

        message = context.classification.exception_message.lower()

        if "invalid literal" in message:
            result.recommended_strategies = [
                "validate_input_range",
                "fix_type_conversion",
                "add_error_handling",
            ]
        elif "not enough values" in message or "too many values" in message:
            result.recommended_strategies = [
                "validate_input_range",
                "fix_enum_value",
                "add_error_handling",
            ]
        else:
            result.recommended_strategies = [
                "validate_input_range",
                "fix_enum_value",
                "add_error_handling",
            ]

        result.constraints = [
            "Validate at the input boundary, not deep in the code",
            "Provide clear error messages about expected format",
        ]

        return result


class IndexErrorHandler(ExceptionHandler):
    """Handler for IndexError exceptions."""

    handles = ["IndexError"]

    def handle(self, context: HandlerContext) -> HandlerResult:
        """Handle IndexError with index-specific guidance."""
        result = HandlerResult()

        result.recommended_strategies = [
            "check_list_bounds",
            "use_safe_list_access",
            "add_error_handling",
        ]

        result.constraints = [
            "Check for empty sequences before accessing [0]",
            "Consider if empty sequence is valid state",
        ]

        result.priority_checks = [
            "check_sequence_source",
            "verify_expected_length",
        ]

        return result


class ImportErrorHandler(ExceptionHandler):
    """Handler for ImportError and ModuleNotFoundError."""

    handles = ["ImportError", "ModuleNotFoundError"]

    def handle(self, context: HandlerContext) -> HandlerResult:
        """Handle import errors."""
        result = HandlerResult()

        result.recommended_strategies = [
            "add_error_handling",
            "add_logging",
        ]

        result.context_additions = {
            "suggestion": "Check if package is installed and path is correct",
        }

        return result


class AssertionErrorHandler(ExceptionHandler):
    """Handler for AssertionError (usually in tests)."""

    handles = ["AssertionError"]

    def handle(self, context: HandlerContext) -> HandlerResult:
        """Handle assertion errors in tests."""
        result = HandlerResult()

        # Check if this is in a test file
        is_test = context.classification.framework == "pytest"

        if is_test:
            result.recommended_strategies = [
                "fix_expected_value",
                "fix_actual_value",
                "add_error_handling",
            ]
            result.constraints = [
                "Determine if test expectation or code is wrong",
                "Don't blindly update expected values",
            ]
        else:
            result.recommended_strategies = [
                "fix_actual_value",
                "add_error_handling",
            ]

        return result


# Registry of all handlers
HANDLERS: List[ExceptionHandler] = [
    KeyErrorHandler(),
    TypeErrorHandler(),
    AttributeErrorHandler(),
    ValueErrorHandler(),
    IndexErrorHandler(),
    ImportErrorHandler(),
    AssertionErrorHandler(),
]


def get_handler(exception_type: str) -> Optional[ExceptionHandler]:
    """Get the appropriate handler for an exception type.

    Args:
        exception_type: The exception type name

    Returns:
        Handler if one exists, None otherwise
    """
    for handler in HANDLERS:
        if handler.can_handle(exception_type):
            return handler
    return None


def handle_exception(context: HandlerContext) -> HandlerResult:
    """Handle an exception using the appropriate handler.

    Args:
        context: Handler context with exception details

    Returns:
        HandlerResult (empty if no handler found)
    """
    handler = get_handler(context.classification.exception_type)
    if handler:
        return handler.handle(context)
    return HandlerResult()


__all__ = [
    "ExceptionHandler",
    "HandlerContext",
    "HandlerResult",
    "KeyErrorHandler",
    "TypeErrorHandler",
    "AttributeErrorHandler",
    "ValueErrorHandler",
    "IndexErrorHandler",
    "ImportErrorHandler",
    "AssertionErrorHandler",
    "get_handler",
    "handle_exception",
    "HANDLERS",
]
