Skip to content
use actix_web::{HttpRequest, HttpResponse, Result as AwResult, get, post, web};

use super::render_layout;
use crate::auth::{Exercise, ExerciseLog, Goal, HodisContext, User};
use crate::config::Server;

#[get("/admin/backup")]
pub async fn admin_backup_handler(
    req: HttpRequest,
    auth_state: web::Data<HodisContext>,
    config: web::Data<Server>,
) -> AwResult<HttpResponse> {
    if !crate::auth::is_admin_request(&req, &auth_state, &config).await {
        return Ok(HttpResponse::Unauthorized()
            .insert_header(("WWW-Authenticate", "Basic realm=\"admin\""))
            .body("Unauthorized"));
    }

    let db_path = config.db_path();
    let data = match std::fs::read(db_path) {
        Ok(data) => data,
        Err(e) => {
            log::error!("Failed to read database file for backup: {e}");
            return Ok(HttpResponse::InternalServerError().body("Failed to read database"));
        }
    };

    let filename = format!(
        "hodis-backup-{}.db",
        chrono::Utc::now().format("%Y-%m-%d")
    );

    Ok(HttpResponse::Ok()
        .content_type("application/x-sqlite3")
        .insert_header(("Content-Disposition", format!("attachment; filename=\"{filename}\"")))
        .body(data))
}

pub fn render_invites_list(invite_links: &[(String, String)]) -> maud::Markup {
    maud::html! {
        @if invite_links.is_empty() {
            p class="black-50 f6 tc" { "Keine aktiven Einladungslinks" }
        } @else {
            @for (code, created) in invite_links {
                div class="flex items-center justify-between pa2 bb b--black-10" {
                    a href={"/auth/signup?code="(code)} class="f6 fw6 brand no-underline underline-hover" { "/auth/signup?code="(code) }
                    span class="f6 black-40 ml2" { (created.split('T').next().unwrap_or("")) }
                    form hx-post="/admin/invites/delete" hx-target="#invites-list" hx-swap="innerHTML" class="dib ma0" style="display:inline;" {
                        input type="hidden" name="code" value=(code);
                        button type="submit" class="f7 red bn bg-transparent pointer underline dim" { "Entfernen" }
                    }
                }
            }
        }
    }
}

pub fn render_exercises_list(exercises: &[Exercise]) -> maud::Markup {
    maud::html! {
        @if exercises.is_empty() {
            p class="black-50 f6 tc" { "Noch keine Übungen" }
        } @else {
            @for ex in exercises {
                div class="flex items-center justify-between pa2 bb b--black-10" {
                    div class="flex-auto" {
                        span class="f6 fw6" { (ex.name) }
                        span class="f6 black-50 ml2" { (format!("{:.1} pts/{}", ex.points_per_unit, ex.unit)) }
                        @if !ex.description.is_empty() {
                            br;
                            span class="f7 black-40" { (ex.description) }
                        }
                    }
                    form hx-post="/admin/exercises/delete" hx-target="#exercises-list" hx-swap="innerHTML" class="dib ma0" style="display:inline;" {
                        input type="hidden" name="id" value=(ex.id);
                        button type="submit" class="f7 red bn bg-transparent pointer underline dim" { "Löschen" }
                    }
                }
            }
        }
    }
}

