from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, Optional, Type


@dataclass
class ItsAIError(Exception):
    status_code: Optional[int]
    type: Optional[str]
    message: str
    payload: Optional[Dict[str, Any]] = None

    def __str__(self) -> str:  # pragma: no cover - trivial
        sc = f" status={self.status_code}" if self.status_code is not None else ""
        tp = f" type={self.type}" if self.type else ""
        return f"ItsAIError{sc}{tp}: {self.message}"


class NetworkError(ItsAIError):
    pass


class KeyNotExist(ItsAIError):
    pass


class ApiKeyDoesNotExist(ItsAIError):
    pass


class RichLimit(ItsAIError):
    pass


class LowWords(ItsAIError):
    pass


class ManyWords(ItsAIError):
    pass


class RateLimitExceeded(ItsAIError):
    pass


class BatchLimitExceed(ItsAIError):
    pass


class BatchLimit(ItsAIError):
    pass


class OnlyEnglish(ItsAIError):
    pass


class Blacklist(ItsAIError):
    pass


class ValidationError(ItsAIError):
    pass


class NotFound(ItsAIError):
    pass


class PermissionDenied(ItsAIError):
    pass


class NotAcceptable(ItsAIError):
    pass


class AuthenticationFailed(ItsAIError):
    pass


class ServerError(ItsAIError):
    pass


class ServiceUnavailable(ItsAIError):
    pass


class RequestTimeout(ItsAIError):
    pass


class NoResultFound(ItsAIError):
    pass


class AnalysisNotFound(ItsAIError):
    pass


class PlagiarismCheckNotFound(ItsAIError):
    pass


class AnalysisIdRequired(ItsAIError):
    pass


class ExecutionLimitExceeded(ItsAIError):
    pass


class InvalidSignature(ItsAIError):
    pass


class EventNameRequired(ItsAIError):
    pass


TYPE_TO_ERROR: Dict[str, Type[ItsAIError]] = {
    "key_not_exist": KeyNotExist,
    "api_key_does_not_exist": ApiKeyDoesNotExist,
    "rich_limit": RichLimit,
    "low_words": LowWords,
    "many_words": ManyWords,
    "rate_limit": RateLimitExceeded,
    "batch_limit_exceed": BatchLimitExceed,
    "batch_limit": BatchLimit,
    "only_english": OnlyEnglish,
    "blacklist": Blacklist,
    "validation": ValidationError,
    "not_found": NotFound,
    "permission_denied": PermissionDenied,
    "not_acceptable": NotAcceptable,
    "authentication_failed": AuthenticationFailed,
    "server_error": ServerError,
    "service_unavailable": ServiceUnavailable,
    "request_timeout": RequestTimeout,
    "no_result_found": NoResultFound,
    "analysis_not_found": AnalysisNotFound,
    "plagiarism_check_not_found": PlagiarismCheckNotFound,
    "analysis_id_required": AnalysisIdRequired,
    "execution_limit_exceeded": ExecutionLimitExceeded,
    "invalid_signature": InvalidSignature,
    "event_name_required": EventNameRequired,
}


def error_from_type(
    error_type: Optional[str],
    status_code: Optional[int],
    message: str,
    payload: Optional[Dict[str, Any]] = None,
) -> ItsAIError:
    exc_cls = TYPE_TO_ERROR.get(error_type or "")
    if exc_cls is None:
        return ItsAIError(status_code=status_code, type=error_type, message=message, payload=payload)
    return exc_cls(status_code=status_code, type=error_type, message=message, payload=payload)


def build_error_from_http(
    status_code: int,
    body_text: Optional[str],
    json_body: Optional[Dict[str, Any]],
) -> ItsAIError:
    if json_body and isinstance(json_body, dict):
        error_type = json_body.get("type")
        message = str(json_body.get("error") or json_body.get("message") or "API error")
        return error_from_type(error_type, status_code, message, payload=json_body)

    # Non-JSON or unexpected
    if body_text is None or not body_text.strip():
        message = "Non-JSON error response"
    else:
        message = body_text.strip()

    # Map by status as fallback
    if status_code == 400:
        return ValidationError(status_code=status_code, type="validation", message=message)
    if status_code == 401:
        return AuthenticationFailed(status_code=status_code, type="authentication_failed", message=message)
    if status_code == 403:
        return PermissionDenied(status_code=status_code, type="permission_denied", message=message)
    if status_code == 404:
        return NotFound(status_code=status_code, type="not_found", message=message)
    if status_code == 406:
        return NotAcceptable(status_code=status_code, type="not_acceptable", message=message)
    if status_code == 408:
        return RequestTimeout(status_code=status_code, type="request_timeout", message=message)
    if status_code == 429:
        return RateLimitExceeded(status_code=status_code, type="rate_limit", message=message)
    if 500 <= status_code < 600:
        if status_code in (503, 504):
            return ServiceUnavailable(status_code=status_code, type="service_unavailable", message=message)
        return ServerError(status_code=status_code, type="server_error", message=message)

    return ItsAIError(status_code=status_code, type=None, message=message)


