//! Error types for SSH operations.

use std::io;
use thiserror::Error;

/// Result type alias for SSH operations.
pub type Result<T> = std::result::Result<T, Error>;

/// Error types that can occur during SSH operations.
#[derive(Debug, Error)]
pub enum Error {
    /// Failed to establish TCP connection to the remote host.
    #[error("Failed to connect to {host}:{port}: {source}")]
    ConnectionFailed {
        host: String,
        port: u16,
        #[source]
        source: io::Error,
    },

    /// SSH handshake failed.
    #[error("SSH handshake failed: {0}")]
    HandshakeFailed(#[from] ssh2::Error),

    /// Authentication failed.
    #[error("Authentication failed for user '{username}': {reason}")]
    AuthenticationFailed { username: String, reason: String },

    /// Failed to open an SSH channel.
    #[error("Failed to open SSH channel: {0}")]
    ChannelFailed(String),

    /// Command execution failed.
    #[error("Command execution failed: {0}")]
    ExecutionFailed(String),

    /// I/O error occurred.
    #[error("I/O error: {0}")]
    Io(#[from] io::Error),

    /// Invalid configuration.
    #[error("Invalid configuration: {0}")]
    InvalidConfig(String),

    /// The client is not connected.
    #[error("Client is not connected. Call connect() first.")]
    NotConnected,

    /// The private key file was not found or could not be read.
    #[error("Private key file not found or unreadable: {path}")]
    PrivateKeyNotFound { path: String },
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_display() {
        let err = Error::NotConnected;
        assert_eq!(
            err.to_string(),
            "Client is not connected. Call connect() first."
        );
    }

    #[test]
    fn test_authentication_error() {
        let err = Error::AuthenticationFailed {
            username: "testuser".to_string(),
            reason: "Invalid password".to_string(),
        };
        assert!(err.to_string().contains("testuser"));
        assert!(err.to_string().contains("Invalid password"));
    }

    #[test]
    fn test_connection_error() {
        let io_err = io::Error::new(io::ErrorKind::ConnectionRefused, "refused");
        let err = Error::ConnectionFailed {
            host: "example.com".to_string(),
            port: 22,
            source: io_err,
        };
        assert!(err.to_string().contains("example.com"));
        assert!(err.to_string().contains("22"));
    }
}
