//! Connection listening tasks of the relay.

use std::net::SocketAddr;
use std::sync::Arc;

use anyhow::Context;
use futures::StreamExt;
use safelog::Sensitive;
use tor_chanmgr::ChanMgr;
use tor_error::debug_report;
use tor_log_ratelim::log_ratelim;
use tor_rtcompat::{NetStreamListener, NetStreamProvider, Runtime, SpawnExt as _};
use tracing::debug;

/// Listens for Tor (OR) connections on a list of listeners,
/// building a channel for each incoming connection.
pub(crate) async fn or_listener<R: Runtime>(
    runtime: R,
    chan_mgr: Arc<ChanMgr<R>>,
    listeners: impl IntoIterator<Item = <R as NetStreamProvider<SocketAddr>>::Listener>,
) -> anyhow::Result<void::Void> {
    // a list of listening streams
    let incoming: Vec<_> = listeners
        .into_iter()
        .map(|listener| {
            let local_addr = listener.local_addr()?;
            let incoming = listener
                .incoming()
                .map(move |next| next.map(|(stream, addr)| (stream, addr, local_addr)));
            Ok(incoming)
        })
        .collect::<Result<_, anyhow::Error>>()?;

    // a single stream for all listeners
    let mut incoming = futures::stream::select_all(incoming);

    while let Some(next) = incoming.next().await {
        // This likely indicates a system configuration issue (for example max num of open files too
        // low), but we don't want to warn too often since it's likely future incoming connections
        // will fail as well.

        // The `log_ratelim` macro requires the error to be `Clone` (although this is likely
        // unnecessary here), so we throw it in an `Arc`.
        let next = next.map_err(Arc::new);

        log_ratelim!(
            "accepting incoming OR connection";
            next;
            Err(_) => WARN, "Dropping connection";
        );

        let Ok((stream, remote_addr, local_addr)) = next else {
            // We should have logged the error above.
            continue;
        };

        // This may be sensitive (for example if this is a client connecting to a guard).
        let remote_addr = Sensitive::new(remote_addr);

        debug!("New incoming OR connection from {remote_addr} on local address {local_addr}");

        // Spawn a task to handle the incoming connection (for example the channel handshake).
        let chan_mgr = Arc::clone(&chan_mgr);
        runtime
            .spawn(async move {
                match chan_mgr.handle_incoming(remote_addr, stream).await {
                    Ok(_chan) => {
                        // TODO: do we need to do anything else here?
                    }
                    // We don't want to make this too loud since it could be triggered by a bad
                    // client (for example a client that disconnects mid-handshake, or a port scan).
                    // This will log at "warn" level if it's an internal error
                    // (see `ErrorKind::is_always_a_warning()`),
                    // but if there are internal errors we should fix them so that networking/
                    // protocol errors will only be logged at "debug" level.
                    Err(e) => debug_report!(e, "Unable to handle incoming OR connection"),
                }
            })
            .context("Failed to spawn incoming channel handler")?;
    }

    Err(anyhow::anyhow!("Incoming stream closed"))
}
