use crate::{auth::RequestExt as _, db, AppState, LayoutData, Request, RequestExt as _};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use surf::http::mime;

#[async_trait]
pub trait RequestExt {
    async fn require_admin(&self) -> tide::Result<()>;
}

#[async_trait]
impl RequestExt for Request {
    async fn require_admin(&self) -> tide::Result<()> {
        let current_user = self
            .current_user()
            .await?
            .ok_or_else(|| tide::Error::from_str(401, "not logged in"))?;

        if current_user.is_admin {
            Ok(())
        } else {
            Err(tide::Error::from_str(
                403,
                "authenticated user is not an admin",
            ))
        }
    }
}

pub fn add_routes(app: &mut tide::Server<Arc<AppState>>) {
    app.at("/admin").get(get_admin_page);
    app.at("/users").post(post_user);
    app.at("/users/:id").put(put_user);
    app.at("/users/:id/delete").post(delete_user);
    app.at("/signups/:id/delete").post(delete_signup);
}

#[derive(Serialize)]
struct AdminData {
    #[serde(flatten)]
    layout: Arc<LayoutData>,
    users: Vec<db::User>,
    signups: Vec<db::Signup>,
}

async fn get_admin_page(mut request: Request) -> tide::Result {
    request.require_admin().await?;

    let data = AdminData {
        layout: request.layout_data().await?,
        users: request.db().get_all_users().await?,
        signups: request.db().get_all_signups().await?,
    };

    Ok(tide::Response::builder(200)
        .body(request.state().render_template("admin.hbs", &data)?)
        .content_type(mime::HTML)
        .build())
}

async fn post_user(mut request: Request) -> tide::Result {
    request.require_admin().await?;

    #[derive(Deserialize)]
    struct Form {
        github_login: String,
        #[serde(default)]
        admin: bool,
    }

    let form = request.body_form::<Form>().await?;
    let github_login = form
        .github_login
        .strip_prefix("@")
        .unwrap_or(&form.github_login);

    if !github_login.is_empty() {
        request.db().create_user(github_login, form.admin).await?;
    }

    Ok(tide::Redirect::new("/admin").into())
}

async fn put_user(mut request: Request) -> tide::Result {
    request.require_admin().await?;

    let user_id = request.param("id")?.parse()?;

    #[derive(Deserialize)]
    struct Body {
        admin: bool,
    }

    let body: Body = request.body_json().await?;

    request
        .db()
        .set_user_is_admin(db::UserId(user_id), body.admin)
        .await?;

    Ok(tide::Response::builder(200).build())
}

async fn delete_user(request: Request) -> tide::Result {
    request.require_admin().await?;
    let user_id = db::UserId(request.param("id")?.parse()?);
    request.db().delete_user(user_id).await?;
    Ok(tide::Redirect::new("/admin").into())
}

async fn delete_signup(request: Request) -> tide::Result {
    request.require_admin().await?;
    let signup_id = db::SignupId(request.param("id")?.parse()?);
    request.db().delete_signup(signup_id).await?;
    Ok(tide::Redirect::new("/admin").into())
}
