//! Implement RPC functionality for finding what ports are running as proxies.

use std::{net::SocketAddr, sync::Arc};
use tor_error::{ErrorKind, HasKind};
use tor_rpcbase::{self as rpc};

use crate::proxy::port_info;

use super::session::ArtiRpcSession;

/// Representation of a single proxy, as delivered by the RPC API.
#[derive(serde::Serialize, Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub(super) struct Proxy {
    /// Where the proxy is listening, what protocol it speaks,
    /// and what protocol-specific options it expects.
    pub(super) listener: ProxyListener,
}

/// Representation of a single proxy's listener location, as delivered by the RPC API.
#[derive(serde::Serialize, Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub(super) enum ProxyListener {
    /// A SOCKS5 proxy.
    #[serde(rename = "socks5")]
    Socks5 {
        /// The address at which we're listening for SOCKS connections.
        tcp_address: Option<SocketAddr>,
    },
    /// An HTTP CONNECT proxy.
    #[cfg(feature = "http-connect")]
    #[serde(rename = "http_connect")]
    HttpConnect {
        /// The address at which we're listening for HTTP CONNECT connections.
        tcp_address: Option<SocketAddr>,
    },
}

impl ProxyListener {
    /// Try to represent the given port as a ProxyListener.
    ///
    /// Return None if it cannot be represented.
    pub(crate) fn try_from_portinfo(port: &port_info::Port) -> Option<Self> {
        use port_info::SupportedProtocol as SP;
        use tor_rtcompat::general::{self, SocketAddr::Inet};

        match (&port.address, &port.protocol) {
            (Inet(a), SP::Socks) => Some(Self::Socks5 {
                tcp_address: Some(*a),
            }),
            #[cfg(feature = "http-connect")]
            (Inet(a), SP::Http) => Some(Self::HttpConnect {
                tcp_address: Some(*a),
            }),
            (Inet(_), SP::DnsUdp) => None,
            // TODO: Handle unix addresses once we can bind to them
            (general::SocketAddr::Unix(_), _) => None,
            (_, _) => None,
        }
    }
}

/// A representation of the set of proxy addresses available from the RPC API.
#[derive(serde::Serialize, Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub(super) struct ProxyInfo {
    /// A list of the supported proxies.
    pub(super) proxies: Vec<Proxy>,
}

/// Get a list of all the currently running proxies.
///
/// This method should not be used when deciding which proxy
/// an RPC application should connect to.
/// Instead, the application should use
/// [`arti:get_rpc_proxy_info`](GetRpcProxyInfo).
#[derive(Debug, serde::Deserialize, derive_deftly::Deftly)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "arti:get_proxy_info"))]
struct GetProxyInfo {}

/// Get a list of the currently running proxies
/// that are integrated with the RPC system.
///
/// This method returns a list of proxies.
/// The RPC application may be not be able to use all proxies from the list,
/// and may prefer some proxies over other.
/// When multiple proxies are equally preferred,
/// the application SHOULD use whichever appears first in the list.
///
/// You typically won't need to invoke this method yourself:
/// your RPC library (like `arti-rpc-client-core`)
/// should take care if it for you.
#[derive(Debug, serde::Deserialize, derive_deftly::Deftly)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "arti:get_rpc_proxy_info"))]
struct GetRpcProxyInfo {}

impl rpc::RpcMethod for GetProxyInfo {
    type Output = ProxyInfo;
    type Update = rpc::NoUpdates;
}

impl rpc::RpcMethod for GetRpcProxyInfo {
    type Output = ProxyInfo;
    type Update = rpc::NoUpdates;
}

/// An error encountered while asking for the proxy addresses.
#[derive(Clone, Debug, thiserror::Error)]
enum GetProxyInfoError {
    /// The Sender was dropped without setting any proxy info;
    /// likely, Arti is shutting down.
    #[error("Arti appears to be shutting down")]
    Shutdown,
}
impl HasKind for GetProxyInfoError {
    fn kind(&self) -> ErrorKind {
        use GetProxyInfoError as E;
        match self {
            E::Shutdown => ErrorKind::ArtiShuttingDown,
        }
    }
}

/// Implementation for GetProxyInfo on ArtiRpcSession.
async fn rpc_session_get_proxy_info(
    session: Arc<ArtiRpcSession>,
    _method: Box<GetProxyInfo>,
    _ctx: Arc<dyn rpc::Context>,
) -> Result<ProxyInfo, GetProxyInfoError> {
    let proxy_info = session.arti_state.get_proxy_info().await;

    match proxy_info {
        Ok(info) => Ok((*info).clone()),
        Err(()) => Err(GetProxyInfoError::Shutdown),
    }
}
rpc::static_rpc_invoke_fn! {rpc_session_get_proxy_info;}

/// Implementation for GetProxyInfo on ArtiRpcSession.
async fn rpc_session_get_rpc_proxy_info(
    session: Arc<ArtiRpcSession>,
    _method: Box<GetRpcProxyInfo>,
    _ctx: Arc<dyn rpc::Context>,
) -> Result<ProxyInfo, GetProxyInfoError> {
    let proxy_info = session.arti_state.get_proxy_info().await;

    match proxy_info {
        Ok(info) => Ok((*info).clone()),
        Err(()) => Err(GetProxyInfoError::Shutdown),
    }
}
rpc::static_rpc_invoke_fn! {rpc_session_get_rpc_proxy_info;}
