use chrono::{DateTime, Local};
use lazy_static::lazy_static;
use std::time::{SystemTime, UNIX_EPOCH};
use urlencoding::encode;

use isahc::{cookies::CookieJar, prelude::*, Request};
use std::sync::RwLock;

struct CookieHold {
    pub jar: CookieJar,
}

lazy_static! {
    /// Stores a client with a persistent cookie store
    static ref SESSION_COOKIE: RwLock<CookieHold> = RwLock::new(CookieHold { jar: CookieJar::new()});
    /// Stores the last time a request was made, in seconds since UNIX epoch
    static ref SESSION_TIME: RwLock<u64> = RwLock::new(0);
}

/// Make a post request with a body and content type
pub async fn classroom_post_redirect(
    url: String,
    content_type: String,
    body: String,
) -> Result<(), String> {
    let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!");

    ensure_session().await?;

    // Get the stored client
    let jar = SESSION_COOKIE.read().unwrap().jar.clone();

    let uri = format!("{}{}", classroom_url, url);

    // Do the request
    let response = Request::post(uri)
        .header("Content-Type", content_type)
        .cookie_jar(jar)
        .body(body)
        .or_else(|err| Err(format!("Error creating request: {:?}", err)))?
        .send();

    let mut response = match response {
        Ok(r) => r,
        Err(err) => return Err(format!("Error sending request: {:?}", err)),
    };

    if response.status() == isahc::http::StatusCode::FOUND {
        return Ok(());
    }

    match response.text() {
        Ok(t) => {
            log_html(&t);
            Err(format!("Expected HTTP 302, got other code."))
        }
        Err(err) => Err(format!("Error getting text from response: {:?}", err)),
    }
}

/// Makes a request to the online classroom, and returns the html string
pub async fn request(url: String) -> Result<String, String> {
    let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!");

    ensure_session().await?;

    // Get the stored client
    let jar = SESSION_COOKIE.read().unwrap().jar.clone();

    let uri = format!("{}{}", classroom_url, url);

    // Do the request
    let response = Request::get(uri)
        .cookie_jar(jar.clone())
        .body(())
        .or_else(|err| Err(format!("Error creating request: {:?}", err)))?
        .send();

    let mut response = match response {
        Ok(r) => r,
        Err(err) => return Err(format!("Error sending request: {:?}", err)),
    };

    match response.text() {
        Ok(t) => {
            log_html(&t);
            Ok(t)
        }
        Err(err) => Err(format!("Error getting text from response: {:?}", err)),
    }
}

/// Handles request for creating a user.
///
/// Returns `Ok("")` if the request was redirected (i.e. the user was created successfully
///
/// Returns `Ok(html)` if the request was not redirected (i.e. the user was not created successfully)
///
/// Returns `Err(err)` if there was an error
pub async fn create_user_request(url: String, body: String) -> Result<String, String> {
    let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!");

    ensure_session().await?;

    // Get the stored client
    let jar = SESSION_COOKIE.read().unwrap().jar.clone();

    let uri = format!("{}{}", classroom_url, url);

    // Do the request
    let response = Request::post(uri)
        .header("Content-Type", "multipart/form-data; boundary=---------------------------83919643214156711801978607619")
        .cookie_jar(jar)
        .body(body)
        .or_else(|err| Err(format!("Error creating request: {:?}", err)))?
        .send();

    let mut response = match response {
        Ok(r) => r,
        Err(err) => return Err(format!("Error sending request: {:?}", err)),
    };

    if response.status() == isahc::http::StatusCode::FOUND {
        println!("Redirected!");
        return Ok("".into());
    }

    match response.text() {
        Ok(t) => {
            log_html(&t);
            Ok(t)
        }
        Err(err) => Err(format!("Error getting text from response: {:?}", err)),
    }
}

pub async fn register_courses_request(url: String, body: String) -> Result<String, String> {
    let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!");

    ensure_session().await?;

    // Get the stored client
    let jar = SESSION_COOKIE.read().unwrap().jar.clone();

    let uri = format!("{}{}", classroom_url, url);

    let response = Request::post(uri)
        .header("Content-Type", "application/x-www-form-urlencoded")
        .cookie_jar(jar)
        .body(body)
        .or_else(|err| Err(format!("Error creating request: {:?}", err)))?
        .send();

    let mut response = match response {
        Ok(r) => r,
        Err(err) => return Err(format!("Error sending request: {:?}", err)),
    };

    match response.text() {
        Ok(t) => {
            log_html(&t);
            Ok(t)
        }
        Err(err) => Err(format!("Error getting text from response: {:?}", err)),
    }
}

/// Makes sure that the session cookie is set, and that it is valid
pub async fn ensure_session() -> Result<(), String> {
    let last_usage_time = SESSION_TIME.read().unwrap().clone();

    // Get current time in seconds
    let current_time = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("Time went backwards")
        .as_secs();

    let time_passed = current_time - last_usage_time;

    // Default PHP session timeout is 1440 seconds. Use a 1400 seconds timeout to be safe
    if time_passed > 1400 {
        login().await?;

        let mut time_ptr = SESSION_TIME.write().unwrap();
        *time_ptr = current_time;
    }

    Ok(())
}

/// Logins to the online classroom, and sets the session cookie
async fn login() -> Result<(), String> {
    let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!");
    let classroom_user =
        std::env::var("CLASSROOM_USER").expect("CLASSROOM_USER env var is not set!");
    let classroom_password =
        std::env::var("CLASSROOM_PASSWORD").expect("CLASSROOM_PASSWORD env var is not set!");

    let jar = CookieJar::new();

    let login_body = format!(
        "login={}&password={}&submitAuth=&_qf__formLogin=",
        encode(classroom_user.as_str()).into_owned(),
        encode(classroom_password.as_str()).into_owned(),
    );

    let response = Request::post(format!("{}/index.php", classroom_url))
        .header("Content-Type", "application/x-www-form-urlencoded")
        .cookie_jar(jar.clone())
        .body(login_body)
        .unwrap()
        .send();

    match response {
        Ok(mut r) => {
            if r.status() == isahc::http::StatusCode::FOUND {
                // TODO: Even if this is a 302, it might not be a successful login
                // check Set-Cookie header
                SESSION_COOKIE.write().unwrap().jar = jar.clone();
                Ok(())
            } else {
                // Write html to file
                match r.text() {
                    Ok(t) => {
                        log_html(&t);
                    }
                    Err(err) => {
                        return Err(format!("Error getting text from login response: {:?}", err))
                    }
                };

                Err(format!("Error connecting to online classroom: not 302"))
            }
        }
        Err(error) => Err(format!("Error connecting to online classroom: {:?}", error)),
    }
}

pub fn log_html(html: &String) {
    // Get current time and date in iso
    let now: DateTime<Local> = Local::now();
    let now = now.to_rfc3339();

    // Write html to file
    let r = std::fs::write(format!("request-logs/{}.html", now), html);
    if let Err(err) = r {
        eprintln!("Error writing request html to file: {:?}", err)
    }
}
