use crate::api::MyceliumClient;
use crate::error::AppResult;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PeerEndpoint {
    pub proto: String,
    pub socket_addr: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PeerInfo {
    pub endpoint: PeerEndpoint,
    #[serde(rename = "type")]
    pub kind: String, // "static" | "inbound" | "linkLocalDiscovery"
    pub connection_state: String, // "alive" | "connecting" | "dead"
    #[serde(default)]
    pub tx_bytes: u64,
    #[serde(default)]
    pub rx_bytes: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct AggregatedStats {
    pub peers_total: usize,
    pub peers_alive: usize,
    pub peers_connecting: usize,
    pub peers_dead: usize,
    pub tx_bytes: u64,
    pub rx_bytes: u64,
}

pub fn aggregate(peers: &[PeerInfo]) -> AggregatedStats {
    let mut s = AggregatedStats {
        peers_total: peers.len(),
        ..Default::default()
    };
    for p in peers {
        s.tx_bytes = s.tx_bytes.saturating_add(p.tx_bytes);
        s.rx_bytes = s.rx_bytes.saturating_add(p.rx_bytes);
        match p.connection_state.as_str() {
            "alive" => s.peers_alive += 1,
            "connecting" => s.peers_connecting += 1,
            "dead" => s.peers_dead += 1,
            _ => {}
        }
    }
    s
}

#[derive(Debug, Serialize)]
struct AddPeerBody<'a> {
    endpoint: &'a str,
}

impl MyceliumClient {
    pub async fn list_peers(&self) -> AppResult<Vec<PeerInfo>> {
        let resp = self.http().get(self.url("/admin/peers")).send().await?;
        Self::parse(resp).await
    }

    /// The upstream OpenAPI doesn't pin the request body shape; the daemon
    /// accepts `{"endpoint": "<proto>://<addr>:<port>"}`.
    pub async fn add_peer(&self, endpoint: &str) -> AppResult<()> {
        let resp = self
            .http()
            .post(self.url("/admin/peers"))
            .json(&AddPeerBody { endpoint })
            .send()
            .await?;
        Self::check_status(resp).await
    }

    pub async fn remove_peer(&self, endpoint: &str) -> AppResult<()> {
        // Endpoints look like `tcp://188.40.132.242:9651` — the slashes and
        // colons must be percent-encoded for the path segment.
        let encoded = url_encode_path_segment(endpoint);
        let resp = self
            .http()
            .delete(self.url(&format!("/admin/peers/{encoded}")))
            .send()
            .await?;
        Self::check_status(resp).await
    }
}

fn url_encode_path_segment(s: &str) -> String {
    // Minimal ASCII percent-encoder for the chars that would otherwise
    // break path parsing. Avoids pulling a full url-encoding crate.
    let mut out = String::with_capacity(s.len());
    for b in s.bytes() {
        match b {
            b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
                out.push(b as char)
            }
            _ => out.push_str(&format!("%{:02X}", b)),
        }
    }
    out
}
