use ahash::AHashMap;
use bytes::Bytes;
use http_body_util::{BodyExt, Full};
use hyper::{header::HOST, Request};
use routerify_ng::RequestInfo;
use serde_json::Value;

use crate::{
    app::api_error::ApiError,
    helpers::{
        determine_content_type_request, extract_boundary, get_all_query, parse_multipart_form,
    },
    req::{
        body::{FormData, RequestBody, RequestBodyType, TextData},
        origin_url::Url,
        query_params::QueryParams,
        request_data::RequestData,
        request_headers::RequestHeaders,
        route_params::RouteParams,
        HttpRequest,
    },
    types::HttpMethods,
};

impl HttpRequest {
    #[doc(hidden)]
    pub async fn from_hyper_request(req: &mut Request<Full<Bytes>>) -> Result<Self, ApiError> {
        let origin_url = match req.uri().authority() {
            Some(authority) => {
                let scheme = req.uri().scheme_str().unwrap_or("http");
                Url::new(format!("{}://{}", scheme, authority))
            }
            None => {
                let uri_string = req
                    .headers()
                    .get(HOST)
                    .and_then(|host| host.to_str().ok())
                    .map(|host| {
                        let scheme = "http";
                        format!("{}://{}", scheme, host)
                    })
                    .unwrap_or(String::new());

                Url::new(uri_string)
            }
        };

        let query_string = req.uri().query().unwrap_or("");
        let queries = url::form_urlencoded::parse(query_string.as_bytes())
            .filter_map(|(key, value)| Some((key.to_string(), value.to_string())));
        let query = QueryParams::from_iterator(queries);

        let method = HttpMethods::from(req.method());
        let path = req.uri().path().to_string();

        let cookie_str_opt = req
            .headers()
            .get(hyper::header::COOKIE)
            .and_then(|v| v.to_str().ok())
            .map(|s| s.to_string());

        let x_forwarded_proto_str = req
            .headers()
            .get("x-forwarded-proto")
            .and_then(|v| v.to_str().ok())
            .unwrap_or("http")
            .to_string();

        let content_type_str_opt = req
            .headers()
            .get(hyper::header::CONTENT_TYPE)
            .and_then(|v| v.to_str().ok())
            .map(|s| s.to_string());

        let headers = RequestHeaders::from_header_map(std::mem::take(req.headers_mut()));

        let mut cookies_map = AHashMap::new();
        if let Some(cookie_str) = &cookie_str_opt {
            for cookie_part in cookie_str.split(';') {
                let trimmed = cookie_part.trim();
                if let Some(equal_pos) = trimmed.find('=') {
                    let name = &trimmed[..equal_pos];
                    let value = &trimmed[equal_pos + 1..];
                    if !name.is_empty() {
                        cookies_map.insert(name.to_string(), value.to_string());
                    }
                }
            }
        }

        let mut data = RequestData::new();
        if let Some(ext_data) = req.extensions().get::<RequestData>() {
            data = ext_data.clone();
        }

        let content_type = content_type_str_opt
            .as_deref()
            .map(determine_content_type_request)
            .unwrap_or(RequestBodyType::EMPTY);

        let request_body = match content_type {
            RequestBodyType::FORM => {
                let collected = req.body_mut().collect().await?;
                let body_bytes = collected.to_bytes();
                match std::str::from_utf8(&body_bytes) {
                    Ok(body_string) => match FormData::from_query_string(body_string) {
                        Ok(fd) => RequestBody::new_form(fd),
                        Err(_e) => RequestBody::new_form(FormData::new()),
                    },
                    Err(_e) => RequestBody::new_form(FormData::new()),
                }
            }
            RequestBodyType::MultipartForm => {
                let collected = req.body_mut().collect().await?;
                let body_bytes = collected.to_bytes();

                let boundary = content_type_str_opt
                    .as_deref()
                    .filter(|ct| ct.to_lowercase().contains("multipart/form-data"))
                    .and_then(|ct| extract_boundary(&ct));

                let (fields, file_parts) = if let Some(boundary) = boundary {
                    let (field_refs, files) = parse_multipart_form(&body_bytes, &boundary);
                    let owned_fields = field_refs
                        .into_iter()
                        .map(|(k, v)| (k.to_string(), v.to_string()))
                        .collect::<Vec<(String, String)>>();
                    (owned_fields, files)
                } else {
                    let body_string = String::from_utf8_lossy(&body_bytes);
                    match FormData::from_query_string(&body_string) {
                        Ok(fd) => {
                            let form_fields = fd
                                .iter()
                                .map(|(k, v)| (k.to_string(), v.to_string()))
                                .collect::<Vec<(String, String)>>();
                            (form_fields, Vec::new())
                        }
                        Err(_e) => (Vec::new(), Vec::new()),
                    }
                };

                let mut form_data = FormData::new();
                for (key, value) in &fields {
                    form_data.insert(key, value);
                }

                if !file_parts.is_empty() {
                    RequestBody::new_binary_with_form_fields(body_bytes, form_data)
                } else {
                    RequestBody::new_form(form_data)
                }
            }
            RequestBodyType::JSON => {
                let collected = req.body_mut().collect().await?;
                let body_bytes = collected.to_bytes();
                let body_json = match serde_json::from_slice::<serde_json::Value>(&body_bytes) {
                    Ok(json) => json,
                    Err(e) => {
                        eprintln!("Error parsing JSON: {}", e);
                        eprintln!("Defaulting to null JSON");
                        Value::Null
                    }
                };
                RequestBody::new_json(body_json)
            }
            RequestBodyType::TEXT => {
                let collected = req.body_mut().collect().await?;
                let body_bytes = collected.to_bytes();
                match TextData::from_bytes(body_bytes.as_ref().to_vec()) {
                    Ok(text) => RequestBody::new_text(text),
                    Err(_) => RequestBody::new_binary(body_bytes),
                }
            }
            RequestBodyType::BINARY => {
                let collected = req.body_mut().collect().await?;
                let body_bytes = collected.to_bytes();
                RequestBody::new_binary(body_bytes)
            }
            RequestBodyType::EMPTY => RequestBody::EMPTY,
        };

        Ok(HttpRequest {
            params: RouteParams::new(),
            query,
            origin_url,
            method,
            path,
            protocol: x_forwarded_proto_str,
            headers,
            data,
            body: request_body,
            cookies: cookies_map,
        })
    }
    pub(crate) fn from_request_info(req_info: &RequestInfo) -> Self {
        let mut headers = RequestHeaders::new();

        req_info.headers().iter().for_each(|(key, value)| {
            if let Ok(v) = value.to_str() {
                headers.insert(key.as_str(), v);
            }
        });

        let method = HttpMethods::from(req_info.method());
        let origin_url = match req_info.uri().authority() {
            Some(authority) => {
                let scheme = req_info.uri().scheme_str().unwrap_or("http");
                Url::new(format!("{}://{}", scheme, authority))
            }
            None => {
                let uri_string = req_info
                    .headers()
                    .get(HOST)
                    .and_then(|host: &hyper::header::HeaderValue| host.to_str().ok())
                    .map(|host| {
                        let scheme = "http";
                        format!("{}://{}", scheme, host)
                    })
                    .unwrap_or(String::new());

                Url::new(uri_string)
            }
        };

        let query_string = req_info.uri().query().unwrap_or("");

        let queries = url::form_urlencoded::parse(query_string.as_bytes())
            .filter_map(|(key, value)| Some((key.to_string(), value.to_string())))
            .collect::<AHashMap<String, String>>();

        let query = QueryParams::from_ahashmap(queries);
        let params = RouteParams::new();

        let mut cookies_map = AHashMap::new();
        let cookies = Self::get_cookies_from_req_info(&req_info);

        cookies.iter().for_each(|cookie| {
            let (name, value) = (cookie.name(), cookie.value());
            cookies_map.insert(name.to_string(), value.to_string());
        });

        let protocol = req_info
            .headers()
            .get("x-forwarded-proto")
            .and_then(|val| val.to_str().ok())
            .unwrap_or("http")
            .to_string();

        let mut data = RequestData::new();
        if let Some(ext_data) = req_info.data::<RequestData>() {
            data = ext_data.clone();
        }

        Self {
            body: RequestBody::EMPTY,
            cookies: cookies_map,
            headers,
            method,
            origin_url,
            params,
            path: req_info.uri().path().to_string(),
            query,
            data,
            protocol,
        }
    }

