"""
Custom exceptions for Reno API errors.
"""

from typing import Optional


# Error codes that are safe to retry
_RETRYABLE_CODES = frozenset({
    3002,  # Model Not Loaded
    3003,  # Model Loading
    3004,  # Model at Capacity
    3007,  # Model Unavailable
    5001,  # Rate Limit Exceeded
    5004,  # Concurrent Limit
    5005,  # Rate Limit Unknown
    6001,  # Server Overloaded
    6002,  # Service Unavailable
    6003,  # Timeout
    6004,  # Database Error
    6005,  # Upstream Error
})

_FRIENDLY_MESSAGES: dict = {
    1001: ("Invalid API Key Format",   "Check that your key starts with 'reno_sk_'."),
    1002: ("Invalid API Key",          "Verify your API key in the dashboard."),
    1003: ("Missing API Key",          "Add an 'Authorization: Bearer <key>' header."),
    1004: ("Expired API Key",          "Generate a new API key."),
    1005: ("Revoked API Key",          "Create a new key; this one was revoked."),
    1006: ("Insufficient Permissions", "Check your subscription tier."),
    1007: ("IP Not Whitelisted",       "Add your IP to the whitelist."),
    1008: ("Account Suspended",        "Contact support."),
    2001: ("Malformed Request",        "Ensure your JSON is valid."),
    2002: ("Missing Model",            "Include a 'model' field."),
    2003: ("Missing Messages",         "Include a 'messages' array."),
    2004: ("Empty Message",            "Message content cannot be empty."),
    2005: ("Invalid Role",             "Role must be 'user', 'assistant', or 'system'."),
    2006: ("Too Many Messages",        "Reduce message history."),
    2007: ("Invalid Temperature",      "Temperature must be between 0 and 2."),
    2008: ("Invalid max_tokens",       "max_tokens must be a positive integer."),
    2009: ("Subscription Error",       "Check your subscription status."),
    3001: ("Model Not Found",          "Try a different model."),
    3002: ("Model Not Loaded",         "Wait a moment and retry."),
    3003: ("Model Loading",            "Please wait, model is loading."),
    3004: ("Model at Capacity",        "Try again later or switch models."),
    3005: ("Unsupported Parameter",    "This model doesn't support that parameter."),
    3006: ("Model Deprecated",         "Upgrade to a newer model."),
    3007: ("Model Unavailable",        "Try again later."),
    4001: ("Context Length Exceeded",  "Shorten your conversation."),
    4002: ("Max Tokens Limit",         "Reduce max_tokens or prompt length."),
    4003: ("Response Truncated",       "Increase max_tokens if needed."),
    4004: ("Prompt Too Long",          "Shorten your prompt."),
    4005: ("Token Estimation Failed",  "Try a simpler request."),
    5001: ("Rate Limit Exceeded",      "Slow down your request rate."),
    5002: ("Daily Quota Exceeded",     "Upgrade or wait until tomorrow."),
    5003: ("Monthly Quota Exceeded",   "Upgrade or wait until next month."),
    5004: ("Concurrent Limit",         "Wait for other requests to finish."),
    5005: ("Rate Limit Unknown",       "Try again later."),
    5006: ("Daily Token Limit",        "Upgrade or wait for reset."),
    6001: ("Server Overloaded",        "Please wait and retry."),
    6002: ("Service Unavailable",      "Check status page."),
    6003: ("Timeout",                  "Try a shorter prompt."),
    6004: ("Database Error",           "Retry; if persists contact support."),
    6005: ("Upstream Error",           "Try again later."),
    6006: ("Maintenance Mode",         "Scheduled maintenance - check status."),
    7001: ("Content Policy Violation", "Review content guidelines."),
    7002: ("NSFW Detected",            "NSFW content not allowed."),
    7003: ("Prompt Blocked",           "Moderation blocked your prompt."),
    7004: ("Response Filtered",        "Try rephrasing."),
    8001: ("Insufficient Credits",     "Purchase more credits."),
    8002: ("Payment Required",         "Update billing information."),
    8003: ("Trial Expired",            "Upgrade to a paid plan."),
    8004: ("Billing Error",            "Check payment details."),
    9001: ("Internal Error",           "Try again; contact support if persists."),
    9002: ("Not Implemented",          "Feature not available yet."),
    9003: ("Feature Disabled",         "Check documentation."),
    9004: ("Unexpected Error",         "Contact support."),
}


class RenoError(Exception):
    """
    Base exception for all Reno API errors.

    Attributes:
        code:       Reno error code (e.g. 1002, 5001).
        message:    Human-readable error description.
        details:    Optional extra context or suggestion from the server.
        retry_after: Seconds to wait before retrying (populated from
                     ``Retry-After`` header when available).
    """

    def __init__(
        self,
        code: int,
        message: str,
        details: Optional[str] = None,
        retry_after: Optional[float] = None,
    ) -> None:
        self.code = code
        self.message = message
        self.details = details
        self.retry_after = retry_after
        super().__init__(f"[{code}] {message}")

    # ------------------------------------------------------------------
    # Properties
    # ------------------------------------------------------------------

    @property
    def is_retryable(self) -> bool:
        """Return True if this error is considered safe to retry."""
        return self.code in _RETRYABLE_CODES

    # ------------------------------------------------------------------
    # Helpers
    # ------------------------------------------------------------------

    def user_friendly(self) -> str:
        """Return a formatted, user-friendly error message."""
        title, suggestion = _FRIENDLY_MESSAGES.get(
            self.code,
            ("Unknown Error", "No suggestion available."),
        )
        lines = [f"{title}: {self.message}", f"💡 {suggestion}"]
        if self.details:
            lines.append(f"   Details: {self.details}")
        if self.retry_after is not None:
            lines.append(f"   Retry after: {self.retry_after:.0f}s")
        return "\n".join(lines)

    def __repr__(self) -> str:  # pragma: no cover
        return (
            f"RenoError(code={self.code!r}, message={self.message!r}, "
            f"details={self.details!r})"
        )


class RenoConnectionError(RenoError):
    """Raised when the SDK cannot reach the Reno API at all."""

    def __init__(self, message: str = "Failed to connect to Reno API") -> None:
        super().__init__(code=6002, message=message)


class RenoTimeoutError(RenoError):
    """Raised when a request to the Reno API exceeds the configured timeout."""

    def __init__(self, message: str = "Request timed out") -> None:
        super().__init__(code=6003, message=message)


class RenoValidationError(RenoError):
    """Raised for client-side validation failures before a request is sent."""

    def __init__(self, message: str) -> None:
        super().__init__(code=2001, message=message)
