use anyhow::Result;
use iroh_metrics::metrics::start_metrics_server;
use tracing::info;
use crate::{
    config::Config,
    dns::{DnsHandler, DnsServer},
    http::HttpServer,
    state::AppState,
    store::ZoneStore,
};
pub async fn run_with_config_until_ctrl_c(config: Config) -> Result<()> {
    let mut store = ZoneStore::persistent(Config::signed_packet_store_path()?)?;
    if let Some(bootstrap) = config.mainline_enabled() {
        info!("mainline fallback enabled");
        store = store.with_mainline_fallback(bootstrap);
    };
    let server = Server::spawn(config, store).await?;
    tokio::signal::ctrl_c().await?;
    info!("shutdown");
    server.shutdown().await?;
    Ok(())
}
pub struct Server {
    http_server: HttpServer,
    dns_server: DnsServer,
    metrics_task: tokio::task::JoinHandle<anyhow::Result<()>>,
}
impl Server {
    pub async fn spawn(config: Config, store: ZoneStore) -> Result<Self> {
        let dns_handler = DnsHandler::new(store.clone(), &config.dns)?;
        let state = AppState { store, dns_handler };
        let metrics_addr = config.metrics_addr();
        let metrics_task = tokio::task::spawn(async move {
            if let Some(addr) = metrics_addr {
                start_metrics_server(addr).await?;
            }
            Ok(())
        });
        let http_server = HttpServer::spawn(
            config.http,
            config.https,
            config.pkarr_put_rate_limit,
            state.clone(),
        )
        .await?;
        let dns_server = DnsServer::spawn(config.dns, state.dns_handler.clone()).await?;
        Ok(Self {
            http_server,
            dns_server,
            metrics_task,
        })
    }
    pub async fn shutdown(self) -> Result<()> {
        self.metrics_task.abort();
        let (res1, res2) = tokio::join!(self.dns_server.shutdown(), self.http_server.shutdown(),);
        res1?;
        res2?;
        Ok(())
    }
    pub async fn run_until_error(self) -> Result<()> {
        tokio::select! {
            res = self.dns_server.run_until_done() => res?,
            res = self.http_server.run_until_done() => res?,
        }
        self.metrics_task.abort();
        Ok(())
    }
    #[cfg(test)]
    pub async fn spawn_for_tests() -> Result<(Self, std::net::SocketAddr, url::Url)> {
        Self::spawn_for_tests_with_mainline(None).await
    }
    #[cfg(test)]
    pub async fn spawn_for_tests_with_mainline(
        mainline: Option<crate::config::BootstrapOption>,
    ) -> Result<(Self, std::net::SocketAddr, url::Url)> {
        use std::net::{IpAddr, Ipv4Addr};
        use crate::config::MetricsConfig;
        let mut config = Config::default();
        config.dns.port = 0;
        config.dns.bind_addr = Some(IpAddr::V4(Ipv4Addr::LOCALHOST));
        config.http.as_mut().unwrap().port = 0;
        config.http.as_mut().unwrap().bind_addr = Some(IpAddr::V4(Ipv4Addr::LOCALHOST));
        config.https = None;
        config.metrics = Some(MetricsConfig::disabled());
        let mut store = ZoneStore::in_memory()?;
        if let Some(bootstrap) = mainline {
            info!("mainline fallback enabled");
            store = store.with_mainline_fallback(bootstrap);
        }
        let server = Self::spawn(config, store).await?;
        let dns_addr = server.dns_server.local_addr();
        let http_addr = server.http_server.http_addr().expect("http is set");
        let http_url = format!("http://{http_addr}").parse()?;
        Ok((server, dns_addr, http_url))
    }
}