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

use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, decode, encode};
use rand::Rng;
use rocket::{
    Request,
    http::Status,
    request::{self, FromRequest, Outcome},
};
use rocket_db_pools::Connection;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256, digest::block_buffer::Lazy};
use sqlx::postgres::PgQueryResult;

use crate::db::Postgres;

static JWT_SECRET: LazyLock<String> = LazyLock::new(|| std::env::var("JWT_SECRET").unwrap());

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum TokenScope {
    Full,
    TotpPending,
}

pub struct Session {
    pub user_id: usize,
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for Session {
    type Error = ();

    async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        match Claims::from_request(req).await {
            Outcome::Success(user) if user.scope == TokenScope::Full => Outcome::Success(Session {
                user_id: user.sub as usize,
            }),
            Outcome::Success(_) => {
                eprintln!("warning: user with scope other than Full attempted to access session");
                Outcome::Error((Status::Forbidden, ()))
            }
            Outcome::Error(err) => {
                eprintln!("Session request guard failed: {:?}", err);
                Outcome::Error(err)
            }
            _ => unreachable!("forward should never be called"),
        }
    }
}

#[derive(Serialize, Deserialize)]
pub struct Claims {
    pub sub: i32,
    pub exp: usize,
    pub scope: TokenScope,
}

impl Claims {
    pub fn new(user_id: usize, scope: TokenScope) -> Self {
        Self {
            sub: user_id as i32,
            exp: (SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs()
                + 3600) as usize,
            scope,
        }
    }

    pub fn encode(&self) -> String {
        encode(
            &Header::default(),
            self,
            &EncodingKey::from_secret(JWT_SECRET.as_bytes()),
        )
        .expect("unable to encode jwt")
    }
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for Claims {
    type Error = ();

    async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        let token = req
            .headers()
            .get_one("Authorization")
            .and_then(|v| v.strip_prefix("Bearer "));

        match token {
            None => Outcome::Error((Status::Unauthorized, ())),
            Some(t) => {
                match decode::<Claims>(
                    t,
                    &DecodingKey::from_secret(JWT_SECRET.as_bytes()),
                    &Validation::default(),
                ) {
                    Ok(data) => Outcome::Success(data.claims),
                    Err(_) => Outcome::Error((Status::Unauthorized, ())),
                }
            }
        }
    }
}
