use crate::db::{DBEntity, User};
use crate::web::ui::check_logged_in;
use crate::web::utils::create_api_token;
use std::fmt::Display;
use std::str::FromStr;
use std::sync::Arc;
use askama::Template;
use axum::extract::{Path, State};
use axum::response::{Html, Redirect};
use axum::routing::get;
use axum::routing::post;
use axum::{debug_handler, Form, Router};
use axum::http::Uri;
use chrono::{DateTime, Duration, Utc};
use enum_iterator::Sequence;
use oauth2::CsrfToken;
use serde::{Deserialize, Serialize};
use tower_sessions::Session;
use tracing::error;
use crate::db::UserAuthToken;
use crate::web::GoatState;
static SESSION_CSRFTOKEN_FIELD: &str = "api_token_csrf_token";
#[derive(Template)]
#[template(path = "user_settings.html")]
struct Settings {
    pub user_is_admin: bool,
}
pub async fn settings(State(_state): State<GoatState>) -> Html<String> {
    let context = Settings {
        user_is_admin: true,
    };
    Html::from(context.render().unwrap())
}
#[derive(Template)]
#[template(path = "user_api_tokens.html")]
struct ApiTokensGetPage {
    csrftoken: String,
    tokens: Vec<Arc<UserAuthToken>>,
    tokenkey: Option<String>,
    token_value: Option<String>,
    pub user_is_admin: bool,
}
pub async fn validate_csrf_expiry(user_input: &str, session: &mut Session) -> bool {
    let session_token: String = match session.get(SESSION_CSRFTOKEN_FIELD).await.unwrap() {
        None => {
            session
                .remove_value(SESSION_CSRFTOKEN_FIELD)
                .await
                .map_err(|err| {
                    error!("Failed to remove CSRF token from session: {err:?}");
                    false
                })
                .unwrap();
            log::debug!("Couldn't get session token from storage");
            return false;
        }
        Some(value) => value,
    };
    let mut split = session_token.split('|');
    let csrf_token = match split.next() {
        None => {
            log::debug!("Didn't get split token");
            return false;
        }
        Some(value) => value,
    };
    if user_input != csrf_token {
        log::debug!("Session and form CSRF token failed to match! user={user_input} <> session={csrf_token}");
        return false;
    }
    let expiry = match split.next() {
        None => {
            log::debug!("Couldn't get timestamp from stored CSRF Token");
            session
                .remove_value(SESSION_CSRFTOKEN_FIELD)
                .await
                .map_err(|err| {
                    error!("Failed to remove CSRF token from session: {err:?}");
                    false
                })
                .unwrap();
            return false;
        }
        Some(value) => value,
    };
    let expiry: DateTime<Utc> = match DateTime::parse_from_rfc3339(expiry) {
        Err(error) => {
            log::debug!("Failed to parse {expiry:?} into datetime: {error:?}");
            session
                .remove_value(SESSION_CSRFTOKEN_FIELD)
                .await
                .map_err(|err| {
                    error!("Failed to remove CSRF token from session: {err:?}");
                    false
                })
                .unwrap();
            return false;
        }
        Ok(value) => value.into(),
    };
    let now = Utc::now();
    if expiry < now {
        log::debug!("Token has expired at {expiry:?}, time is now {now:?}");
        session
            .remove_value(SESSION_CSRFTOKEN_FIELD)
            .await
            .map_err(|err| {
                error!("Failed to remove CSRF token from session: {err:?}");
                false
            })
            .unwrap();
        return false;
    }
    log::debug!("CSRF Token was valid!");
    true
}
async fn store_api_csrf_token(
    session: &mut Session,
    expiry_plus_seconds: Option<i64>,
) -> Result<String, String> {
    let csrftoken = CsrfToken::new_random();
    let csrftoken = csrftoken.secret().to_string();
    let csrf_expiry: DateTime<Utc> =
        Utc::now() + Duration::seconds(expiry_plus_seconds.unwrap_or(300));
    let stored_csrf = format!("{csrftoken}|{}", csrf_expiry.to_rfc3339());
    if let Err(error) = session.insert(SESSION_CSRFTOKEN_FIELD, stored_csrf).await {
        return Err(format!("Failed to store CSRF Token for user: {error:?}"));
    };
    Ok(csrftoken)
}
#[debug_handler]
pub async fn api_tokens_get(
    State(state): State<GoatState>,
    mut session: Session,
) -> Result<Html<String>, Redirect> {
    let user = check_logged_in(&mut session, Uri::from_static(URI_SETTINGS_API_TOKENS)).await?;
    let csrftoken = match store_api_csrf_token(&mut session, None).await {
        Ok(value) => value,
        Err(error) => {
            log::error!("Failed to store csrf token in DB: {error:?}");
            return Err(Redirect::to("/"));
        }
    };
    let token_value: Option<String> = session.remove("new_api_token").await.unwrap();
    let tokenkey: Option<String> = session.remove("new_api_tokenkey").await.unwrap();
    let tokens =
        match UserAuthToken::get_all_user(&state.read().await.connpool, user.id.unwrap()).await {
            Err(error) => {
                log::error!("Failed to pull tokens for user {:#?}: {error:?}", user.id);
                vec![]
            }
            Ok(val) => val,
        };
    let context = ApiTokensGetPage {
        csrftoken,
        tokens,
        tokenkey,
        token_value,
        user_is_admin: user.admin,
    };
    Ok(Html::from(context.render().unwrap()))
}
#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Sequence)]
pub enum ApiTokenLifetime {
    EightHours,
    TwentyFourHours,
    ThirtyDays,
    Forever,
}
impl From<ApiTokenLifetime> for i32 {
    fn from(input: ApiTokenLifetime) -> Self {
        match input {
            ApiTokenLifetime::EightHours => 8 * 60 * 60,
            ApiTokenLifetime::TwentyFourHours => 24 * 60 * 60,
            ApiTokenLifetime::ThirtyDays => 30 * 86400,
            ApiTokenLifetime::Forever => -1,
        }
    }
}
impl Display for ApiTokenLifetime {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            ApiTokenLifetime::EightHours => "Eight Hours (8h)",
            ApiTokenLifetime::TwentyFourHours => "Twenty-Four Hours (24h)",
            ApiTokenLifetime::ThirtyDays => "Thirty Days (30d)",
            ApiTokenLifetime::Forever => "No Expiry",
        })
    }
}
impl ApiTokenLifetime {
    fn variant_str(&self) -> String {
        match self {
            ApiTokenLifetime::EightHours => "EightHours".to_string(),
            ApiTokenLifetime::TwentyFourHours => "TwentyFourHours".to_string(),
            ApiTokenLifetime::ThirtyDays => "ThirtyDays".to_string(),
            ApiTokenLifetime::Forever => "Forever".to_string(),
        }
    }
}
#[derive(Debug, Deserialize, Serialize, Eq, PartialEq)]
pub enum ApiTokenCreatePageState {
    Start,
    Generating,
    Finished,
    Error,
}
impl Display for ApiTokenCreatePageState {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ApiTokenCreatePageState::Start => f.write_fmt(format_args!("Start")),
            ApiTokenCreatePageState::Generating => f.write_fmt(format_args!("Generating")),
            ApiTokenCreatePageState::Finished => f.write_fmt(format_args!("Finished")),
            ApiTokenCreatePageState::Error => f.write_fmt(format_args!("Error")),
        }
    }
}
impl Default for ApiTokenCreatePageState {
    fn default() -> Self {
        Self::Start
    }
}
impl ApiTokenCreatePageState {
    pub fn next(&self) -> Self {
        match &self {
            ApiTokenCreatePageState::Start => Self::Generating,
            ApiTokenCreatePageState::Generating => Self::Finished,
            ApiTokenCreatePageState::Finished => Self::Error,
            ApiTokenCreatePageState::Error => Self::Start,
        }
    }
}
#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Template, Default)]
#[template(path = "user_api_token_form.html")]
pub struct ApiTokenPage {
    pub token_name: Option<String>,
    pub tokenkey: Option<String>,
    pub token_value: Option<String>,
    pub csrftoken: String,
    pub state: ApiTokenCreatePageState,
    pub lifetime: Option<ApiTokenLifetime>,
    pub lifetimes: Option<Vec<(String, String)>>,
    pub user_is_admin: bool,
}
pub async fn api_tokens_post(
    mut session: Session,
    State(state): State<GoatState>,
    Form(form): Form<ApiTokenPage>,
) -> Result<Html<String>, Redirect> {
    eprintln!("Got form: {form:?}");
    let user = check_logged_in(&mut session, Uri::from_static(URI_SETTINGS_API_TOKENS)).await?;
    if !validate_csrf_expiry(&form.csrftoken, &mut session).await {
        log::debug!("Failed to validate csrf expiry");
        return Err(Redirect::to("/ui/settings"));
    }
    let context = match form.state {
        ApiTokenCreatePageState::Start => {
            let lifetimes: Vec<(String, String)> = enum_iterator::all::<ApiTokenLifetime>()
                .map(|l| (l.to_string(), l.variant_str()))
                .collect();
            eprintln!("Lifetimes: {lifetimes:?}");
            ApiTokenPage {
                csrftoken: store_api_csrf_token(&mut session, None).await.unwrap(),
                state: ApiTokenCreatePageState::Start,
                token_name: None,
                lifetimes: Some(lifetimes),
                lifetime: None,
                tokenkey: None,
                token_value: None,
                user_is_admin: user.admin,
            }
        }
        ApiTokenCreatePageState::Generating => {
            log::debug!("In the 'Generating' state");
            let state_reader = state.read().await;
            let api_cookie_secret = state_reader.config.api_cookie_secret();
            let lifetime: i32 = form.lifetime.unwrap().into();
            let user: User = match session.get("user").await.unwrap() {
                Some(val) => val,
                None => {
                    log::debug!("Couldn't get user from session store");
                    return Err(Redirect::to("/ui"));
                }
            };
            let userid: i64 = user.id.unwrap();
            let api_token = create_api_token(api_cookie_secret, lifetime, userid);
            log::trace!("Starting to store token in the DB, grabbing writer...");
            println!("got writer...");
            let uat = UserAuthToken {
                id: None,
                name: form.token_name.unwrap(),
                issued: api_token.issued,
                expiry: api_token.expiry,
                userid,
                tokenkey: api_token.token_key.to_owned(),
                tokenhash: api_token.token_hash,
            };
            log::trace!("Starting to store token in the DB, grabbing transaction...");
            let mut txn = match state_reader.connpool.begin().await {
                Ok(val) => val,
                Err(error) => todo!(
                    "Need to handle failing to pick up a txn for api token storage: {error:?}"
                ),
            };
            log::trace!("Starting to store token in the DB, saving...");
            match uat.save_with_txn(&mut txn).await {
                Err(error) => todo!("Need to handle this! {error:?}"),
                Ok(_) => {
                    if let Err(error) = session
                        .insert("new_api_token", &api_token.token_secret)
                        .await
                    {
                        log::error!(
                            "Failed to store new API token in the session, ruh roh? {error:?}"
                        );
                        txn.rollback()
                            .await
                            .map_err(|e| {
                                log::error!("Txn rollback fail: {e:?}");
                                todo!()
                            })
                            .unwrap();
                        todo!("Failed to store new API token in the session, ruh roh? {error:?}");
                    };
                    if let Err(error) = session
                        .insert("new_api_tokenkey", &api_token.token_key)
                        .await
                    {
                        log::error!(
                            "Failed to store new API tokenkey in the session, ruh roh? {error:?}"
                        );
                        txn.rollback()
                            .await
                            .map_err(|e| {
                                log::error!("Txn rollback fail: {e:?}");
                                todo!()
                            })
                            .unwrap();
                        todo!(
                            "Failed to store new API tokenkey in the session, ruh roh? {error:?}"
                        );
                    };
                    if let Err(error) = txn.commit().await {
                        log::error!("Failed to save the API token to storage, oh no?");
                        todo!("Failed to save the API token to storage, oh no? {error:?}");
                    };
                }
            };
            let csrftoken = store_api_csrf_token(&mut session, Some(30)).await.unwrap();
            return Err(Redirect::to(&format!(
                "/ui/settings/api_tokens?state={csrftoken}?token_created=1"
            )));
        }
        ApiTokenCreatePageState::Finished => todo!(),
        ApiTokenCreatePageState::Error => todo!(),
    };
    Ok(Html::from(context.render().unwrap()))
    }
