//! HTTP client for resource fetching.
//!
//! Provides:
//! - `fetch` — standard HTTP GET with cookies
//! - `intercept` — fetch with an InterceptAction (continue/fail/fulfill)
//! - `fetch_text`, `post`, `post_json` — convenience methods

use crate::config::BrowserConfig;
use crate::error::{CoreError, Result};
use crate::network::cookie::CookieJar;
use crate::network::intercept::{InterceptAction, InterceptedBody, InterceptedResponse};
use parking_lot::RwLock;
use reqwest::{Client, Response};
use std::sync::Arc;
use url::Url;

/// HTTP client wrapper with cookie support and configurable defaults.
pub struct HttpClient {
    client: Client,
    #[allow(dead_code)]
    config: BrowserConfig,
    cookie_jar: Arc<RwLock<CookieJar>>,
}

impl HttpClient {
    /// Build a new HTTP client from browser config.
    pub fn new(config: &BrowserConfig, cookie_jar: Arc<RwLock<CookieJar>>) -> Result<Self> {
        let mut builder = Client::builder()
            .user_agent(&config.user_agent)
            .pool_max_idle_per_host(config.connection_pool_size)
            .timeout(config.default_timeout)
            .redirect(reqwest::redirect::Policy::limited(10));

        if config.accept_invalid_certs {
            builder = builder.danger_accept_invalid_certs(true);
        }

        let client = builder
            .build()
            .map_err(|e| CoreError::NetworkError(e.to_string()))?;

        Ok(Self {
            client,
            config: config.clone(),
            cookie_jar,
        })
    }

    /// Fetch a URL and return the response.
    pub async fn fetch(&self, url: &Url) -> Result<Response> {
        let cookies = self.cookie_jar.read().cookies_for_url(url);

        let mut request = self.client.get(url.as_str());
        if !cookies.is_empty() {
            request = request.header("Cookie", cookies);
        }

        let response = request
            .send()
            .await
            .map_err(|e| CoreError::NetworkError(e.to_string()))?;

        // Store response cookies
        if let Some(set_cookie) = response.headers().get("set-cookie") {
            if let Ok(val) = set_cookie.to_str() {
                self.cookie_jar.write().store(url, val);
            }
        }

        Ok(response)
    }

    /// Fetch with an InterceptAction from the Fetch domain.
    ///
    /// - `Continue`: perform the actual HTTP request (with optional modifications)
    /// - `Fail`: return a network error immediately
    /// - `Fulfill`: return a synthetic response via InterceptedResponse
    pub async fn intercept(
        &self,
        url: &Url,
        _method: Option<&str>,
        _headers: &[(String, String)],
        _post_data: Option<&str>,
        action: InterceptAction,
    ) -> Result<Response> {
        use reqwest::header::{HeaderName, HeaderValue};

        match action {
            InterceptAction::Continue { url: url_mod, method: method_mod, headers: headers_mod, post_data: post_data_mod } => {
                let effective_url = url_mod.as_ref().and_then(|u| Url::parse(u).ok()).unwrap_or_else(|| url.clone());
                let effective_method = method_mod.as_deref().unwrap_or("GET");
                let effective_post = post_data_mod.as_deref();
                let cookies = self.cookie_jar.read().cookies_for_url(&effective_url);


                let mut req_builder = if effective_method == "POST" {
                    let body = effective_post.unwrap_or_default();
                    self.client.post(effective_url.as_str()).body(body.to_string())
                } else {
                    self.client.get(effective_url.as_str())
                };

                if !cookies.is_empty() {
                    req_builder = req_builder.header("Cookie", cookies);
                }
                // Apply modified headers
                for (k, v) in headers_mod.iter() {
                    if let (Ok(name), Ok(val)) = (
                        HeaderName::try_from(k.as_str()),
                        HeaderValue::try_from(v.as_str()),
                    ) {
                        req_builder = req_builder.header(name, val);
                    }
                }

                let response = req_builder
                    .send()
                    .await
                    .map_err(|e| CoreError::NetworkError(e.to_string()))?;

                if let Some(set_cookie) = response.headers().get("set-cookie") {
                    if let Ok(val) = set_cookie.to_str() {
                        self.cookie_jar.write().store(&effective_url, val);
                    }
                }
                Ok(response)
            }
            InterceptAction::Fail { error_reason } => {
                Err(CoreError::NetworkError(error_reason))
            }
            InterceptAction::Fulfill { status_code, status_text, headers: resp_headers, body } => {
                let resp = InterceptedResponse {
                    status_code,
                    status_text,
                    headers: resp_headers,
                    body: InterceptedBody::Bytes(body),
                };
                Err(CoreError::InterceptedResponse(resp))
            }
        }
    }

    /// Fetch URL and return body as a string, auto-detecting encoding.
    ///
    /// Uses `Content-Type` header charset, BOM, and HTML `<meta>` tags
    /// to detect the character encoding. Falls back to UTF-8.
    pub async fn fetch_text(&self, url: &Url) -> Result<String> {
        let response = self.fetch(url).await?;
        let content_type = response
            .headers()
            .get("content-type")
            .and_then(|v| v.to_str().ok())
            .map(|s| s.to_string());

        let bytes = response
            .bytes()
            .await
            .map_err(|e| CoreError::NetworkError(e.to_string()))?;

        Ok(crate::encoding::decode_html(
            &bytes,
            content_type.as_deref(),
        ))
    }

    /// Send a POST request with a raw body.
    pub async fn post(&self, url: &Url, body: impl Into<reqwest::Body>) -> Result<Response> {
        let response = self
            .client
            .post(url.as_str())
            .body(body)
            .send()
            .await
            .map_err(|e| CoreError::NetworkError(e.to_string()))?;

        // Store response cookies
        if let Some(set_cookie) = response.headers().get("set-cookie") {
            if let Ok(val) = set_cookie.to_str() {
                self.cookie_jar.write().store(url, val);
            }
        }

        Ok(response)
    }

    /// Send a POST request with a JSON body.
    pub async fn post_json(&self, url: &Url, json: &serde_json::Value) -> Result<Response> {
        let response = self
            .client
            .post(url.as_str())
            .json(json)
            .send()
            .await
            .map_err(|e| CoreError::NetworkError(e.to_string()))?;

        // Store response cookies
        if let Some(set_cookie) = response.headers().get("set-cookie") {
            if let Ok(val) = set_cookie.to_str() {
                self.cookie_jar.write().store(url, val);
            }
        }

        Ok(response)
    }

    /// Send a POST request with URL-encoded form data.
    pub async fn post_form(&self, url: &Url, form: &[(&str, &str)]) -> Result<Response> {
        let response = self
            .client
            .post(url.as_str())
            .form(form)
            .send()
            .await
            .map_err(|e| CoreError::NetworkError(e.to_string()))?;

        // Store response cookies
        if let Some(set_cookie) = response.headers().get("set-cookie") {
            if let Ok(val) = set_cookie.to_str() {
                self.cookie_jar.write().store(url, val);
            }
        }

        Ok(response)
    }

    /// Get the underlying reqwest client.
    pub fn raw_client(&self) -> &Client {
        &self.client
    }
}
