use crate::{Database, MFATicket, UnvalidatedTicket, ValidatedTicket};
use revolt_result::Error;
use rocket::{
    http::Status,
    outcome::Outcome,
    request::{self, FromRequest},
    Request,
};

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

    #[allow(clippy::collapsible_match)]
    async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
        if let Some(header_mfa_ticket) = request.headers().get("x-mfa-ticket").next() {
            if let Ok(ticket) = request
                .rocket()
                .state::<Database>()
                .expect("`Database`")
                .fetch_ticket_by_token(header_mfa_ticket)
                .await
            {
                Outcome::Success(ticket)
            } else {
                Outcome::Error((Status::Unauthorized, create_error!(InvalidToken)))
            }
        } else {
            Outcome::Error((Status::Unauthorized, create_error!(MissingHeaders)))
        }
    }
}

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

    #[allow(clippy::collapsible_match)]
    async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
        match request.guard::<MFATicket>().await {
            Outcome::Success(ticket) => {
                if ticket.validated {
                    let db = request
                        .rocket()
                        .state::<Database>()
                        .expect("`Database`");

                    if ticket.claim(db).await.is_ok() {
                        Outcome::Success(ValidatedTicket(ticket))
                    } else {
                        Outcome::Error((Status::Forbidden, create_error!(InvalidToken)))
                    }
                } else {
                    Outcome::Error((Status::Forbidden, create_error!(InvalidToken)))
                }
            }
            Outcome::Forward(f) => Outcome::Forward(f),
            Outcome::Error(err) => Outcome::Error(err),
        }
    }
}

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

    #[allow(clippy::collapsible_match)]
    async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
        match request.guard::<MFATicket>().await {
            Outcome::Success(ticket) => {
                if !ticket.validated {
                    Outcome::Success(UnvalidatedTicket(ticket))
                } else {
                    Outcome::Error((Status::Forbidden, create_error!(InvalidToken)))
                }
            }
            Outcome::Forward(f) => Outcome::Forward(f),
            Outcome::Error(err) => Outcome::Error(err),
        }
    }
}