#[derive(Deserialize, Serialize, Debug, Clone, Template)]
#[template(path = "user_api_token_delete.html")]
pub struct ApiTokenDelete {
    pub id: i64,
    pub token_name: Option<String>,
    pub csrftoken: String,
    pub user_is_admin: bool,
}
const URI_SETTINGS_API_TOKENS: &str = "/ui/settings/api_tokens";
pub async fn api_tokens_delete_get(
    axum::extract::State(state): axum::extract::State<GoatState>,
    Path(id): Path<String>,
    mut session: Session,
) -> Result<Html<String>, Redirect> {
    let user = check_logged_in(&mut session, Uri::from_static(URI_SETTINGS_API_TOKENS)).await?;
    let csrftoken = match store_api_csrf_token(&mut session, None).await {
        Ok(val) => val,
        Err(err) => {
            log::error!("Failed to store CSRF token in the session store: {err:?}");
            return Err(Redirect::to(URI_SETTINGS_API_TOKENS));
        }
    };
    let id = match i64::from_str(&id) {
        Ok(val) => val,
        Err(error) => {
            log::debug!("Got an invalid id parsing the URL: {error:?}");
            return Err(Redirect::to("/"));
        }
    };
    let state_reader = state.read().await;
    let pool = state_reader.connpool.clone();
    let uat = match UserAuthToken::get(&pool, id).await {
        Err(err) => {
            log::debug!("Requested delete for token: {err:?}");
            return Err(Redirect::to(URI_SETTINGS_API_TOKENS));
        }
        Ok(res) => {
            if res.userid != user.id.unwrap() {
                log::debug!(
                    "You can't delete another user's tokens! uid={} token.userid={}",
                    user.id.unwrap(),
                    res.userid
                );
                return Err(Redirect::to(URI_SETTINGS_API_TOKENS));
            };
            res
        }
    };
    let context = ApiTokenDelete {
        id,
        token_name: Some(uat.name.clone()),
        csrftoken,
        user_is_admin: user.admin,
    };
    Ok(Html::from(context.render().unwrap()))
}
pub async fn api_tokens_delete_post(
    State(state): State<GoatState>,
    mut session: Session,
    Form(form): Form<ApiTokenDelete>,
) -> Result<Html<String>, Redirect> {
    check_logged_in(&mut session, Uri::from_static(URI_SETTINGS_API_TOKENS)).await?;
    if !validate_csrf_expiry(&form.csrftoken, &mut session).await {
        log::debug!("Failed to validate csrf expiry");
        return Err(Redirect::to("/ui/settings"));
    }
    log::debug!("Deleting token from Form: {form:?}");
    let user: User = match session.get("user").await.unwrap() {
        Some(val) => val,
        None => {
            log::debug!("Couldn't get user from session store");
            return Err(Redirect::to("/ui"));
        }
    };
    let pool = state.read().await.connpool.clone();
    let uat = match UserAuthToken::get(&pool, form.id).await {
        Err(err) => {
            log::debug!("Requested delete for existing token: {err:?}");
            return Err(Redirect::to(URI_SETTINGS_API_TOKENS));
        }
        Ok(res) => {
            if res.userid != user.id.unwrap() {
                log::debug!(
                    "You can't delete another user's tokens! uid={} token.userid={}",
                    user.id.unwrap(),
                    res.userid
                );
                return Err(Redirect::to(URI_SETTINGS_API_TOKENS));
            };
            res
        }
    };
    if let Err(error) = uat.delete(&pool).await {
        log::debug!("Failed to delete token {:?}: {error:?}", uat.id);
    };
    log::info!(
        "id={} action=api_token_delete token_id={}",
        uat.userid,
        uat.id.unwrap()
    );
    Err(Redirect::to(URI_SETTINGS_API_TOKENS))
}
pub fn router() -> Router<GoatState> {
    Router::new()
        .route("/", get(settings))
        .route("/api_tokens", get(api_tokens_get))
        .route("/api_tokens", post(api_tokens_post))
        .route("/api_tokens/delete/:id", get(api_tokens_delete_get))
        .route("/api_tokens/delete/:id", post(api_tokens_delete_post))
}