use std::collections::HashMap;
use std::str::FromStr;

use actix_web::{HttpRequest, HttpResponse, web};
use actix_web::dev::HttpResponseBuilder;
use actix_web::http::{HeaderName, HeaderValue, StatusCode};
use serde::Serialize;

use crate::api_data::http_error::HttpError;
use crate::controllers::routes::RequestResult;
use crate::data::api_client::APIClient;
use crate::data::base_request_handler::{BaseRequestHandler, RequestValue};
use crate::data::config::conf;
use crate::data::error::{Res, ResultBoxError};
use crate::data::user_token::UserAccessToken;
use crate::helpers::{account_helper, api_helper};

/// Http request handler
///
/// @author Pierre Hubert

pub struct HttpRequestHandler {
    request: web::HttpRequest,
    body: HashMap<String, RequestValue>,
    response: Option<web::HttpResponse>,
    headers: HashMap<String, String>,
    client: Option<APIClient>,
    curr_user_token: Option<UserAccessToken>,
}

impl HttpRequestHandler {
    /// Construct a new request handler
    pub fn new(req: HttpRequest, body: HashMap<String, RequestValue>) -> HttpRequestHandler {
        HttpRequestHandler {
            request: req,
            body,
            response: None,
            headers: HashMap::new(),
            client: None,
            curr_user_token: None,
        }
    }

    /// Check if a response has been set for this request
    pub fn has_response(&self) -> bool {
        self.response.is_some()
    }

    /// Get the response status code, eg. 200 or 404
    pub fn response_status_code(&self) -> u16 {
        self.response.as_ref().unwrap().status().as_u16()
    }

    /// Take the response from this struct
    pub fn response(self) -> ResultBoxError<HttpResponse> {
        let mut response = self.response.unwrap();

        // Put additional headers if required
        for (k, v) in &self.headers {
            response.headers_mut().insert(HeaderName::from_str(k)?,
                                          HeaderValue::from_str(v)?,
            );
        }

        Ok(response)
    }


    /// Get the path of the request
    pub fn request_path(&self) -> String {
        self.request.path().to_string()
    }

    /// Get information about the client which made the request
    pub fn api_client(&self) -> &APIClient {
        self.client.as_ref().unwrap()
    }

    /// Check API client tokens
    pub fn check_client_token(&mut self) -> RequestResult {
        // TODO : remove fallback
        let client_name = self.post_string_with_fallback("client", "serviceName")?;

        let client = self.ok_or_bad_request(
            api_helper::get_client(&client_name),
            "Client not recognized!",
        )?;


        if let Some(domain) = &client.domain {
            let allowed_origin = match conf().force_https {
                true => format!("https://{}", domain),
                false => format!("http://{}", domain)
            };

            match self.request.headers().get("Referer") {
                None => self.bad_request("Unknown origin!".to_string())?,
                Some(s) => {
                    if !s.to_str()?.starts_with(&allowed_origin) {
                        self.bad_request("Use of this client is prohibited from this domain!".to_string())?;
                    }
                }
            }

            self.headers.insert("Access-Control-Allow-Origin".to_string(), allowed_origin);
        }

        self.client = Some(client);

        Ok(())
    }

    /// Check login token
    pub fn check_user_token(&mut self) -> Res {
        // TODO : remove fallback
        let token = self.post_string_with_fallback("token", "userToken1")?;

        // Find user
        match account_helper::find_user_by_login_token(&token, self.api_client()) {
            Ok(token) => {
                if token.need_refresh() {
                    account_helper::refresh_access_token(&token)?;
                }

                self.curr_user_token = Some(token);

                Ok(())
            }
            Err(e) => {
                println!("Error marking login tokens as invalid: {}", e);
                self.response = Some(
                    actix_web::HttpResponse::
                    build(actix_web::http::StatusCode::from_u16(412)?)
                        .json(HttpError::new(412, "Please check your login tokens!")));
                Err(e)
            }
        }
    }
}

impl BaseRequestHandler for HttpRequestHandler {
    /// Get request parameter
    fn post_parameter_opt(&self, name: &str) -> Option<&RequestValue> {
        self.body.get(name)
    }

    /// Set request response
    fn set_response<T: Serialize>(&mut self, data: T) -> RequestResult {
        self.response = Some(HttpResponse::Ok().json(data));
        Ok(())
    }

    /// Set request error
    fn set_error(&mut self, error: HttpError) {
        self.response = Some(HttpResponseBuilder::new(StatusCode::from_u16(error.error.code).unwrap())
            .json(error));
    }

    /// Get the remote IP address
    fn remote_ip(&self) -> String {
        let mut ip = self.request.peer_addr().unwrap().ip().to_string();

        // We check if the request comes from a trusted reverse proxy
        if let Some(proxy) = conf().proxy.as_ref() {
            if ip.eq(proxy) {
                if let Some(header) = self.request.headers().get("X-Forwarded-For") {
                    let header: Vec<String> = header
                        .to_str()
                        .unwrap()
                        .to_string()
                        .split(",")
                        .map(|f| f.to_string())
                        .collect();

                    if header.len() > 0 {
                        ip = header[0].to_string();
                    }
                }
            }
        }

        ip
    }

    fn user_access_token(&self) -> Option<&UserAccessToken> {
        self.curr_user_token.as_ref()
    }
}