"""Exception registry using existing exception hierarchies.

Uses Python's built-in exception hierarchy + domain-specific libraries
instead of creating 150+ custom exception types.
"""

import builtins
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Set, Type


@dataclass
class ExceptionMetadata:
    """Metadata for exception categorization (not a new exception class)."""

    category: str  # "io", "data", "auth", "validation", "network", "database"
    tags: Set[str] = field(default_factory=set)
    root_causes: List[str] = field(default_factory=list)
    fix_strategies: List[str] = field(default_factory=list)
    severity: int = 3  # 1=critical, 5=minor
    is_recoverable: bool = True
    context_needed: List[str] = field(default_factory=list)


class ExceptionRegistry:
    """Registry using existing exception hierarchies + metadata tagging."""

    def __init__(self):
        # Python built-in exceptions (already organized by PEP 3151)
        self.builtins = self._load_builtins()

        # Domain-specific exceptions from libraries
        self.httpx_exceptions = self._load_httpx_exceptions()
        self.pydantic_exceptions = self._load_pydantic_exceptions()
        self.sqlalchemy_exceptions = self._load_sqlalchemy_exceptions()
        self.django_exceptions = self._load_django_exceptions()
        self.requests_exceptions = self._load_requests_exceptions()
        self.asyncio_exceptions = self._load_asyncio_exceptions()

        # Metadata for categorization
        self.metadata: Dict[str, ExceptionMetadata] = self._build_metadata()

    def _load_builtins(self) -> Dict[str, Type[BaseException]]:
        """Load all Python built-in exceptions."""
        return {
            name: obj
            for name, obj in vars(builtins).items()
            if isinstance(obj, type) and issubclass(obj, BaseException)
        }

    def _load_httpx_exceptions(self) -> Dict[str, type]:
        """Load httpx exception hierarchy (well-structured for HTTP)."""
        try:
            import httpx

            return {
                "TimeoutException": httpx.TimeoutException,
                "ConnectTimeout": httpx.ConnectTimeout,
                "ReadTimeout": httpx.ReadTimeout,
                "WriteTimeout": httpx.WriteTimeout,
                "PoolTimeout": httpx.PoolTimeout,
                "HTTPStatusError": httpx.HTTPStatusError,
                "RequestError": httpx.RequestError,
                "ConnectError": httpx.ConnectError,
                "TooManyRedirects": httpx.TooManyRedirects,
            }
        except ImportError:
            return {}

    def _load_requests_exceptions(self) -> Dict[str, type]:
        """Load requests library exceptions."""
        try:
            from requests import exceptions as req_exc

            return {
                "RequestException": req_exc.RequestException,
                "ConnectionError": req_exc.ConnectionError,
                "HTTPError": req_exc.HTTPError,
                "Timeout": req_exc.Timeout,
                "TooManyRedirects": req_exc.TooManyRedirects,
                "JSONDecodeError": req_exc.JSONDecodeError,
            }
        except ImportError:
            return {}

    def _load_pydantic_exceptions(self) -> Dict[str, type]:
        """Load Pydantic validation errors (30+ error types)."""
        try:
            from pydantic import ValidationError

            return {"ValidationError": ValidationError}
        except ImportError:
            return {}

    def _load_sqlalchemy_exceptions(self) -> Dict[str, type]:
        """Load SQLAlchemy exceptions (PEP 249 compliant)."""
        try:
            from sqlalchemy import exc

            return {
                "IntegrityError": exc.IntegrityError,
                "OperationalError": exc.OperationalError,
                "ProgrammingError": exc.ProgrammingError,
                "DataError": exc.DataError,
                "InterfaceError": exc.InterfaceError,
                "DatabaseError": exc.DatabaseError,
                "InternalError": exc.InternalError,
                "NotSupportedError": exc.NotSupportedError,
                "NoResultFound": exc.NoResultFound,
                "MultipleResultsFound": exc.MultipleResultsFound,
            }
        except ImportError:
            return {}

    def _load_django_exceptions(self) -> Dict[str, type]:
        """Load Django exceptions if available."""
        try:
            from django.core.exceptions import (
                MultipleObjectsReturned,
                ObjectDoesNotExist,
                PermissionDenied,
                ValidationError,
            )

            return {
                "ValidationError": ValidationError,
                "ObjectDoesNotExist": ObjectDoesNotExist,
                "MultipleObjectsReturned": MultipleObjectsReturned,
                "PermissionDenied": PermissionDenied,
            }
        except ImportError:
            return {}

    def _load_asyncio_exceptions(self) -> Dict[str, type]:
        """Load asyncio exceptions."""
        import asyncio

        return {
            "TimeoutError": asyncio.TimeoutError,
            "CancelledError": asyncio.CancelledError,
            "InvalidStateError": asyncio.InvalidStateError,
        }

    def _build_metadata(self) -> Dict[str, ExceptionMetadata]:
        """Build metadata tags for exception categories."""
        return {
            # IO Errors (OSError hierarchy from PEP 3151)
            "FileNotFoundError": ExceptionMetadata(
                category="io",
                tags={"file", "missing"},
                root_causes=["file_deleted", "wrong_path", "permission"],
                fix_strategies=["check_path_exists", "create_directory", "fix_path"],
                context_needed=["file_path", "working_directory"],
                severity=3,
            ),
            "PermissionError": ExceptionMetadata(
                category="io",
                tags={"file", "permission"},
                root_causes=["insufficient_permissions", "file_locked", "readonly"],
                fix_strategies=["check_permissions", "run_as_admin", "use_temp_file"],
                context_needed=["file_path", "user_permissions"],
                severity=3,
            ),
            "IsADirectoryError": ExceptionMetadata(
                category="io",
                tags={"file", "directory"},
                root_causes=["expected_file_got_directory"],
                fix_strategies=["check_is_file", "fix_path"],
                context_needed=["path"],
                severity=3,
            ),
            "NotADirectoryError": ExceptionMetadata(
                category="io",
                tags={"file", "directory"},
                root_causes=["expected_directory_got_file"],
                fix_strategies=["check_is_directory", "fix_path"],
                context_needed=["path"],
                severity=3,
            ),
            "FileExistsError": ExceptionMetadata(
                category="io",
                tags={"file", "exists"},
                root_causes=["file_already_exists"],
                fix_strategies=["use_exist_ok", "remove_first", "use_unique_name"],
                context_needed=["file_path"],
                severity=4,
            ),
            # Network Errors
            "ConnectionError": ExceptionMetadata(
                category="network",
                tags={"connection", "transient"},
                root_causes=["network_down", "server_unavailable", "dns_failure"],
                fix_strategies=["add_retry_logic", "check_network", "validate_host"],
                context_needed=["host", "port"],
                severity=2,
                is_recoverable=True,
            ),
            "TimeoutError": ExceptionMetadata(
                category="network",
                tags={"timeout", "transient"},
                root_causes=["slow_server", "network_latency", "timeout_too_short"],
                fix_strategies=["increase_timeout", "add_retry_logic", "check_server"],
                context_needed=["timeout_value", "operation"],
                severity=2,
                is_recoverable=True,
            ),
            "ConnectionRefusedError": ExceptionMetadata(
                category="network",
                tags={"connection", "refused"},
                root_causes=["service_not_running", "wrong_port", "firewall"],
                fix_strategies=["start_service", "check_port", "check_firewall"],
                context_needed=["host", "port"],
                severity=2,
            ),
            "ConnectionResetError": ExceptionMetadata(
                category="network",
                tags={"connection", "reset"},
                root_causes=["server_closed_connection", "network_issue"],
                fix_strategies=["add_retry_logic", "check_server_logs"],
                context_needed=["host", "port"],
                severity=2,
                is_recoverable=True,
            ),
            # Data Errors
            "KeyError": ExceptionMetadata(
                category="data",
                tags={"dict", "missing_key"},
                root_causes=[
                    "api_response_changed",
                    "config_missing",
                    "typo",
                    "optional_field",
                ],
                fix_strategies=[
                    "use_get_default",
                    "validate_keys_early",
                    "fix_key_name",
                ],
                context_needed=["dict_source", "expected_key", "available_keys"],
                severity=3,
            ),
            "TypeError": ExceptionMetadata(
                category="data",
                tags={"type", "mismatch"},
                root_causes=[
                    "none_value",
                    "wrong_type_passed",
                    "api_returned_different_type",
                ],
                fix_strategies=[
                    "add_type_check",
                    "add_none_check",
                    "fix_type_conversion",
                ],
                context_needed=["expected_type", "actual_type", "function_signature"],
                severity=3,
            ),
            "ValueError": ExceptionMetadata(
                category="data",
                tags={"validation", "invalid"},
                root_causes=["invalid_input", "out_of_range", "format_error"],
                fix_strategies=[
                    "add_validation",
                    "fix_type_conversion",
                    "sanitize_input",
                ],
                context_needed=["expected_value", "actual_value"],
                severity=3,
            ),
            "AttributeError": ExceptionMetadata(
                category="data",
                tags={"attribute", "none"},
                root_causes=["none_object", "typo_attribute", "missing_method"],
                fix_strategies=[
                    "add_none_check",
                    "fix_attribute_name",
                    "check_type",
                ],
                context_needed=["object_type", "missing_attr", "similar_attrs"],
                severity=3,
            ),
            "IndexError": ExceptionMetadata(
                category="data",
                tags={"list", "index"},
                root_causes=["empty_list", "wrong_index", "off_by_one"],
                fix_strategies=[
                    "add_bounds_check",
                    "fix_index_calculation",
                    "handle_empty_list",
                ],
                context_needed=["list_length", "requested_index"],
                severity=3,
            ),
            "NameError": ExceptionMetadata(
                category="code",
                tags={"undefined", "variable"},
                root_causes=["typo", "missing_import", "scope_issue"],
                fix_strategies=["fix_typo", "add_import", "define_variable"],
                context_needed=["undefined_name", "similar_names"],
                severity=3,
            ),
            "ImportError": ExceptionMetadata(
                category="code",
                tags={"import", "module"},
                root_causes=["missing_package", "wrong_path", "typo"],
                fix_strategies=["install_package", "fix_import_path", "fix_typo"],
                context_needed=["module_name", "available_modules"],
                severity=3,
            ),
            "ModuleNotFoundError": ExceptionMetadata(
                category="code",
                tags={"import", "module", "missing"},
                root_causes=["missing_package", "wrong_package_name", "not_installed"],
                fix_strategies=["install_package", "fix_package_name", "add_to_requirements"],
                context_needed=["module_name"],
                severity=3,
            ),
            # Assertion and Test Errors
            "AssertionError": ExceptionMetadata(
                category="assertion",
                tags={"test", "assertion"},
                root_causes=["wrong_value", "logic_error", "test_assumption_invalid"],
                fix_strategies=["fix_value_source", "adjust_assertion", "fix_logic"],
                context_needed=["assertion_line", "compared_values"],
                severity=3,
            ),
            # HTTP Errors (httpx/requests)
            "HTTPStatusError": ExceptionMetadata(
                category="http",
                tags={"api", "status_code"},
                root_causes=["api_error", "auth_failure", "not_found", "rate_limit"],
                fix_strategies=[
                    "handle_http_error",
                    "fix_auth_header",
                    "add_retry_logic",
                ],
                context_needed=["endpoint", "status_code", "response_body"],
                severity=2,
            ),
            "ConnectTimeout": ExceptionMetadata(
                category="http",
                tags={"timeout", "connection"},
                root_causes=["server_unreachable", "network_slow", "firewall"],
                fix_strategies=["increase_timeout", "add_retry", "check_url"],
                context_needed=["url", "timeout_value"],
                severity=2,
                is_recoverable=True,
            ),
            "ReadTimeout": ExceptionMetadata(
                category="http",
                tags={"timeout", "read"},
                root_causes=["slow_response", "large_payload", "server_busy"],
                fix_strategies=["increase_timeout", "add_retry", "paginate_request"],
                context_needed=["url", "timeout_value"],
                severity=2,
                is_recoverable=True,
            ),
            # Database Errors (SQLAlchemy / PEP 249)
            "IntegrityError": ExceptionMetadata(
                category="database",
                tags={"constraint", "duplicate"},
                root_causes=["unique_violation", "foreign_key_violation", "check_constraint"],
                fix_strategies=["fix_sql_constraint", "use_upsert", "validate_data"],
                context_needed=["query", "constraint_name", "table_name"],
                severity=2,
            ),
            "OperationalError": ExceptionMetadata(
                category="database",
                tags={"connection", "operational"},
                root_causes=["connection_lost", "deadlock", "database_locked"],
                fix_strategies=["add_retry", "fix_connection", "check_database"],
                context_needed=["query", "database_url"],
                severity=2,
                is_recoverable=True,
            ),
            "ProgrammingError": ExceptionMetadata(
                category="database",
                tags={"syntax", "programming"},
                root_causes=["invalid_sql", "wrong_column", "missing_table"],
                fix_strategies=["fix_sql_syntax", "check_schema", "run_migrations"],
                context_needed=["query", "table_schema"],
                severity=3,
            ),
            "DataError": ExceptionMetadata(
                category="database",
                tags={"data", "invalid"},
                root_causes=["data_too_long", "invalid_format", "numeric_overflow"],
                fix_strategies=["validate_data", "truncate_data", "fix_data_type"],
                context_needed=["query", "data_value"],
                severity=3,
            ),
            "NoResultFound": ExceptionMetadata(
                category="database",
                tags={"query", "empty"},
                root_causes=["no_matching_record", "wrong_filter", "deleted_record"],
                fix_strategies=["use_first_or_none", "check_filter", "handle_not_found"],
                context_needed=["query", "filter_params"],
                severity=4,
            ),
            # Validation Errors (Pydantic)
            "ValidationError": ExceptionMetadata(
                category="validation",
                tags={"pydantic", "schema"},
                root_causes=["invalid_field", "missing_required", "type_mismatch"],
                fix_strategies=["fix_input_data", "make_field_optional", "add_validator"],
                context_needed=["field_errors", "input_data"],
                severity=3,
            ),
            # JSON Errors
            "JSONDecodeError": ExceptionMetadata(
                category="data",
                tags={"json", "parsing"},
                root_causes=["invalid_json", "empty_response", "html_instead_of_json"],
                fix_strategies=["validate_json", "handle_empty_response", "check_content_type"],
                context_needed=["raw_input", "position"],
                severity=3,
            ),
            # Async Errors
            "CancelledError": ExceptionMetadata(
                category="async",
                tags={"cancelled", "task"},
                root_causes=["task_cancelled", "timeout", "shutdown"],
                fix_strategies=["handle_cancellation", "add_cleanup"],
                context_needed=["task_name"],
                severity=4,
                is_recoverable=False,
            ),
            # Common built-in exceptions (filling coverage gaps)
            "OSError": ExceptionMetadata(
                category="io",
                tags={"os", "system"},
                root_causes=["disk_full", "too_many_open_files", "device_error"],
                fix_strategies=["check_disk_space", "close_file_handles", "add_retry"],
                context_needed=["errno", "filename"],
                severity=3,
            ),
            "RuntimeError": ExceptionMetadata(
                category="runtime",
                tags={"runtime", "logic"},
                root_causes=["logic_error", "invalid_state", "generator_misuse"],
                fix_strategies=["fix_logic", "check_state", "fix_generator"],
                context_needed=["operation", "state"],
                severity=3,
            ),
            "RecursionError": ExceptionMetadata(
                category="runtime",
                tags={"recursion", "stack_overflow"},
                root_causes=["infinite_recursion", "missing_base_case", "deep_nesting"],
                fix_strategies=["add_base_case", "use_iteration", "increase_recursion_limit"],
                context_needed=["function_name", "recursion_depth"],
                severity=2,
                is_recoverable=False,
            ),
            "UnicodeError": ExceptionMetadata(
                category="data",
                tags={"encoding", "unicode"},
                root_causes=["wrong_encoding", "binary_data_as_text", "invalid_bytes"],
                fix_strategies=["specify_encoding", "use_errors_replace", "detect_encoding"],
                context_needed=["encoding", "position"],
                severity=3,
            ),
            "UnicodeDecodeError": ExceptionMetadata(
                category="data",
                tags={"encoding", "decode"},
                root_causes=["wrong_encoding", "binary_data_as_text", "mixed_encodings"],
                fix_strategies=["specify_encoding", "use_errors_replace", "read_as_binary"],
                context_needed=["encoding", "position", "byte_value"],
                severity=3,
            ),
            "UnicodeEncodeError": ExceptionMetadata(
                category="data",
                tags={"encoding", "encode"},
                root_causes=["unsupported_character", "ascii_only_context"],
                fix_strategies=["use_utf8", "use_errors_replace", "strip_non_ascii"],
                context_needed=["encoding", "character"],
                severity=3,
            ),
            "SyntaxError": ExceptionMetadata(
                category="code",
                tags={"syntax", "parsing"},
                root_causes=["invalid_syntax", "missing_bracket", "wrong_indentation"],
                fix_strategies=["fix_syntax", "check_brackets", "fix_indentation"],
                context_needed=["filename", "line_number", "offset"],
                severity=2,
                is_recoverable=False,
            ),
            "ZeroDivisionError": ExceptionMetadata(
                category="data",
                tags={"math", "division"},
                root_causes=["denominator_zero", "missing_guard", "empty_aggregation"],
                fix_strategies=["add_zero_check", "use_default_value", "validate_input"],
                context_needed=["expression", "denominator_source"],
                severity=3,
            ),
            "MemoryError": ExceptionMetadata(
                category="resource",
                tags={"memory", "oom"},
                root_causes=["large_data_in_memory", "memory_leak", "unbounded_collection"],
                fix_strategies=["use_streaming", "add_pagination", "reduce_batch_size"],
                context_needed=["memory_usage", "data_size"],
                severity=1,
                is_recoverable=False,
            ),
            "StopIteration": ExceptionMetadata(
                category="data",
                tags={"iterator", "exhausted"},
                root_causes=["iterator_exhausted", "empty_sequence", "next_on_empty"],
                fix_strategies=["use_default_with_next", "check_empty_first", "use_for_loop"],
                context_needed=["iterator_source"],
                severity=4,
            ),
            "OverflowError": ExceptionMetadata(
                category="data",
                tags={"math", "overflow"},
                root_causes=["number_too_large", "exponent_too_high"],
                fix_strategies=["add_bounds_check", "use_decimal", "cap_value"],
                context_needed=["expression", "value"],
                severity=3,
            ),
            "NotImplementedError": ExceptionMetadata(
                category="code",
                tags={"abstract", "unimplemented"},
                root_causes=["abstract_method_not_overridden", "feature_not_implemented"],
                fix_strategies=["implement_method", "use_correct_subclass"],
                context_needed=["class_name", "method_name"],
                severity=3,
                is_recoverable=False,
            ),
        }

    def get_metadata(self, exception_type: str) -> ExceptionMetadata:
        """Get metadata for an exception type, with intelligent fallback."""
        # Direct match
        if exception_type in self.metadata:
            return self.metadata[exception_type]

        # Check for suffix match (e.g., "requests.exceptions.ConnectionError")
        for known_type, metadata in self.metadata.items():
            if exception_type.endswith(known_type):
                return metadata

        # Check parent classes using the loaded exceptions
        exc_class = self._find_exception_class(exception_type)
        if exc_class:
            for known_type, metadata in self.metadata.items():
                known_class = self._find_exception_class(known_type)
                if known_class and issubclass(exc_class, known_class):
                    return metadata

        # Default metadata
        return ExceptionMetadata(
            category="unknown",
            tags=set(),
            root_causes=["unknown"],
            fix_strategies=["add_error_handling", "investigate"],
            severity=3,
        )

    def _find_exception_class(self, name: str) -> Optional[type]:
        """Find exception class by name across all registries."""
        # Check builtins
        if name in self.builtins:
            return self.builtins[name]

        # Check domain exceptions
        for registry in [
            self.httpx_exceptions,
            self.pydantic_exceptions,
            self.sqlalchemy_exceptions,
            self.django_exceptions,
            self.requests_exceptions,
            self.asyncio_exceptions,
        ]:
            if name in registry:
                return registry[name]

        return None

    def get_all_categories(self) -> Set[str]:
        """Get all known categories."""
        return {m.category for m in self.metadata.values()}

    def get_exceptions_by_category(self, category: str) -> List[str]:
        """Get all exception types in a category."""
        return [
            exc_type
            for exc_type, metadata in self.metadata.items()
            if metadata.category == category
        ]

    def is_recoverable(self, exception_type: str) -> bool:
        """Check if an exception type is typically recoverable."""
        metadata = self.get_metadata(exception_type)
        return metadata.is_recoverable
