use anyhow::Result;
use deadpool_diesel::postgres::Connection;
use diesel::prelude::*;
use uuid::Uuid;

use crate::{
    app_error::AppError, csrf::validate_csrf_token, team_memberships::TeamMembership, teams::Team,
    users::User,
};

/// Returns a ForbiddenError if user is not a member of the indicated team.
/// Intended to be used in HTTP handlers to check authorization. The team
/// struct is often useful in such cases, so it is returned if the
/// authorization check is successful.
pub async fn require_team_membership(
    current_user: &User,
    team_id: &Uuid,
    db_conn: &Connection,
) -> Result<Team, AppError> {
    let maybe_team = {
        let current_user_id = current_user.id;
        let team_id = *team_id;
        db_conn
            .interact::<_, Result<Option<(Team, _)>>>(move |conn| {
                TeamMembership::all()
                    .filter(TeamMembership::with_user_id(&current_user_id))
                    .filter(TeamMembership::with_team_id(&team_id))
                    .first(conn)
                    .optional()
                    .map_err(Into::into)
            })
            .await
            .unwrap()?
    };
    match maybe_team {
        Some((team, _)) => Ok(team),
        None => Err(AppError::ForbiddenError(
            "not a member of requested team".to_string(),
        )),
    }
}

/// Returns a ForbiddenError if the CSRF token parameters do not match an entry
/// in the database. Do not expect this function to invalidate tokens after use.
pub async fn require_valid_csrf_token(
    csrf_token: &str,
    current_user: &User,
    db_conn: &Connection,
) -> Result<(), AppError> {
    if validate_csrf_token(db_conn, csrf_token, Some(current_user.id)).await? {
        Ok(())
    } else {
        Err(AppError::ForbiddenError("invalid CSRF token".to_string()))
    }
}
