use std::collections::HashMap;

use crate::model::{
    family::Family,
    log::Log,
    logbook::Logbook,
    role::Role,
    user::{
        AdminUser, User, UserWithMembershipPdf, UserWithRoles, UserWithRolesAndMembershipPdf,
        VorstandUser,
    },
};
use futures::future::join_all;
use rocket::{
    form::Form,
    fs::TempFile,
    get,
    http::{ContentType, Status},
    post,
    request::{FlashMessage, FromRequest, Outcome},
    response::{Flash, Redirect},
    routes, FromForm, Request, Route, State,
};
use rocket_dyn_templates::{tera::Context, Template};
use sqlx::SqlitePool;

// Custom request guard to extract the Referer header
struct Referer(String);

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

    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        match request.headers().get_one("Referer") {
            Some(referer) => Outcome::Success(Referer(referer.to_string())),
            None => Outcome::Error((Status::BadRequest, ())),
        }
    }
}

#[get("/user")]
async fn index(
    db: &State<SqlitePool>,
    user: VorstandUser,
    flash: Option<FlashMessage<'_>>,
) -> Template {
    let user_futures: Vec<_> = User::all(db)
        .await
        .into_iter()
        .map(|u| async move { UserWithRolesAndMembershipPdf::from_user(db, u).await })
        .collect();

    let user: User = user.into();
    let allowed_to_edit = user.has_role(db, "admin").await;

    let users: Vec<UserWithRolesAndMembershipPdf> = join_all(user_futures).await;

    let roles = Role::all(db).await;
    let families = Family::all_with_members(db).await;

    let mut context = Context::new();
    if let Some(msg) = flash {
        context.insert("flash", &msg.into_inner());
    }
    context.insert("allowed_to_edit", &allowed_to_edit);
    context.insert("users", &users);
    context.insert("roles", &roles);
    context.insert("families", &families);
    context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await);

    Template::render("admin/user/index", context.into_json())
}

#[get("/user", rank = 2)]
async fn index_admin(
    db: &State<SqlitePool>,
    user: AdminUser,
    flash: Option<FlashMessage<'_>>,
) -> Template {
    let user_futures: Vec<_> = User::all(db)
        .await
        .into_iter()
        .map(|u| async move { UserWithRolesAndMembershipPdf::from_user(db, u).await })
        .collect();
    let users: Vec<UserWithRolesAndMembershipPdf> = join_all(user_futures).await;

    let user: User = user.user;
    let allowed_to_edit = user.has_role(db, "admin").await;

    let roles = Role::all(db).await;
    let families = Family::all_with_members(db).await;

    let mut context = Context::new();
    if let Some(msg) = flash {
        context.insert("flash", &msg.into_inner());
    }
    context.insert("allowed_to_edit", &allowed_to_edit);
    context.insert("users", &users);
    context.insert("roles", &roles);
    context.insert("families", &families);
    context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await);

    Template::render("admin/user/index", context.into_json())
}

#[get("/user/fees")]
async fn fees(
    db: &State<SqlitePool>,
    admin: VorstandUser,
    flash: Option<FlashMessage<'_>>,
) -> Template {
    let mut context = Context::new();

    let users = User::all_payer_groups(db).await;
    let mut fees = Vec::new();
    for user in users {
        if let Some(fee) = user.fee(db).await {
            fees.push(fee);
        }
    }

    context.insert("fees", &fees);

    if let Some(msg) = flash {
        context.insert("flash", &msg.into_inner());
    }
    context.insert(
        "loggedin_user",
        &UserWithRoles::from_user(admin.into(), db).await,
    );

    Template::render("admin/user/fees", context.into_json())
}

#[get("/user/scheckbuch")]
async fn scheckbuch(
    db: &State<SqlitePool>,
    user: VorstandUser,
    flash: Option<FlashMessage<'_>>,
) -> Template {
    let mut context = Context::new();

    let scheckbooks = Role::find_by_name(db, "scheckbuch").await.unwrap();
    let scheckbooks = User::all_with_role(db, &scheckbooks).await;
    let mut scheckbooks_with_roles = Vec::new();
    for s in scheckbooks {
        scheckbooks_with_roles.push((
            Logbook::completed_with_user(db, &s).await,
            UserWithRoles::from_user(s, db).await,
        ))
    }

    context.insert("scheckbooks", &scheckbooks_with_roles);

    if let Some(msg) = flash {
        context.insert("flash", &msg.into_inner());
    }
    context.insert(
        "loggedin_user",
        &UserWithRoles::from_user(user.into(), db).await,
    );

    Template::render("admin/user/scheckbuch", context.into_json())
}

