use foxtive::prelude::AppResult;
use ntex::http::header;
use ntex::util::Bytes;
use ntex::web::HttpRequest;
use serde::de::DeserializeOwned;
use serde_json::{Map, Value, json};
use tracing::debug;

use crate::http::extractors::ClientInfo;

#[allow(dead_code)]
pub trait RequestExt {
    #[cfg(feature = "database")]
    fn db_pool(&self) -> &foxtive::database::DBPool;

    fn client_info(&self) -> ClientInfo;

    fn get_headers(&self) -> Map<String, Value>;

    fn json<T: DeserializeOwned>(bytes: Bytes) -> AppResult<T>;

    fn ip(&self) -> Option<String>;

    fn user_agent(&self) -> Option<String>;
}

impl RequestExt for HttpRequest {
    #[cfg(feature = "database")]
    fn db_pool(&self) -> &foxtive::database::DBPool {
        use foxtive::prelude::AppStateExt;
        foxtive::FOXTIVE.app().database()
    }

    fn client_info(&self) -> ClientInfo {
        ClientInfo {
            ip: self.ip(),
            ua: self.user_agent(),
        }
    }

    fn get_headers(&self) -> Map<String, Value> {
        let mut headers_json_object = Map::new();

        for (name, value) in self.headers().iter() {
            headers_json_object.insert(name.to_string(), json!(value.to_str().unwrap()));
        }

        headers_json_object
    }

    fn json<T: DeserializeOwned>(bytes: Bytes) -> AppResult<T> {
        let raw = String::from_utf8(bytes.to_vec())?;
        debug!("[json-body]: {raw}");
        Ok(serde_json::from_str::<T>(&raw)?)
    }

    fn ip(&self) -> Option<String> {
        self.connection_info()
            .remote()
            .map(|v| v.to_string())
            .or_else(|| self.peer_addr().map(|s| s.to_string()))
    }

    fn user_agent(&self) -> Option<String> {
        self.headers()
            .get(header::USER_AGENT)
            .map(|ua| ua.to_str().unwrap().to_string())
    }
}
