use crate::config::scraper::RequestConfig;
use crate::http_profile::HttpClientProfile;
use crate::proxy::config::ProxyConfig;
use anyhow::Result;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use reqwest::Client;
use std::str::FromStr;
use std::time::Duration;

use super::headers::build_headers;

/// Legacy: proxy + timeout only (YAML scrapers).
pub fn build_client(proxy: &ProxyConfig, timeout_secs: u64) -> Result<Client> {
    build_client_merged(None, Some(proxy), &RequestConfig {
        method: "GET".to_string(),
        headers: None,
        timeout_secs: Some(timeout_secs),
        connect_timeout_secs: None,
        follow_redirects: None,
    })
}

/// Merge order: DB profile base → YAML `proxy` / `request` overrides (YAML wins on conflicts).
pub fn build_client_merged(
    profile: Option<&HttpClientProfile>,
    yaml_proxy: Option<&ProxyConfig>,
    yaml_request: &RequestConfig,
) -> Result<Client> {
    let timeout_secs = yaml_request
        .timeout_secs
        .or_else(|| profile.map(|p| p.timeout_secs))
        .unwrap_or(30);

    let connect_timeout = yaml_request
        .connect_timeout_secs
        .or_else(|| profile.and_then(|p| p.connect_timeout_secs));

    let follow_redirects = yaml_request.follow_redirects.unwrap_or_else(|| {
        profile
            .map(|p| p.follow_redirects)
            .unwrap_or(true)
    });

    let cookie_store = profile.map(|p| p.cookie_store).unwrap_or(false);

    let danger = profile
        .map(|p| p.danger_accept_invalid_certs)
        .unwrap_or(false);

    let mut builder = Client::builder()
        .timeout(Duration::from_secs(timeout_secs))
        .danger_accept_invalid_certs(danger)
        .cookie_store(cookie_store);

    if let Some(secs) = connect_timeout {
        builder = builder.connect_timeout(Duration::from_secs(secs));
    }

    if !follow_redirects {
        builder = builder.redirect(reqwest::redirect::Policy::none());
    }

    let proxy_chain: Option<String> = resolve_proxy_url(yaml_proxy, profile);
    if let Some(ref url) = proxy_chain {
        if !url.is_empty() {
            builder = builder.proxy(reqwest::Proxy::all(url)?);
        }
    }

    let mut headers = HeaderMap::new();
    if let Some(p) = profile {
        if let Some(ref map) = p.default_headers {
            merge_json_headers(&mut headers, map)?;
        }
        if let Some(ref ua) = p.user_agent {
            if let Ok(v) = HeaderValue::from_str(ua) {
                headers.insert(reqwest::header::USER_AGENT, v);
            }
        }
    }
    if let Some(ref h) = yaml_request.headers {
        headers.extend(build_headers(h));
    }

    Ok(builder.default_headers(headers).build()?)
}

fn resolve_proxy_url(yaml_proxy: Option<&ProxyConfig>, profile: Option<&HttpClientProfile>) -> Option<String> {
    let from_yaml = yaml_proxy.and_then(|p| match p {
        ProxyConfig::None => None,
        ProxyConfig::Http { url } | ProxyConfig::Socks5 { url } => Some(url.clone()),
    });
    from_yaml.or_else(|| profile.and_then(|p| p.proxy_url.clone()))
}

fn merge_json_headers(out: &mut HeaderMap, map: &serde_json::Map<String, serde_json::Value>) -> Result<()> {
    for (k, v) in map {
        if let Some(s) = v.as_str() {
            let name = HeaderName::from_str(k).map_err(|e| anyhow::anyhow!("header {}: {}", k, e))?;
            let val = HeaderValue::from_str(s).map_err(|e| anyhow::anyhow!("header {} value: {}", k, e))?;
            out.insert(name, val);
        }
    }
    Ok(())
}

/// CLI / dexter-scrape: optional profile + optional URL override (`cli_override` profile name in logs).
pub fn build_client_for_cli(
    profile: Option<&HttpClientProfile>,
    proxy_url_override: Option<&str>,
    cookie_store_default: bool,
) -> Result<Client> {
    let timeout_secs = profile.map(|p| p.timeout_secs).unwrap_or(60);
    let connect_timeout = profile.and_then(|p| p.connect_timeout_secs);
    let follow_redirects = profile.map(|p| p.follow_redirects).unwrap_or(true);
    let cookie_store = profile.map(|p| p.cookie_store).unwrap_or(cookie_store_default);
    let danger = profile.map(|p| p.danger_accept_invalid_certs).unwrap_or(false);

    let mut builder = Client::builder()
        .timeout(Duration::from_secs(timeout_secs))
        .danger_accept_invalid_certs(danger)
        .cookie_store(cookie_store);

    if let Some(secs) = connect_timeout {
        builder = builder.connect_timeout(Duration::from_secs(secs));
    }
    if !follow_redirects {
        builder = builder.redirect(reqwest::redirect::Policy::none());
    }

    let url = proxy_url_override
        .filter(|s| !s.is_empty())
        .map(String::from)
        .or_else(|| profile.and_then(|p| p.proxy_url.clone()));

    if let Some(ref u) = url {
        if !u.is_empty() {
            builder = builder.proxy(reqwest::Proxy::all(u)?);
        }
    }

    let mut headers = HeaderMap::new();
    if let Some(p) = profile {
        if let Some(ref map) = p.default_headers {
            merge_json_headers(&mut headers, map)?;
        }
        if let Some(ref ua) = p.user_agent {
            if let Ok(v) = HeaderValue::from_str(ua) {
                headers.insert(reqwest::header::USER_AGENT, v);
            }
        }
    }

    Ok(builder.default_headers(headers).build()?)
}
