use serde::Serialize;
use std::env;
use std::fmt;
use std::sync::Arc;
use wreq::Client;
use wreq::Proxy;
use wreq::Response;
use wreq::Version;
use wreq::cookie::Jar;
use wreq::header::{HeaderMap, HeaderValue, USER_AGENT};
use wreq::multipart::Form;
use wreq::redirect::Policy;
use wreq_util::Emulation;

use crate::util::flaresolverr::FlareSolverrRequest;
use crate::util::flaresolverr::Flaresolverr;
use crate::util::proxy;

// A Send + Sync error type for all async paths
type AnyErr = Box<dyn std::error::Error + Send + Sync + 'static>;

#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct Requester {
    #[serde(skip)]
    client: Client,
    #[serde(skip)]
    cookie_jar: Arc<Jar>,
    proxy: bool,
    flaresolverr_session: Option<String>,
    user_agent: Option<String>,
}

impl fmt::Debug for Requester {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Requester")
            .field("proxy", &self.proxy)
            .field("flaresolverr_session", &self.flaresolverr_session)
            .field("user_agent", &self.user_agent)
            .finish()
    }
}

impl Requester {
    fn build_client(cookie_jar: Arc<Jar>, user_agent: Option<&str>) -> Client {
        let mut builder = Client::builder()
            .cert_verification(false)
            .emulation(Emulation::Firefox146)
            .cookie_provider(cookie_jar)
            .redirect(Policy::default());

        if let Some(user_agent) = user_agent {
            let mut headers = HeaderMap::new();
            if let Ok(value) = HeaderValue::from_str(user_agent) {
                headers.insert(USER_AGENT, value);
                builder = builder.default_headers(headers);
            }
        }

        builder.build().expect("Failed to create HTTP client")
    }

    pub fn new() -> Self {
        let cookie_jar = Arc::new(Jar::default());
        let client = Self::build_client(cookie_jar.clone(), None);

        let requester = Requester {
            client,
            cookie_jar,
            proxy: false,
            flaresolverr_session: None,
            user_agent: None,
        };

        proxy::init_all_proxies_background(requester.clone());

        requester
    }

    pub fn set_proxy(&mut self, proxy: bool) {
        if proxy {
            println!("Proxy enabled");
        }
        self.proxy = proxy;
    }

    pub async fn get_raw(&mut self, url: &str) -> Result<Response, wreq::Error> {
        let client = Self::build_client(self.cookie_jar.clone(), self.user_agent.as_deref());

        let mut request = client.get(url).version(Version::HTTP_11);

        if self.proxy {
            if let Ok(proxy_url) = env::var("BURP_URL") {
                let proxy = Proxy::all(&proxy_url).unwrap();
                request = request.proxy(proxy);
            }
        }

        request.send().await
    }

    pub async fn get_raw_with_headers(
        &mut self,
        url: &str,
        headers: Vec<(String, String)>,
    ) -> Result<Response, wreq::Error> {
        let client = Self::build_client(self.cookie_jar.clone(), self.user_agent.as_deref());

        let mut request = client.get(url).version(Version::HTTP_11);

        if self.proxy {
            if let Ok(proxy_url) = env::var("BURP_URL") {
                let proxy = Proxy::all(&proxy_url).unwrap();
                request = request.proxy(proxy);
            }
        }
        // Set custom headers
        for (key, value) in headers.iter() {
            request = request.header(key, value);
        }
        request.send().await
    }

    pub async fn post_json<S>(
        &mut self,
        url: &str,
        data: &S,
        headers: Vec<(String, String)>,
    ) -> Result<Response, wreq::Error>
    where
        S: Serialize + ?Sized,
    {
        let mut request = self.client.post(url).version(Version::HTTP_11).json(data);

        // Set custom headers
        for (key, value) in headers.iter() {
            request = request.header(key, value);
        }

        if self.proxy {
            if let Ok(proxy_url) = env::var("BURP_URL") {
                let proxy = Proxy::all(&proxy_url).unwrap();
                request = request.proxy(proxy);
            }
        }

        request.send().await
    }

