"""Client-level exception taxonomy.

These exceptions represent stable, backend-agnostic failure meanings surfaced by the SDK.
Gateways and service layers should raise these (or subclasses)
so callers can handle errors consistently across:
- Snowflake SQL (Snowpark)
- Direct Access HTTP
"""

from __future__ import annotations


class ClientError(Exception):
    """Base error for client execution."""


class ClientAuthError(ClientError):
    pass


class ClientNotFoundError(ClientError):
    pass


class ClientNotReadyError(ClientError):
    pass


class ClientConflictError(ClientError):
    pass


class ClientInvalidRequestError(ClientError):
    pass


class ClientSqlCompilationError(ClientInvalidRequestError):
    """SQL compilation failed (syntax error, invalid SQL, etc.)."""

    def __init__(self, *, operation: str, statement: str, cause: Exception | None = None):
        self.operation = operation
        self.statement = statement
        self.cause = cause

        stmt = (statement or "").strip()
        if len(stmt) > 500:
            stmt = stmt[:500] + " ... <truncated>"

        super().__init__(
            "\n".join(
                [
                    "SQL compilation failed.",
                    "",
                    f"operation: {operation}",
                    f"statement: {stmt}",
                    "",
                    "Original error:",
                    f"{cause}" if cause else "<unknown>",
                ]
            )
        )


class ClientTimeoutError(ClientError):
    pass


class ClientServerError(ClientError):
    pass


class ClientUnknownError(ClientError):
    pass


# -----------------------------------------------------------------------------
# Snowflake-native-app / operational exceptions (generic; used by multiple services)
# -----------------------------------------------------------------------------


class DuoSecurityFailed(ClientAuthError):
    """Connection failed due to Duo security."""

    def __init__(self, cause: Exception | None = None):
        self.cause = cause
        msg = "\n".join(
            [
                "Authentication failed due to Duo Security.",
                "",
                "What to check:",
                "- If you're using Duo Push, approve the login request.",
                "- If you're using a browser-based flow/SSO, complete the interactive step.",
                "- Confirm your network/VPN requirements for your Snowflake account.",
            ]
            + (["", f"Original error: {cause}"] if cause else [])
        )
        super().__init__(msg)


class SnowflakeDatabaseException(ClientServerError):
    """Generic Snowflake database error wrapper."""

    def __init__(self, cause: Exception | None = None):
        self.cause = cause
        msg = "\n".join(
            [
                "Snowflake returned a database error while executing a request.",
                "",
                "Original error:",
                f"{cause}" if cause else "<unknown>",
            ]
        )
        super().__init__(msg)


class SnowflakeConnectionFailed(ClientAuthError):
    """Snowflake connection/auth failed before we could run any SQL (network/SSO/timeout/retry exhaustion)."""

    def __init__(
        self,
        *,
        account: str | None = None,
        user: str | None = None,
        authenticator: str | None = None,
        cause: Exception | None = None,
    ):
        self.account = account
        self.user = user
        self.authenticator = authenticator
        self.cause = cause

        details: list[str] = []
        if account:
            details.append(f"account: {account}")
        if user:
            details.append(f"user: {user}")
        if authenticator:
            details.append(f"authenticator: {authenticator}")

        super().__init__(
            "\n".join(
                [
                    "Failed to connect/authenticate to Snowflake.",
                    "",
                    "What happened:",
                    "- Snowflake login/connection retries were exhausted (or timed out) before a session could be created.",
                    *(["", "Connection details:"] + details if details else []),
                    "",
                    "Common fixes:",
                    "- Check your network/VPN/proxy settings and that you can reach your Snowflake account URL.",
                    "- If using SSO / external browser auth, complete the interactive login step.",
                    "- If using Duo/MFA, approve the prompt.",
                    "- Verify the account/region/host and credentials in your config.",
                    "",
                    "Original error:",
                    f"{cause}" if cause else "<unknown>",
                ]
            )
        )


class SnowflakeAppMissingException(ClientAuthError):
    """The RelationalAI native app isn't installed/authorized under the configured name."""

    def __init__(self, app_name: str, role: str | None = None):
        self.app_name = app_name
        self.role = role
        role_hint = f" (current role: {role})" if role else ""
        super().__init__(
            "\n".join(
                [
                    f"RelationalAI app database '{app_name}' was not found or is not authorized{role_hint}.",
                    "",
                    "Most common causes:",
                    "- The native app isn't installed in this Snowflake account.",
                    "- Your Snowflake role doesn't have USAGE privileges on the app database.",
                    "- Your config points to the wrong app name (the connection `database` is used as the app name).",
                    "",
                    "How to verify in Snowflake:",
                    f"- Run: show databases like '{app_name}';",
                    "- Confirm you're using the intended account/region and role:",
                    "  select current_account(), current_region(), current_role();",
                    "",
                    "How to fix (ask your admin if needed):",
                    f"- Install/enable the RelationalAI native app named '{app_name}'.",
                    f"- Grant access to your role: grant usage on database {app_name} to role <YOUR_ROLE>;",
                ]
            )
        )


class SnowflakeRaiAppNotStarted(ClientNotReadyError):
    """The RelationalAI native app exists, but its service isn't started/active."""

    def __init__(self, app_name: str):
        self.app_name = app_name
        super().__init__(
            "\n".join(
                [
                    f"RelationalAI app '{app_name}' is installed but not activated (service not started).",
                    "",
                    "Activate it by running the following SQL:",
                    "",
                    f"call {app_name}.app.activate();",
                    "",
                    "If this still fails, verify the app/service state and permissions for your role.",
                ]
            )
        )


class AbortedTransactionError(ClientServerError):
    """Transaction aborted error with optional problem details."""

    def __init__(self, type_field: str | None, message_field: str | None, report_field: str | None):
        self.type_field = type_field
        self.message_field = message_field
        self.report_field = report_field
        parts: list[str] = []
        if type_field:
            parts.append(f"type: {type_field}")
        if message_field:
            parts.append(f"message: {message_field}")
        if report_field:
            parts.append(f"report: {report_field}")

        super().__init__(
            "\n".join(
                [
                    "Transaction aborted while executing a request.",
                    "",
                    "Details:",
                    *(parts if parts else ["<none>"]),
                ]
            )
        )

