use crate::client::{ConfiguredClient, HttpConnectionOptions};
use crate::dns::LocalhostResolver;
use crate::error::Result;
use std::collections::BTreeMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;

/// A cached HTTP client along with its DNS resolver.
/// The resolver is needed to set the event sender per-request.
pub struct CachedClient {
    pub client: ConfiguredClient,
    pub resolver: Arc<LocalhostResolver>,
}

pub struct HttpConnectionManager {
    connections: Arc<RwLock<BTreeMap<String, (CachedClient, Instant)>>>,
    ttl: Duration,
}

impl HttpConnectionManager {
    pub fn new() -> Self {
        Self {
            connections: Arc::new(RwLock::new(BTreeMap::new())),
            ttl: Duration::from_secs(10 * 60),
        }
    }

    pub async fn get_client(&self, opt: &HttpConnectionOptions) -> Result<CachedClient> {
        let mut connections = self.connections.write().await;
        // The key must include any per-request option that changes how the
        // client is built, or a send after a settings change reuses a client
        // built with the old value for up to the cache TTL.
        let id = format!("{}::{}::{}", opt.id, opt.validate_certificates, opt.http_version);

        // Clean old connections
        connections.retain(|_, (_, last_used)| last_used.elapsed() <= self.ttl);

        if let Some((cached, last_used)) = connections.get_mut(&id) {
            *last_used = Instant::now();
            return Ok(CachedClient {
                client: cached.client.clone(),
                resolver: cached.resolver.clone(),
            });
        }

        let (client, resolver) = opt.build_client()?;
        let cached = CachedClient { client: client.clone(), resolver: resolver.clone() };
        connections.insert(id.into(), (cached, Instant::now()));

        Ok(CachedClient { client, resolver })
    }
}