#[allow(clippy::too_many_arguments)]
pub fn render_admin_content(
    invite_links: &[(String, String)],
    exercises: &[Exercise],
    goal: &Option<Goal>,
    total_points: f64,
    all_logs: &[ExerciseLog],
    all_users: &[User],
) -> maud::Markup {
    maud::html! {
        div id="admin-content" class="mw7 center" style="padding: 0 1rem;" {
            h1 class="f3 fw6 mb4" style="color: #e3001a;" { "Admin-Bereich" }

            div class="mb4" {
                h2 class="f4 fw6 mb2 black-70" { "Ziel" }
                @if let Some(g) = goal {
                    div class="ba b--black-20 br2 pa3 mb2" {
                        div class="flex justify-between items-center mb2" {
                            span class="fw6" { (g.name) }
                            div class="flex items-center" {
                                form hx-post="/admin/goals/edit" hx-target="#admin-content" hx-swap="outerHTML" class="dib ma0 flex items-center" style="display:inline-flex;" {
                                    input type="hidden" name="id" value=(g.id);
                                    input type="number" name="target_points" value=(format!("{:.0}", g.target_points)) step="1" min="1" class="db pa1 ba b--black-30 br1 f6" style="width: 70px;";
                                    button type="submit" class="f7 btn-brand br1 bn pointer pa1 ml1" { "Speichern" }
                                }
                                form hx-post="/admin/goals/delete" hx-target="#goal-result" hx-swap="innerHTML" class="dib ma0 ml2" style="display:inline;" {
                                    input type="hidden" name="id" value=(g.id);
                                    button type="submit" class="f7 red bn bg-transparent pointer underline dim" { "Löschen" }
                                }
                            }
                        }
                        div class="w-100 bg-light-gray br2" style="height: 12px;" {
                            @let pct = if g.target_points > 0.0 { (total_points / g.target_points * 100.0).min(100.0) } else { 0.0 };
                            div class="bg-brand br2" style=(format!("width: {:.1}%; height: 12px;", pct)) {}
                        }
                        p class="f6 black-50 mt1" { (format!("{:.1} / {:.0} Punkte ({:.1}%)", total_points, g.target_points, pct)) }
                    }
                } @else {
                    p class="black-50 f6" { "Noch kein Ziel gesetzt" }
                }
                form hx-post="/admin/goals/create" hx-swap="innerHTML" hx-target="#goal-result" class="ba b--black-20 br2 pa3" {
                    div class="mb3" {
                        label class="db f6 black-70 mb1" for="goal-name" { "Zielname" }
                        input type="text" name="name" id="goal-name" required class="db w-100 pa2 ba b--black-30 br1" placeholder="z.B. Frühjahrs-Challenge";
                    }
                    div class="mb3" {
                        label class="db f6 black-70 mb1" for="goal-target" { "Zielpunkte" }
                        input type="number" name="target_points" id="goal-target" required step="0.1" min="0.1" class="db w-100 pa2 ba b--black-30 br1" placeholder="z.B. 1000";
                    }
                    button type="submit" class="pa2 btn-brand br1 f6 fw5" { "Ziel setzen" }
                }
                div id="goal-result" class="mt2" {}
            }

            div class="mb4" {
                h2 class="f4 fw6 mb2 black-70" { "Datenbank-Backup" }
                p class="f6 black-50 mb2" { "Lade eine Kopie der aktuellen Datenbank herunter." }
                a href="/admin/backup" class="pa2 btn-brand br1 f6 fw5 no-underline underline-hover" download { "Backup herunterladen" }
            }

            div class="mb4" {
                h2 class="f4 fw6 mb2 black-70" { "Einladungslinks" }
                p class="f6 black-50 mb2" { "Erstelle Einladungslinks für neue Nutzer. Links bleiben gültig bis sie gelöscht werden." }
                form hx-post="/admin/invites/create" hx-target="#invites-list" hx-swap="innerHTML" class="mb3" {
                    button type="submit" class="pa2 btn-brand br1 f6 fw5" { "Neuen Einladungslink erstellen" }
                }
                div class="ba b--black-20 br2" {
                    div id="invites-list" {
                        (render_invites_list(invite_links))
                    }
                }
            }

            div class="mb4" {
                h2 class="f4 fw6 mb2 black-70" { "Übungen" }
                form hx-post="/admin/exercises/create" hx-target="#exercises-list" hx-swap="innerHTML" class="ba b--black-20 br2 pa3 mb3" {
                    div class="mb3" {
                        label class="db f6 black-70 mb1" for="ex-name" { "Name" }
                        input type="text" name="name" id="ex-name" required class="db w-100 pa2 ba b--black-30 br1" placeholder="z.B. Laufen";
                    }
                    div class="mb3" {
                        label class="db f6 black-70 mb1" for="ex-desc" { "Beschreibung" }
                        input type="text" name="description" id="ex-desc" class="db w-100 pa2 ba b--black-30 br1" placeholder="Optionale Beschreibung";
                    }
                    div class="mb3 flex" {
                        div class="flex-auto mr2" {
                            label class="db f6 black-70 mb1" for="ex-unit" { "Einheit (km, Wdh., usw.)" }
                            input type="text" name="unit" id="ex-unit" value="reps" class="db w-100 pa2 ba b--black-30 br1";
                        }
                        div style="width: 120px;" {
                            label class="db f6 black-70 mb1" for="ex-pts" { "Punkte/Einheit" }
                            input type="number" name="points_per_unit" id="ex-pts" value="1" step="0.1" min="0.1" required class="db w-100 pa2 ba b--black-30 br1";
                        }
                    }
                    button type="submit" class="pa2 btn-brand br1 f6 fw5" { "Übung hinzufügen" }
                }
                div class="ba b--black-20 br2" {
                    div id="exercises-list" {
                        (render_exercises_list(exercises))
                    }
                }
            }

            div class="mb4" {
                h2 class="f4 fw6 mb2 black-70" { "Registrierte Nutzer" }
                @if all_users.is_empty() {
                    p class="black-50 f6" { "Noch keine Nutzer" }
                } @else {
                    div class="ba b--black-20 br2 overflow-hidden" {
                        div class="flex pa2 bb b--black-10 bg-light-gray" {
                            div class="flex-auto fw6 f6" { "Benutzername" }
                            div class="f6 fw6" style="width: 100px;" { "Rolle" }
                            div class="f6 fw6" style="width: 100px;" { "Aktion" }
                            div class="f6 fw6 tr" style="width: 100px;" { "Beigetreten" }
                        }
                        @for user in all_users {
                            div class="flex pa2 bb b--black-10 items-center" {
                                div class="flex-auto" {
                                    span class="f6" { (&user.username) }
                                }
                                div style="width: 100px;" {
                                    @if user.is_admin {
                                        span class="f6 fw6 brand" { "Admin" }
                                    } @else {
                                        span class="f6 black-50" { "Nutzer" }
                                    }
                                }
                                div style="width: 100px;" {
                                    form hx-post="/admin/users/toggle-admin" hx-target="#admin-content" hx-swap="outerHTML" class="dib ma0" style="display:inline;" {
                                        input type="hidden" name="user_id" value=(user.id);
                                        input type="hidden" name="is_admin" value=(if user.is_admin { "false" } else { "true" });
                                        @if user.is_admin {
                                            button type="submit" class="f7 red bn bg-transparent pointer underline dim" { "Admin entfernen" }
                                        } @else {
                                            button type="submit" class="f7 brand bn bg-transparent pointer underline dim" { "Zum Admin" }
                                        }
                                    }
                                }
                                div class="tr black-40 f6" style="width: 100px;" {
                                    (user.created_at.split('T').next().unwrap_or(""))
                                }
                            }
                        }
                    }
                }
            }

            div class="mb4" {
                h2 class="f4 fw6 mb2 black-70" { "Letzte Aktivitäten" }
                @if all_logs.is_empty() {
                    p class="black-50 f6" { "Noch keine Aktivitäten" }
                } @else {
                    div class="ba b--black-20 br2 overflow-hidden" {
                        div class="flex pa2 bb b--black-10 bg-light-gray" {
                            div class="flex-auto fw6 f6" { "Übung" }
                            div class="fw6 f6" style="width: 80px;" { "Menge" }
                            div class="fw6 f6" style="width: 80px;" { "Punkte" }
                            div class="fw6 f6 tr" style="width: 100px;" { "Datum" }
                        }
                        @for log in all_logs.iter().take(20) {
                            div class="flex pa2 bb b--black-10 items-center" {
                                div class="flex-auto f6" { (&log.exercise_id[..8]) }
                                div class="f6" style="width: 80px;" { (format!("{:.1}", log.amount)) }
                                div class="f6" style="width: 80px;" { (format!("{:.1}", log.points_earned)) }
                                div class="tr black-40 f6" style="width: 100px;" { (log.created_at.split('T').next().unwrap_or("")) }
                            }
                        }
                    }
                }
            }
        }
    }
}

