use std::sync::Arc;

use axum::body::Bytes;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Redirect};
use axum_extra::extract::CookieJar;
use axum_extra::extract::cookie::{Cookie, SameSite};
use cookie::time::Duration;

use base64::{Engine as B64Engine, engine::general_purpose::STANDARD as b64};
use configinator::AuthConfig;

// ?? evaluate creating read-only tokens to be used for templates ('__Host-' sent on every
// ?? request), and action tokens ('__Secure-' to be sent for only specific paths corresponding
// ?? to actions).
fn access_token_cookie(
    jar: CookieJar,
    token: &Bytes,
    auth_config: &AuthConfig,
    secure: bool,
) -> CookieJar {
    let name = if secure {
        tracing::info!("generated host access cookie");
        "__Host-ORDINARY-ACCESS-TOKEN"
    } else {
        tracing::warn!("generated insecure access cookie");
        "ORDINARY-ACCESS-TOKEN"
    };

    jar.add(
        Cookie::build((name, b64.encode(token)))
            .secure(secure)
            .http_only(true)
            .path("/")
            .same_site(SameSite::Strict)
            .max_age(Duration::seconds(i64::from(
                auth_config.access_token.lifetime,
            ))),
    )
}

pub async fn fetch_access_cookies(
    State(state): State<Arc<crate::server::AppState>>,
    jar: CookieJar,
    body: Bytes,
) -> impl IntoResponse {
    let span = tracing::info_span!("token", flv = "cookie");

    span.in_scope(|| match state.auth.access_get(body) {
        Ok(token) => {
            let cookie = if state.auth.config.cookies_enabled {
                Some(access_token_cookie(
                    jar,
                    &token,
                    &state.auth.config,
                    state.secure_cookies,
                ))
            } else {
                None
            };

            (StatusCode::OK, cookie, token)
        }
        Err(e) => {
            tracing::warn!("{e}");
            (StatusCode::UNAUTHORIZED, None, Bytes::new())
        }
    })
}

pub async fn fetch_access(
    State(state): State<Arc<crate::server::AppState>>,
    body: Bytes,
) -> impl IntoResponse {
    let span = tracing::info_span!("token", flv = "fetch");

    span.in_scope(|| match state.auth.access_get(body) {
        Ok(token) => (StatusCode::OK, token),
        Err(e) => {
            tracing::warn!("{e}");
            (StatusCode::UNAUTHORIZED, Bytes::new())
        }
    })
}

pub async fn redirect_access(
    State(state): State<Arc<crate::server::AppState>>,
    jar: CookieJar,
) -> Result<(CookieJar, Redirect), StatusCode> {
    let span = tracing::info_span!("token", flv = "redirect");

    let cookie_name = if state.secure_cookies {
        "__Secure-ORDINARY-REFRESH-TOKEN"
    } else {
        "ORDINARY-REFRESH-TOKEN"
    };

    span.in_scope(|| {
        if let Some(refresh_token) = jar.get(cookie_name) {
            if let Ok(refresh_token) = b64.decode(refresh_token.value()) {
                if let Ok(access_token) = state
                    .auth
                    .access_get(Bytes::copy_from_slice(&refresh_token[..]))
                {
                    return Ok((
                        access_token_cookie(
                            jar,
                            &access_token,
                            &state.auth.config,
                            state.secure_cookies,
                        ),
                        Redirect::to("/"),
                    ));
                }
            } else {
                tracing::error!("failed to decode refresh cookie");
            }
        } else {
            tracing::error!("no refresh cookie");
        }

        Err(StatusCode::UNAUTHORIZED)
    })
}