    #[cfg(feature = "with-wynd")]
    #[doc(hidden)]
    pub fn to_hyper_request(&self) -> Result<Request<Full<Bytes>>, Box<dyn std::error::Error>> {
        let path = if self.path.is_empty() {
            "/".to_string()
        } else if !self.path.starts_with('/') {
            return Err("Path must start with '/'".into());
        } else {
            self.path.clone()
        };

        let mut uri_builder = path.to_string();
        if !self.query.is_empty() {
            uri_builder.push('?');
            uri_builder.push_str(&get_all_query(&self.query));
        }

        let uri: hyper::Uri = uri_builder
            .parse()
            .map_err(|e| format!("Failed to parse URI '{}': {}", uri_builder, e))?;

        let mut builder = Request::builder()
            .method(self.method.to_string().as_str())
            .uri(uri);

        if let Some(headers) = builder.headers_mut() {
            if !self.headers.is_empty() {
                for (name, value) in self.headers.iter() {
                    if headers.contains_key(name) {
                        headers.append(name, value.clone());
                    } else {
                        headers.insert(name, value.clone());
                    }
                }
            }

            if !self.cookies.is_empty() && !headers.contains_key(hyper::header::COOKIE) {
                let cookie_str: String = self
                    .cookies
                    .iter()
                    .map(|(name, value)| format!("{}={}", name, value))
                    .collect::<Vec<_>>()
                    .join("; ");
                headers.insert(
                    hyper::header::COOKIE,
                    hyper::header::HeaderValue::from_str(&cookie_str)?,
                );
            }
        }

        let data = self.get_all_data();

        if let Some(ext) = builder.extensions_mut() {
            ext.insert(data.clone());
        }
        let body = match &self.body {
            RequestBody::JSON(json) => {
                let json_str = serde_json::to_string(json)?;
                builder
                    .headers_mut()
                    .unwrap()
                    .insert(hyper::header::CONTENT_TYPE, "application/json".parse()?);
                Full::from(hyper::body::Bytes::from(json_str))
            }
            RequestBody::TEXT(text) => {
                builder
                    .headers_mut()
                    .unwrap()
                    .insert(hyper::header::CONTENT_TYPE, "text/plain".parse()?);
                Full::from(hyper::body::Bytes::from(text.as_bytes().to_vec()))
            }
            RequestBody::FORM(form) => {
                let form_str = form.to_string();
                builder.headers_mut().unwrap().insert(
                    hyper::header::CONTENT_TYPE,
                    "application/x-www-form-urlencoded".parse()?,
                );
                Full::from(hyper::body::Bytes::from(form_str))
            }
            RequestBody::BINARY(bytes) => {
                builder.headers_mut().unwrap().insert(
                    hyper::header::CONTENT_TYPE,
                    "application/octet-stream".parse()?,
                );
                Full::from(bytes.clone())
            }
            RequestBody::BinaryWithFields(bytes, _form_data) => {
                builder
                    .headers_mut()
                    .unwrap()
                    .insert(hyper::header::CONTENT_TYPE, "multipart/form-data".parse()?);
                Full::from(bytes.clone())
            }
            RequestBody::EMPTY => Full::from(Bytes::new()),
        };
        let request = builder.body(body)?;
        Ok(request)
    }

