"""
Errors Module for Exosphere

This module defines custom exception types used throughout the
Exosphere application, along with presentation helpers that rewrite
inscrutable upstream error messages into something a human can act on.
"""

from cyclopts import CycloptsError, UnusedCliTokensError
from rich.box import ROUNDED
from rich.console import RenderableType
from rich.panel import Panel

# Standard authentication error message for better UX
# This is intended to be displayed whenever Paramiko raises
# PasswordRequiredException, which is nearly always, and has the
# most inscrutable error message known to man.
AUTH_FAILURE_MESSAGE = (
    "Auth Failure. "
    "Verify that keypair authentication is enabled on the server, "
    "that your agent is running with the correct keys loaded, "
    "and that your username is correct for the host."
)

# Sudo authentication failure message for better UX
# This is intended to be displayed whenever a sudo command fails due to
# a password prompt, or failure raised by Invoke's AuthFailure exception.
# This will generally come up when a user is not configured with passwordless
# sudo, but has a Sudo Policy of NOPASSWD or equivalent.
SUDO_AUTH_FAILURE_MESSAGE = (
    "Sudo failed: "
    "Ensure the user is configured with passwordless sudo. "
    "You can use 'exosphere sudo generate' to produce a sudoers snippet for this host. "
    "See: https://exosphere.readthedocs.io/en/stable/connections.html#id1"
)


def error_formatter(error: CycloptsError) -> RenderableType:
    """
    Runtime error formatter callable for the CLI

    Error display hook setup on the root App, inherited by all
    subcommands.

    It is intended to:

    - Reword certain error messages for better UX, especially those that
      are otherwise inscrutable dumps of internal state
    - Apply a consistent visual style to all errors, with the ability
      to change them in one central place, if necessary.

    As long as what it returns is a Rich Renderable, all is well.

    :param error: The CycloptsError to handle
    :return: a Renderable with the error object
    """
    message: RenderableType

    if isinstance(error, UnusedCliTokensError):
        tokens = ", ".join(error.unused_tokens or [])
        message = f"Unexpected argument(s): {tokens}. See --help for usage."
    else:
        message = error

    return Panel(
        message,
        title="Error",
        title_align="left",
        border_style="red",
        box=ROUNDED,
        expand=False,
    )


class DataRefreshError(Exception):
    """Exception raised for errors encountered during data refresh."""

    def __init__(self, message: str, stdout: str = "", stderr: str = "") -> None:
        self.stdout = stdout
        self.stderr = stderr
        super().__init__(message)

    def __str__(self) -> str:
        return str(self.args[-1]) if self.args else super().__str__()


class UnsupportedOSError(DataRefreshError):
    """Exception raised for unsupported operating systems."""

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


class OfflineHostError(DataRefreshError):
    """Exception raised for offline hosts."""

    def __init__(self, message: str = "Host is offline or unreachable") -> None:
        super().__init__(message)
