use onion_tunnel::BootstrapStatus;
use serde::Serialize;
use std::collections::HashMap;
use std::net::SocketAddr;

/// Details about a Tor relay in a connection's circuit.
#[derive(Serialize, Debug)]
pub struct RelayDetails {
    /// The RSA identity of the relay, if there is one.
    pub rsa_identity: Option<String>,
    /// The Ed25519 identity of the relay, if there is one.
    pub ed_identity: Option<String>,
    /// The IP addresses of the relay.
    pub addresses: Vec<SocketAddr>,
    /// The 2-letter country code of the relay, if there is one.
    pub country_code: Option<String>,
}

/// An asynchronous update about the state of the tunnel, sent to the Java side.
#[derive(Serialize, Debug)]
#[serde(tag = "type")]
pub enum TunnelEvent {
    /// An update to the bootstrapping status.
    Bootstrap {
        bootstrap_percent: i32,
        is_ready_for_traffic: bool,
        bootstrap_status: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        blockage_message: Option<String>,
    },
    /// A new connection just successfully completed.
    NewConnection {
        /// The Android source IP and port.
        proxy_src: SocketAddr,
        /// The proxy's "fake" destination IP and port.
        proxy_dst: SocketAddr,
        /// The actual address we tried to reach over the Tor network.
        tor_dst: String,
        /// The android application ID.
        app_id: u64,
        /// Details about the connection's circuit.
        circuit: Vec<RelayDetails>,
    },
    /// We failed to connect to something over Tor.
    FailedConnection {
        /// The Android source IP and port.
        proxy_src: SocketAddr,
        /// The proxy's "fake" destination IP and port.
        proxy_dst: SocketAddr,
        /// The actual address we tried to reach over the Tor network.
        tor_dst: String,
        /// The android application ID.
        app_id: u64,
        /// The error message returned from Tor.
        //
        // FIXME(eta): This shouldn't be stringly-typed!
        error: String,
    },
    /// A connection was closed.
    ///
    /// NOTE that this might not perfectly match up with a `NewConnection` event.
    ClosedConnection {
        /// The Android source IP and port.
        proxy_src: SocketAddr,
        /// The proxy's "fake" destination IP and port.
        proxy_dst: SocketAddr,
        /// If the connection didn't exit cleanly, the error message.
        //
        // FIXME(eta): This shouldn't be stringly-typed!
        error: Option<String>,
    },
    /// A new set of directory information is available.
    ///
    /// Currently, this is only used for relay country code information.
    NewDirectory {
        /// A map from 2-letter country code to the number of relays supposedly in that country.
        relays_by_country: HashMap<String, usize>,
    },
}

impl TunnelEvent {
    pub fn bootstrap_done() -> Self {
        Self::Bootstrap {
            bootstrap_percent: 100,
            is_ready_for_traffic: true,
            bootstrap_status: "Bootstrapping complete".into(),
            blockage_message: None,
        }
    }
    pub fn bootstrap(bootstrap_status: BootstrapStatus) -> Self {
        let percent = (bootstrap_status.as_frac() * 100.0).round() as i32;
        // NOTE(eta): We should also get the blockage kind out, when arti exports it properly.
        let blockage = bootstrap_status
            .blocked()
            .map(|blockage| blockage.to_string());
        Self::Bootstrap {
            bootstrap_percent: percent,
            is_ready_for_traffic: bootstrap_status.ready_for_traffic(),
            bootstrap_status: bootstrap_status.to_string(),
            blockage_message: blockage,
        }
    }
}