    #[cfg(not(feature = "with-wynd"))]
    #[doc(hidden)]
    pub fn to_hyper_request(&self) -> Result<Request<Full<Bytes>>, Box<dyn std::error::Error>> {
        let mut path = if self.path.is_empty() {
            "/".to_string()
        } else if !self.path.starts_with('/') {
            return Err("Path must start with '/'".into());
        } else {
            self.path.clone()
        };
        if !self.query.is_empty() {
            path.push('?');
            path.push_str(&get_all_query(&self.query));
        }

        let uri: hyper::Uri = path
            .parse()
            .map_err(|e| format!("Failed to parse URI '{}': {}", path, e))?;

        let mut builder = Request::builder()
            .method(self.method.to_string().as_str())
            .uri(uri);

        if let Some(headers) = builder.headers_mut() {
            if !self.headers.is_empty() {
                for (name, value) in self.headers.iter() {
                    if headers.contains_key(name) {
                        headers.append(name, value.clone());
                    } else {
                        headers.insert(name, value.clone());
                    }
                }
            }

            if !self.cookies.is_empty() && !headers.contains_key(hyper::header::COOKIE) {
                let cookie_str = {
                    let cap = self
                        .cookies
                        .iter()
                        .map(|(k, v)| k.len() + v.len() + 2)
                        .sum::<usize>()
                        .saturating_sub(2);

                    let mut s = String::with_capacity(cap);
                    let mut first = true;

                    for (name, value) in &self.cookies {
                        if !first {
                            s.push_str("; ");
                        }
                        first = false;
                        s.push_str(name);
                        s.push('=');
                        s.push_str(value);
                    }
                    s
                };
                let cookie = cookie_str.as_bytes();
                headers.insert(
                    hyper::header::COOKIE,
                    hyper::header::HeaderValue::from_bytes(&cookie)?,
                );
            }
        }
        let data = self.get_all_data();
        if let Some(ext) = builder.extensions_mut() {
            ext.insert(data.clone());
        }
        let body = match &self.body {
            RequestBody::JSON(json) => {
                let json_str = serde_json::to_string(json)?;

                builder
                    .headers_mut()
                    .unwrap()
                    .insert(hyper::header::CONTENT_TYPE, "application/json".parse()?);
                Full::from(Bytes::from(json_str))
            }
            RequestBody::TEXT(text) => {
                builder
                    .headers_mut()
                    .unwrap()
                    .insert(hyper::header::CONTENT_TYPE, "text/plain".parse()?);
                Full::from(Bytes::from(text.as_bytes().to_vec()))
            }
            RequestBody::FORM(form) => {
                builder.headers_mut().unwrap().insert(
                    hyper::header::CONTENT_TYPE,
                    "application/x-www-form-urlencoded".parse()?,
                );
                Full::from(Bytes::from(form.to_string()))
            }
            RequestBody::BINARY(bytes) => {
                builder.headers_mut().unwrap().insert(
                    hyper::header::CONTENT_TYPE,
                    "application/octet-stream".parse()?,
                );
                Full::from(bytes.clone())
            }
            RequestBody::BinaryWithFields(bytes, _form_data) => {
                builder
                    .headers_mut()
                    .unwrap()
                    .insert(hyper::header::CONTENT_TYPE, "multipart/form-data".parse()?);
                Full::from(bytes.clone())
            }
            RequestBody::EMPTY => Full::from(Bytes::new()),
        };
        let request = builder.body(body)?;

        Ok(request)
    }
}