    pub async fn post(
        &mut self,
        url: &str,
        data: &str,
        headers: Vec<(&str, &str)>,
    ) -> Result<Response, wreq::Error> {
        let mut request = self
            .client
            .post(url)
            .version(Version::HTTP_11)
            .body(data.to_string());

        // Set custom headers
        for (key, value) in headers.iter() {
            request = request.header(key.to_string(), value.to_string());
        }

        if self.proxy {
            if let Ok(proxy_url) = env::var("BURP_URL") {
                let proxy = Proxy::all(&proxy_url).unwrap();
                request = request.proxy(proxy);
            }
        }

        request.send().await
    }

    pub async fn post_multipart(
        &mut self,
        url: &str,
        form: Form,
        headers: Vec<(String, String)>,
        _http_version: Option<Version>,
    ) -> Result<Response, wreq::Error> {
        let http_version = match _http_version {
            Some(v) => v,
            None => Version::HTTP_11,
        };

        let mut request = self.client.post(url).multipart(form).version(http_version);

        // Set custom headers
        for (key, value) in headers.iter() {
            request = request.header(key, value);
        }

        if self.proxy {
            if let Ok(proxy_url) = env::var("BURP_URL") {
                let proxy = Proxy::all(&proxy_url).unwrap();
                request = request.proxy(proxy);
            }
        }

        request.send().await
    }

    pub async fn get(
        &mut self,
        url: &str,
        _http_version: Option<Version>,
    ) -> Result<String, AnyErr> {
        self.get_with_headers(url, Vec::new(), _http_version).await
    }

    pub async fn get_with_headers(
        &mut self,
        url: &str,
        headers: Vec<(String, String)>,
        _http_version: Option<Version>,
    ) -> Result<String, AnyErr> {
        let http_version = match _http_version {
            Some(v) => v,
            None => Version::HTTP_11,
        };
        loop {
            let mut request = self.client.get(url).version(http_version);
            for (key, value) in headers.iter() {
                request = request.header(key, value);
            }
            if self.proxy {
                if let Ok(proxy_url) = env::var("BURP_URL") {
                    let proxy = Proxy::all(&proxy_url).unwrap();
                    request = request.proxy(proxy);
                }
            }
            let response = request.send().await?;
            if response.status().is_success() || response.status().as_u16() == 404 {
                return Ok(response.text().await?);
            }
            if response.status().as_u16() == 429 {
                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
                continue;
            } else {
                println!(
                    "Direct request to {} failed with status: {}",
                    url,
                    response.status()
                );
                break;
            }
        }

        // If direct request failed, try FlareSolverr. Map its error to a Send+Sync error immediately,
        // so no non-Send error value lives across later `.await`s.
        let flare_url = match env::var("FLARE_URL") {
            Ok(url) => url,
            Err(e) => return Err(format!("FLARE_URL not set: {e}").into()),
        };
        let mut flare = Flaresolverr::new(flare_url);
        if self.proxy && env::var("BURP_URL").is_ok() {
            flare.set_proxy(true);
        }

        let res = flare
            .solve(FlareSolverrRequest {
                cmd: "request.get".to_string(),
                url: url.to_string(),
                maxTimeout: 60000,
            })
            .await
            .map_err(|e| -> AnyErr { format!("Failed to solve FlareSolverr: {e}").into() })?;

        // Rebuild client and apply UA/cookies from FlareSolverr
        let cookie_origin = url.split('/').take(3).collect::<Vec<&str>>().join("/");

        let useragent = res.solution.userAgent;
        self.user_agent = Some(useragent);

        if url::Url::parse(&cookie_origin).is_ok() {
            for cookie in res.solution.cookies {
                self.cookie_jar
                    .add_cookie_str(&format!("{}={}", cookie.name, cookie.value), &cookie_origin);
            }
        }

        self.client = Self::build_client(self.cookie_jar.clone(), self.user_agent.as_deref());

        // Retry the original URL with the updated client & (optional) proxy
        let mut request = self.client.get(url).version(Version::HTTP_11);
        for (key, value) in headers.iter() {
            request = request.header(key, value);
        }
        if self.proxy {
            if let Ok(proxy_url) = env::var("BURP_URL") {
                let proxy = Proxy::all(&proxy_url).unwrap();
                request = request.proxy(proxy);
            }
        }

        let response = request.send().await?;
        if response.status().is_success() {
            return Ok(response.text().await?);
        }

        // Fall back to FlareSolverr-provided body
        Ok(res.solution.response)
    }
}