#[get("/user/fees/paid?<user_ids>")]
async fn fees_paid(
    db: &State<SqlitePool>,
    admin: AdminUser,
    user_ids: Vec<i32>,
    referer: Referer,
) -> Flash<Redirect> {
    let mut res = String::new();
    for user_id in user_ids {
        let user = User::find_by_id(db, user_id).await.unwrap();
        res.push_str(&format!("{} + ", user.name));
        if user.has_role(db, "paid").await {
            Log::create(
                db,
                format!("{} set fees NOT paid for '{}'", admin.user.name, user.name),
            )
            .await;
            user.remove_role(db, &Role::find_by_name(db, "paid").await.unwrap())
                .await;
        } else {
            Log::create(
                db,
                format!("{} set fees paid for '{}'", admin.user.name, user.name),
            )
            .await;
            user.add_role(db, &Role::find_by_name(db, "paid").await.unwrap())
                .await;
        }
    }

    res.truncate(res.len() - 3); // remove ' + ' from the end

    Flash::success(
        Redirect::to(referer.0),
        format!("Zahlungsstatus von {} erfolgreich geändert", res),
    )
}

#[get("/user/<user>/reset-pw")]
async fn resetpw(db: &State<SqlitePool>, _admin: AdminUser, user: i32) -> Flash<Redirect> {
    let user = User::find_by_id(db, user).await;
    match user {
        Some(user) => {
            user.reset_pw(db).await;
            Flash::success(
                Redirect::to("/admin/user"),
                format!("Passwort von {} zurückgesetzt", user.name),
            )
        }
        None => Flash::error(Redirect::to("/admin/user"), "User does not exist"),
    }
}

#[get("/user/<user>/delete")]
async fn delete(db: &State<SqlitePool>, admin: AdminUser, user: i32) -> Flash<Redirect> {
    let user = User::find_by_id(db, user).await;
    Log::create(db, format!("{} deleted user: {user:?}", admin.user.name)).await;
    match user {
        Some(user) => {
            user.delete(db).await;
            Flash::success(
                Redirect::to("/admin/user"),
                format!("Benutzer {} gelöscht", user.name),
            )
        }
        None => Flash::error(Redirect::to("/admin/user"), "User does not exist"),
    }
}

#[derive(FromForm, Debug)]
pub struct UserEditForm<'a> {
    pub(crate) id: i32,
    pub(crate) dob: Option<String>,
    pub(crate) weight: Option<String>,
    pub(crate) sex: Option<String>,
    pub(crate) roles: HashMap<String, String>,
    pub(crate) member_since_date: Option<String>,
    pub(crate) birthdate: Option<String>,
    pub(crate) mail: Option<String>,
    pub(crate) nickname: Option<String>,
    pub(crate) notes: Option<String>,
    pub(crate) phone: Option<String>,
    pub(crate) address: Option<String>,
    pub(crate) family_id: Option<i64>,
    pub(crate) membership_pdf: Option<TempFile<'a>>,
}

#[post("/user", data = "<data>", format = "multipart/form-data")]
async fn update(
    db: &State<SqlitePool>,
    data: Form<UserEditForm<'_>>,
    admin: AdminUser,
) -> Flash<Redirect> {
    let user = User::find_by_id(db, data.id).await;
    Log::create(
        db,
        format!("{} updated user from {user:?} to {data:?}", admin.user.name),
    )
    .await;
    let Some(user) = user else {
        return Flash::error(
            Redirect::to("/admin/user"),
            format!("User with ID {} does not exist!", data.id),
        );
    };

    user.update(db, data.into_inner()).await;

    Flash::success(Redirect::to("/admin/user"), "Successfully updated user")
}

#[get("/user/<user>/membership")]
async fn download_membership_pdf(
    db: &State<SqlitePool>,
    admin: AdminUser,
    user: i32,
) -> (ContentType, Vec<u8>) {
    let user = User::find_by_id(db, user).await.unwrap();
    let user = UserWithMembershipPdf::from(db, user).await;
    Log::create(
        db,
        format!(
            "{} downloaded membership application for user: {user:?}",
            admin.user.name
        ),
    )
    .await;

    (ContentType::PDF, user.membership_pdf.unwrap())
}

#[derive(FromForm, Debug)]
struct UserAddForm<'r> {
    name: &'r str,
}

#[post("/user/new", data = "<data>")]
async fn create(
    db: &State<SqlitePool>,
    data: Form<UserAddForm<'_>>,
    admin: AdminUser,
) -> Flash<Redirect> {
    if User::create(db, data.name).await {
        Log::create(
            db,
            format!("{} created new user: {data:?}", admin.user.name),
        )
        .await;
        Flash::success(Redirect::to("/admin/user"), "Successfully created user")
    } else {
        Flash::error(
            Redirect::to("/admin/user"),
            format!("User {} already exists", data.name),
        )
    }
}

pub fn routes() -> Vec<Route> {
    routes![
        index,
        index_admin,
        resetpw,
        update,
        create,
        delete,
        fees,
        fees_paid,
        scheckbuch,
        download_membership_pdf
    ]
}