#[get("/admin")]
pub async fn admin_page(
    req: HttpRequest,
    auth_state: web::Data<HodisContext>,
    config: web::Data<Server>,
) -> AwResult<maud::Markup> {
    let is_admin = crate::auth::is_admin_request(&req, &auth_state, &config).await;

    if !is_admin {
        return Ok(maud::html! {
            script {
                "window.location.href = '/admin-login';"
            }
        });
    }

    let invite_links = auth_state
        .db()
        .get_all_invite_links()
        .await
        .unwrap_or_default();
    let exercises = auth_state
        .db()
        .get_all_exercises()
        .await
        .unwrap_or_default();
    let goal = auth_state.db().get_active_goal().await.unwrap_or_default();
    let total_points = auth_state.db().get_total_points().await.unwrap_or(0.0);
    let all_logs = auth_state
        .db()
        .get_all_exercise_logs()
        .await
        .unwrap_or_default();
    let all_users = auth_state.db().get_all_users().await.unwrap_or_default();

    let content = render_admin_content(
        &invite_links,
        &exercises,
        &goal,
        total_points,
        &all_logs,
        &all_users,
    );

    Ok(render_layout(&content, None, false))
}

#[get("/admin-login")]
pub async fn admin_login_page(req: HttpRequest) -> AwResult<maud::Markup> {
    let content = maud::html! {
        div class="flex flex-column items-center justify-center" style="min-height: calc(100vh - 8rem); padding: 2rem 1rem;" {
            div class="w-100 mw6" {
                h1 class="f3 fw6 mb4 tc" style="color: #e3001a;" { "Admin-Anmeldung" }

                p class="f6 black-60 tc mb3" {
                    "Gib deine Admin-Zugangsdaten ein, um auf den Admin-Bereich zuzugreifen."
                }

                form method="POST" action="/admin" class="ba b--black-20 br2 pa4 bg-white" {
                    div class="mb3" {
                        label class="db f6 black-70 mb2" for="admin-user" { "Benutzername" }
                        input type="text" name="username" id="admin-user" required autocomplete="username" class="db w-100 pa2 ba b--black-30 br1";
                    }
                    div class="mb4" {
                        label class="db f6 black-70 mb2" for="admin-pass" { "Passwort" }
                        input type="password" name="password" id="admin-pass" required autocomplete="current-password" class="db w-100 pa2 ba b--black-30 br1";
                    }
                    button type="submit" class="w-100 pa2 btn-brand br1 f5 fw6" {
                        "Als Admin anmelden"
                    }
                }

                div class="mt3 tc" {
                    a href="/" class="brand no-underline f6 underline" { "Zurück zur Startseite" }
                }
            }
        }
    };

    if req.headers().get("HX-Request").is_some() {
        Ok(content)
    } else {
        Ok(render_layout(&content, None, false))
    }
}

#[derive(Debug, serde::Deserialize)]
pub struct AdminLoginForm {
    pub username: String,
    pub password: String,
}

#[post("/admin")]
pub async fn admin_login_handler(
    req: HttpRequest,
    form: web::Form<AdminLoginForm>,
    config: web::Data<Server>,
) -> Result<HttpResponse, actix_web::Error> {
    if form.username == config.admin_username() && form.password == config.admin_password() {
        use base64::Engine;
        let credentials = base64::engine::general_purpose::STANDARD
            .encode(format!("{}:{}", form.username, form.password));

        let cookie = actix_web::cookie::Cookie::build("admin_auth", credentials)
            .path("/")
            .http_only(true)
            .same_site(actix_web::cookie::SameSite::Strict)
            .finish();

        if req.headers().get("HX-Request").is_some() {
            Ok(HttpResponse::Ok()
                .insert_header(("HX-Redirect", "/admin"))
                .cookie(cookie)
                .finish())
        } else {
            Ok(HttpResponse::Found()
                .insert_header(("Location", "/admin"))
                .cookie(cookie)
                .finish())
        }
    } else {
        Ok(HttpResponse::Found()
            .insert_header(("Location", "/admin-login"))
            .finish())
    }
}