pub mod admin;
pub mod messages;
pub mod peers;
pub mod pubkey;
pub mod routes;
pub mod topics;

use crate::error::{AppError, AppResult};
use reqwest::{Client, Response};
use serde::de::DeserializeOwned;
use std::time::Duration;

/// Thin REST client for the mycelium daemon's HTTP API.
///
/// The base URL is set after the sidecar reports ready; clients are cheap
/// to clone (the inner `reqwest::Client` keeps a shared connection pool).
#[derive(Debug, Clone)]
pub struct MyceliumClient {
    base: String,
    http: Client,
}

impl MyceliumClient {
    pub fn new(base: impl Into<String>) -> Self {
        // Mycelium's HTTP server seems to drop idle keep-alive connections
        // around the 10s mark; reusing a pooled stale connection surfaces
        // as a generic "error sending request" once `start_daemon`
        // returned. Open a fresh TCP connection per request — overhead is
        // negligible on loopback and immune to server-side closes.
        let http = Client::builder()
            .pool_max_idle_per_host(0)
            .timeout(Duration::from_secs(10))
            .connect_timeout(Duration::from_secs(3))
            .build()
            .expect("reqwest client build");
        Self {
            base: base.into(),
            http,
        }
    }

    pub fn base_url(&self) -> &str {
        &self.base
    }

    pub(crate) fn url(&self, path: &str) -> String {
        format!("{}/api/v1{}", self.base, path)
    }

    pub(crate) async fn parse<T: DeserializeOwned>(resp: Response) -> AppResult<T> {
        let status = resp.status();
        if status.is_success() {
            resp.json::<T>().await.map_err(AppError::from)
        } else {
            let body = resp.text().await.unwrap_or_default();
            Err(AppError::DaemonStatus {
                status: status.as_u16(),
                body,
            })
        }
    }

    pub(crate) async fn check_status(resp: Response) -> AppResult<()> {
        let status = resp.status();
        if status.is_success() {
            Ok(())
        } else {
            let body = resp.text().await.unwrap_or_default();
            Err(AppError::DaemonStatus {
                status: status.as_u16(),
                body,
            })
        }
    }

    pub(crate) fn http(&self) -> &Client {
        &self.http
    }
}
