from __future__ import annotations

import json
import ssl
import socket
import xml.etree.ElementTree as ElementTree
from typing import Any

from .._compat import optional_import
from .base import ErrorInfo, default_message, safe_client_message

YAML = optional_import("yaml")


def _json_error_details(exc: json.JSONDecodeError) -> list[dict[str, Any]]:
    return [{"type": "invalid_json", "message": exc.msg, "lineno": exc.lineno, "colno": exc.colno, "pos": exc.pos}]


def describe_python_exception(exc: Exception, *, allow_long_message: bool) -> ErrorInfo | None:
    if isinstance(exc, FileNotFoundError):
        return ErrorInfo(404, "resource_not_found", default_message(404), error_type=exc.__class__.__name__, retryable=False)
    if isinstance(exc, FileExistsError):
        return ErrorInfo(409, "resource_exists", default_message(409), error_type=exc.__class__.__name__, retryable=False)
    if isinstance(exc, PermissionError):
        return ErrorInfo(403, "forbidden", default_message(403), error_type=exc.__class__.__name__, retryable=False)

    if isinstance(exc, json.JSONDecodeError):
        return ErrorInfo(400, "invalid_json", "The request body is not valid JSON.", _json_error_details(exc), exc.__class__.__name__, retryable=False)

    if YAML and isinstance(exc, getattr(YAML, "YAMLError", tuple())):
        details = None
        mark = getattr(exc, "problem_mark", None)
        if mark is not None:
            details = [{"type": "invalid_yaml", "line": getattr(mark, "line", None), "column": getattr(mark, "column", None)}]
        return ErrorInfo(400, "invalid_yaml", "The supplied YAML content is invalid.", details, exc.__class__.__name__, retryable=False)

    if isinstance(exc, ElementTree.ParseError):
        line, column = getattr(exc, "position", (None, None))
        details = [{"type": "invalid_xml", "line": line, "column": column}]
        return ErrorInfo(400, "invalid_xml", "The supplied XML content is invalid.", details, exc.__class__.__name__, retryable=False)

    if isinstance(exc, (TimeoutError, socket.timeout)):
        return ErrorInfo(504, "upstream_timeout", default_message(504), error_type=exc.__class__.__name__, retryable=True)

    if isinstance(exc, (ConnectionRefusedError, ConnectionResetError, ConnectionAbortedError, BrokenPipeError, BlockingIOError, InterruptedError, ConnectionError, socket.gaierror, socket.herror)):
        return ErrorInfo(503, "service_unavailable", default_message(503), error_type=exc.__class__.__name__, retryable=True)

    if isinstance(exc, ssl.SSLCertVerificationError):
        return ErrorInfo(502, "upstream_ssl_error", "A secure upstream connection failed.", error_type=exc.__class__.__name__, retryable=False)

    if isinstance(exc, ssl.SSLError):
        return ErrorInfo(503, "service_unavailable", default_message(503), error_type=exc.__class__.__name__, retryable=False)

    cancelled_error = getattr(__import__("asyncio"), "CancelledError")
    invalid_state_error = getattr(__import__("asyncio"), "InvalidStateError")
    incomplete_read_error = getattr(__import__("asyncio"), "IncompleteReadError")
    limit_overrun_error = getattr(__import__("asyncio"), "LimitOverrunError")

    if isinstance(exc, cancelled_error):
        raise exc

    if isinstance(exc, incomplete_read_error):
        details = [{"type": "incomplete_read", "expected": getattr(exc, "expected", None)}]
        return ErrorInfo(502, "upstream_bad_response", "The upstream stream ended unexpectedly.", details, exc.__class__.__name__, retryable=True)

    if isinstance(exc, limit_overrun_error):
        details = [{"type": "limit_overrun", "consumed": getattr(exc, "consumed", None)}]
        return ErrorInfo(422, "invalid_data", default_message(422), details, exc.__class__.__name__, retryable=False)

    if isinstance(exc, invalid_state_error):
        return ErrorInfo(500, "runtime_error", default_message(500), error_type=exc.__class__.__name__)

    if isinstance(exc, MemoryError):
        return ErrorInfo(503, "service_unavailable", default_message(503), error_type=exc.__class__.__name__, retryable=True)

    if isinstance(exc, (OverflowError, ZeroDivisionError, UnicodeDecodeError, UnicodeEncodeError, UnicodeTranslateError)):
        return ErrorInfo(422, "invalid_data", default_message(422), error_type=exc.__class__.__name__, retryable=False)

    if isinstance(exc, NotImplementedError):
        return ErrorInfo(501, "not_implemented", "This operation is not implemented.", error_type=exc.__class__.__name__, retryable=False)

    if isinstance(exc, (AttributeError, ImportError, ModuleNotFoundError, RuntimeError, RecursionError, AssertionError, SystemError, ReferenceError, SyntaxError)):
        return ErrorInfo(500, "internal_error", default_message(500), error_type=exc.__class__.__name__)

    if isinstance(exc, (ValueError, TypeError, KeyError, IndexError, EOFError, IsADirectoryError, NotADirectoryError)):
        return ErrorInfo(
            400,
            "bad_request",
            safe_client_message(exc, default_message(400), allow_long_message=allow_long_message),
            error_type=exc.__class__.__name__,
            retryable=False,
        )

    return None
