use std::path::Path;

use super::{AppError, UserData};
use axum::{
    body::Body,
    extract::State,
    http::Request,
    middleware::Next,
    response::{IntoResponse, Redirect},
};
use axum_extra::TypedHeader;
use chrono::Utc;
use headers::Cookie;
use sqlx::SqlitePool;

pub async fn inject_user_data(
    State(db_pool): State<SqlitePool>,
    cookie: Option<TypedHeader<Cookie>>,
    mut request: Request<Body>,
    next: Next,
) -> Result<impl IntoResponse, AppError> {
    if let Some(cookie) = cookie {
        if let Some(session_token) = cookie.get("session_token") {
            let session_token: Vec<&str> = session_token.split('_').collect();
            let query: Result<(i64, i64, String), _> = sqlx::query_as(
                r#"SELECT user_id,expires_at,session_token_p2 FROM user_sessions WHERE session_token_p1=?"#,
            )
            .bind(session_token[0])
            .fetch_one(&db_pool)
            .await;

            if let Ok(query) = query {
                if let Ok(session_token_p2_db) = query.2.as_bytes().try_into() {
                    if let Ok(session_token_p2_cookie) = session_token
                        .get(1)
                        .copied()
                        .unwrap_or_default()
                        .as_bytes()
                        .try_into()
                    {
                        if constant_time_eq::constant_time_eq_n::<36>(
                            session_token_p2_cookie,
                            session_token_p2_db,
                        ) {
                            let id = query.0;
                            let expires_at = query.1;
                            if expires_at > Utc::now().timestamp() {
                                let row = sqlx::query_as!(
                                    UserData,
                                    "SELECT * FROM users WHERE id = ?",
                                    id
                                )
                                .fetch_one(&db_pool)
                                .await?;

                                request.extensions_mut().insert(Some(UserData {
                                    id: row.id,
                                    email: row.email,
                                    name: row.name,
                                    family_name: row.family_name,
                                    given_name: row.given_name,
                                }));
                            }
                        }
                    }
                }
            }
        }
    }

    Ok(next.run(request).await)
}

pub async fn check_auth(
    State(app_state): State<SqlitePool>,
    request: Request<Body>,
    next: Next,
) -> Result<impl IntoResponse, AppError> {
    if request
        .extensions()
        .get::<Option<UserData>>()
        .ok_or("check_auth: extensions have no UserData")?
        .is_some()
    {
        let path = &*request.uri().to_string();
        println!("{}", path);
        println!(
            "{}",
            &*request
                .extensions()
                .get::<Option<UserData>>()
                .unwrap()
                .as_ref()
                .unwrap()
                .email
        );

        let query: Result<(i64,), _> = match path {
            "/profile" => {
                sqlx::query_as(r#"select u.id from users u where email =?"#)
                    .bind(
                        request
                            .extensions()
                            .get::<Option<UserData>>()
                            .unwrap()
                            .as_ref()
                            .unwrap()
                            .email
                            .as_str(),
                    )
                    .fetch_one(&app_state)
                    .await
            }
            _ => {
                // loop through path to find a permission
                let mut remaining_path = Path::new(path);
                loop {
                    let query: Result<(String,), _> = sqlx::query_as(r#"select r.item from role_permissions r join user_roles ur on ur.role_id = r.role_id join users u on u.id = ur.user_id where item = ? and email =?"#)
                    .bind(remaining_path.to_str().unwrap())
                    .bind(request.extensions().get::<Option<UserData>>().unwrap().as_ref().unwrap().email.as_str())
                    .fetch_one(&app_state)
                    .await;
                    if let Ok(query) = query {
                        if query.0 != "" {
                            break;
                        }
                    }
                    if remaining_path.parent().is_none() {
                        break;
                    }
                    remaining_path = remaining_path.parent().unwrap();

                    println!("{}", remaining_path.to_str().unwrap());
                }
                sqlx::query_as(r#"select u.id from role_permissions r join user_roles ur on ur.role_id = r.role_id join users u on u.id = ur.user_id where item = ? and email =?"#)
            .bind(remaining_path.to_str().unwrap())
            .bind(request.extensions().get::<Option<UserData>>().unwrap().as_ref().unwrap().email.as_str())
            .fetch_one(&app_state)
            .await
            }
        };

        // user is logged in
        // check if user has the proper role
        // if not, display banner and return to home page
        let user_id = if let Ok(query) = query {
            query.0
        } else {
            // user does not have the proper role
            0
        };

        println!("{}", user_id);
        if user_id == 0 {
            // user does not have the proper role
            // display banner and return to home page
            return Ok(Redirect::to("/").into_response());
        }

        Ok(next.run(request).await)
    } else {
        // user is not logged in
        // redirect to login page with return_url
        let login_url = "/login?return_url=".to_owned() + &*request.uri().to_string();
        Ok(Redirect::to(login_url.as_str()).into_response())
    }
}
