use crate::dns::LocalhostResolver;
use crate::error::Result;
use log::{debug, info, warn};
use reqwest::{Client, Proxy, redirect};
use std::sync::Arc;
use yaak_models::models::DnsOverride;
use yaak_tls::{ClientCertificateConfig, get_tls_config};

#[derive(Clone)]
pub struct HttpConnectionProxySettingAuth {
    pub user: String,
    pub password: String,
}

#[derive(Clone)]
pub enum HttpConnectionProxySetting {
    Disabled,
    System,
    Enabled {
        http: String,
        https: String,
        auth: Option<HttpConnectionProxySettingAuth>,
        bypass: String,
    },
}

#[derive(Clone)]
pub struct HttpConnectionOptions {
    pub id: String,
    pub validate_certificates: bool,
    pub proxy: HttpConnectionProxySetting,
    pub client_certificate: Option<ClientCertificateConfig>,
    pub dns_overrides: Vec<DnsOverride>,
}

impl HttpConnectionOptions {
    /// Build a reqwest Client and return it along with the DNS resolver.
    /// The resolver is returned separately so it can be configured per-request
    /// to emit DNS timing events to the appropriate channel.
    pub(crate) fn build_client(&self) -> Result<(Client, Arc<LocalhostResolver>)> {
        let mut client = Client::builder()
            .connection_verbose(true)
            .redirect(redirect::Policy::none())
            // Decompression is handled by HttpTransaction, not reqwest
            .no_gzip()
            .no_brotli()
            .no_deflate()
            .referer(false)
            .tls_info(true)
            // Disable connection pooling to ensure DNS resolution happens on each request
            // This is needed so we can emit DNS timing events for each request
            .pool_max_idle_per_host(0);

        // Configure TLS with optional client certificate
        let config =
            get_tls_config(self.validate_certificates, true, self.client_certificate.clone())?;
        client = client.use_preconfigured_tls(config);

        // Configure DNS resolver - keep a reference to configure per-request
        let resolver = LocalhostResolver::new(self.dns_overrides.clone());
        client = client.dns_resolver(resolver.clone());

        // Configure proxy
        match self.proxy.clone() {
            HttpConnectionProxySetting::System => { /* Default */ }
            HttpConnectionProxySetting::Disabled => {
                client = client.no_proxy();
            }
            HttpConnectionProxySetting::Enabled { http, https, auth, bypass } => {
                for p in build_enabled_proxy(http, https, auth, bypass) {
                    client = client.proxy(p)
                }
            }
        }

        info!(
            "Building new HTTP client validate_certificates={} client_cert={}",
            self.validate_certificates,
            self.client_certificate.is_some()
        );

        Ok((client.build()?, resolver))
    }
}

fn build_enabled_proxy(
    http: String,
    https: String,
    auth: Option<HttpConnectionProxySettingAuth>,
    bypass: String,
) -> Vec<Proxy> {
    debug!("Using proxy http={http} https={https} bypass={bypass}");

    let mut proxies = Vec::new();

    if !http.is_empty() {
        match Proxy::http(http) {
            Ok(mut proxy) => {
                if let Some(HttpConnectionProxySettingAuth { user, password }) = auth.clone() {
                    debug!("Using http proxy auth");
                    proxy = proxy.basic_auth(user.as_str(), password.as_str());
                }
                proxies.push(proxy.no_proxy(reqwest::NoProxy::from_string(&bypass)));
            }
            Err(e) => {
                warn!("Failed to apply http proxy {e:?}");
            }
        };
    }

    if !https.is_empty() {
        match Proxy::https(https) {
            Ok(mut proxy) => {
                if let Some(HttpConnectionProxySettingAuth { user, password }) = auth {
                    debug!("Using https proxy auth");
                    proxy = proxy.basic_auth(user.as_str(), password.as_str());
                }
                proxies.push(proxy.no_proxy(reqwest::NoProxy::from_string(&bypass)));
            }
            Err(e) => {
                warn!("Failed to apply https proxy {e:?}");
            }
        };
    }

    proxies
}
