use std::time::{SystemTime, UNIX_EPOCH};

use once_cell::sync::OnceCell;
use reqwest::{Client, cookie::Jar, Url};

/// Stores the ch_sid cookie value
static SESSION_COOKIE: OnceCell<String> = OnceCell::new();
/// Stores the last time a request was made, in seconds since UNIX epoch
static SESSION_TIME: OnceCell<u64> = OnceCell::new();

/// 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?;

    // Create a client & set cookies
    let cookie = SESSION_COOKIE.get().expect("SESSION_COOKIE was not set, even after calling ensure_session");
    let cookie = format!("ch_sid={};", cookie);
    let cookie_url = format!("{}", classroom_url).parse::<Url>().expect("Error parsing CLASSROOM_URL into a url");

    let jar = Jar::default();
    jar.add_cookie_str(cookie.as_str(), &cookie_url);

    let client = reqwest::Client::builder()
        .cookie_provider(jar.into())
        .build();

    let client = match client {
        Ok(c) => c,
        Err(error) => return Err(format!("Error creating client: {:?}", error))
    };

    // Do the request
    let response = client
        .get(format!("{}{}", classroom_url, url))
        .send()
        .await;

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

    match response.text().await {
        Ok(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 = match SESSION_TIME.get() {
        Some(time) => *time,
        None => 0,
    };

    // 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?;
        if let Err(err) = SESSION_TIME.set(current_time) {
            return Err(format!("Error setting session time: {:?}", err));
        }
    }

    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 clasroom_user =
        std::env::var("CLASSROOM_USER").expect("CLASSROOM_USER env var is not set!");
    let clasroom_password =
        std::env::var("CLASSROOM_PASSWORD").expect("CLASSROOM_PASSWORD env var is not set!");

    let params = [
        ("login", clasroom_user),
        ("password", clasroom_password),
        ("submitAuth", "".into()),
        ("_qf__formLogin", "".into()),
    ];
    let client = Client::new();
    let result = client
        .post(format!("{}/index.php", classroom_url))
        .form(&params)
        .send()
        .await;

    match result {
        Ok(response) => {
            let Some(session_cookie) = response.cookies().find(|c| c.name() == "ch_sid") else {
                return Err("Response succeeded, but no session cookie was foun".into());
            };

            match SESSION_COOKIE.set(session_cookie.value().into()) {
                Ok(_) => Ok(()),
                Err(error) => Err(format!("Error setting session cookie: {:?}", error)),
            }
        }
        Err(error) => Err(format!("Error connecting to online classroom: {:?}", error)),
    }
}
