use std::fmt;

use crate::netstack::Error as NetstackError;

/// Errors that may occur while interacting with a device.
#[derive(Debug, thiserror::Error, Clone, Copy, Eq, PartialEq)]
pub enum Error {
    /// An operation timed-out.
    ///
    /// This error can often be handled by retrying.
    #[error("operation timed-out")]
    Timeout,

    /// A connection was reset.
    ///
    /// This error can often be handled by retrying.
    #[error("connection reset")]
    ConnectionReset,

    /// An error reading or parsing the key file.
    #[error("an error reading or parsing the key file")]
    KeyFileRead,

    /// An error writing out the key file.
    #[error("an error writing out the key file")]
    KeyFileWrite,

    /// The environment variable `TS_RS_EXPERIMENT` was not set.
    ///
    /// The end-user must set `TS_RS_EXPERIMENT=this_is_unstable_software` to acknowledge that tailscale-rs
    /// is early-days experimental software containing bugs, unvalidated cryptography, and no stability
    /// or compatibility guarantees.
    #[error("the environment variable `{}` was not set", crate::ENV_MAGIC_VAR)]
    UnstableEnvVar,

    /// An error occurred which can not be anticipated or handled by a library user.
    ///
    /// This is likely due to a bug in our code or a rare and unexpected error.
    ///
    /// [`InternalErrorKind`] is intended to be informational (might be used to improve error reporting
    /// in logs or to the end-user), rather then inspected during handling.
    #[error("internal error ({0})")]
    Internal(InternalErrorKind),
}

/// Informational detail on the kind of internal error.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum InternalErrorKind {
    /// Invalid socket state.
    InvalidSocketState,
    /// Response type mismatched to request type.
    InternalResponseMismatch,
    /// Channel closed.
    InternalChannelClosed,
    /// Handle to invalid TCP listener.
    BadListenerHandle,
    /// Handle to invalid socket.
    BadSocketHandle,
    /// Bad request.
    BadRequest,
    /// Buffer is full, cannot read in packet.
    BufferFull,
    /// Actor missing or shutdown.
    Actor,
    /// The operation is not supported while running in TUN transport mode, or
    /// TUN mode was requested but is unavailable (no device, or the `tun`
    /// feature is disabled in this build).
    UnsupportedInTunMode,
    /// The requested resource (e.g. a Taildrop file) does not exist.
    NotFound,
    /// The resource already exists (e.g. a Taildrop transfer for the same file is in progress).
    AlreadyExists,
    /// An underlying I/O error (e.g. a Taildrop filesystem operation failed).
    Io,
}

impl fmt::Display for InternalErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            InternalErrorKind::InvalidSocketState => write!(f, "invalid socket state"),
            InternalErrorKind::InternalResponseMismatch => {
                write!(f, "response type mismatched to request type")
            }
            InternalErrorKind::InternalChannelClosed => write!(f, "channel closed"),
            InternalErrorKind::BadListenerHandle => write!(f, "handle to invalid TCP listener"),
            InternalErrorKind::BadSocketHandle => write!(f, "handle to invalid socket"),
            InternalErrorKind::BadRequest => write!(f, "bad request"),
            InternalErrorKind::BufferFull => write!(f, "buffer full"),
            InternalErrorKind::Actor => write!(f, "actor missing or shutdown"),
            InternalErrorKind::UnsupportedInTunMode => {
                write!(f, "operation unsupported in TUN transport mode")
            }
            InternalErrorKind::NotFound => write!(f, "resource not found"),
            InternalErrorKind::AlreadyExists => write!(f, "resource already exists"),
            InternalErrorKind::Io => write!(f, "I/O error"),
        }
    }
}

impl From<crate::netstack::InternalErrorKind> for InternalErrorKind {
    fn from(e: crate::netstack::InternalErrorKind) -> Self {
        match e {
            crate::netstack::InternalErrorKind::InvalidSocketState => {
                InternalErrorKind::InvalidSocketState
            }
            crate::netstack::InternalErrorKind::InternalResponseMismatch => {
                InternalErrorKind::InternalResponseMismatch
            }
            crate::netstack::InternalErrorKind::InternalChannelClosed => {
                InternalErrorKind::InternalChannelClosed
            }
            crate::netstack::InternalErrorKind::BadListenerHandle => {
                InternalErrorKind::BadListenerHandle
            }
            crate::netstack::InternalErrorKind::BadSocketHandle => {
                InternalErrorKind::BadSocketHandle
            }
            crate::netstack::InternalErrorKind::BufferFull => InternalErrorKind::BufferFull,
            _ => unreachable!(),
        }
    }
}

impl From<ts_runtime::Error> for Error {
    fn from(value: ts_runtime::Error) -> Self {
        match value.kind {
            ts_runtime::ErrorKind::Timeout => Error::Timeout,
            ts_runtime::ErrorKind::ActorGone
            | ts_runtime::ErrorKind::MailboxFull
            | ts_runtime::ErrorKind::ReplyErr => Error::Internal(InternalErrorKind::Actor),
            // TUN transport mode: a netstack-only operation, or TUN requested but unavailable
            // (no device / `tun` feature off).
            ts_runtime::ErrorKind::UnsupportedInTunMode | ts_runtime::ErrorKind::TunUnavailable => {
                Error::Internal(InternalErrorKind::UnsupportedInTunMode)
            }
        }
    }
}

impl From<NetstackError> for Error {
    fn from(value: NetstackError) -> Self {
        match value {
            NetstackError::Internal(k) => Error::Internal(k.into()),
            NetstackError::ConnectionReset => Error::ConnectionReset,
            NetstackError::BadRequest(_) => Error::Internal(InternalErrorKind::BadRequest),
        }
    }
}
