"""Benutzer- und Gruppenverwaltung -- identisch in beiden Anwendungen."""

from __future__ import annotations

from typing import Any

from flask import Blueprint, abort, flash, jsonify, redirect, render_template, request, url_for

from .. import audit
from ..auth import directory
from ..auth.models import admin_required, current_user, login_required, permission_required
from ..auth.service import client_ip
from ..db import Database
from ..extension import core, db, transaction
from ..rbac import service as rbac
from ..rbac.model import ACTION_META, Action
from ..security import sessions as session_store

bp = Blueprint("admin", __name__, url_prefix="/verwaltung")


def _audit(conn: Any, action: str, target: str = "", detail: str = "", severity: str = "info") -> None:
    audit.log(
        conn,
        actor=current_user.username,
        actor_id=current_user.id,
        action=action,
        target=target,
        detail=detail,
        ip=client_ip(),
        severity=severity,
    )


def _int_list(name: str) -> list[int]:
    values: list[int] = []
    for raw in request.form.getlist(name):
        try:
            values.append(int(raw))
        except (TypeError, ValueError):
            continue
    return values


# --------------------------------------------------------------------------- #
# Benutzer
# --------------------------------------------------------------------------- #


@bp.get("/benutzer")
@permission_required("users.view")
def users():
    conn = db()
    return render_template(
        "tesm_core/users.html",
        users=rbac.list_users(conn),
        groups=[group for group in rbac.list_groups(conn)],
        directory_enabled=directory.is_enabled(conn),
        policy=core().password_policy,
    )


@bp.post("/benutzer/neu")
@permission_required("users.create")
def user_create():
    conn = db()
    try:
        with transaction():
            user_id = rbac.create_user(
                conn,
                username=request.form.get("username", ""),
                password=request.form.get("password", ""),
                is_admin=request.form.get("is_admin") == "1",
                first_name=request.form.get("first_name", ""),
                last_name=request.form.get("last_name", ""),
                email=request.form.get("email", ""),
                group_ids=_int_list("group_ids"),
                actor=current_user,
                tree=core().permissions,
                policy=core().password_policy,
                must_change_password=request.form.get("must_change_password") == "1",
            )
            _audit(
                conn,
                "user.created",
                request.form.get("username", ""),
                "Administrator" if request.form.get("is_admin") == "1" else "Benutzer",
                severity="notice",
            )
        flash(f"Konto {request.form.get('username')} wurde angelegt.", "success")
        return redirect(url_for("admin.users", highlight=user_id))
    except (rbac.ValidationError, rbac.PermissionDenied) as exc:
        flash(str(exc), "danger")
        return redirect(url_for("admin.users"))


@bp.post("/benutzer/<int:user_id>")
@permission_required("users.edit")
def user_update(user_id: int):
    conn = db()
    try:
        with transaction():
            changes = rbac.update_user(
                conn,
                user_id,
                actor=current_user,
                tree=core().permissions,
                first_name=request.form.get("first_name"),
                last_name=request.form.get("last_name"),
                email=request.form.get("email"),
                is_admin=request.form.get("is_admin") == "1",
                is_locked=request.form.get("is_locked") == "1",
                group_ids=_int_list("group_ids") if "group_ids" in request.form else None,
            )
            if request.form.get("new_password"):
                rbac.set_password(
                    conn,
                    user_id,
                    request.form["new_password"],
                    policy=core().password_policy,
                    must_change=True,
                )
                changes.append("Passwort zurueckgesetzt")
                session_store.revoke_all_for_user(conn, user_id, reason="password-reset")
            target = rbac.get_user(conn, user_id) or {}
            if changes:
                _audit(
                    conn,
                    "user.updated",
                    str(target.get("username") or user_id),
                    "; ".join(changes),
                    severity="notice",
                )
        flash("Aenderungen gespeichert." if changes else "Keine Aenderungen.", "success" if changes else "info")
    except (rbac.ValidationError, rbac.PermissionDenied) as exc:
        flash(str(exc), "danger")
    return redirect(url_for("admin.users"))


@bp.post("/benutzer/<int:user_id>/loeschen")
@permission_required("users.delete")
def user_delete(user_id: int):
    conn = db()
    try:
        with transaction():
            target = rbac.get_user(conn, user_id) or {}
            mode = rbac.soft_delete_user(conn, user_id, actor=current_user)
            _audit(
                conn,
                "user.deleted",
                str(target.get("username") or user_id),
                f"Modus: {mode}",
                severity="warning",
            )
        flash(
            "Konto endgueltig entfernt (Verzeichniskonto)."
            if mode == "endgueltig"
            else "Konto in den Papierkorb verschoben.",
            "success",
        )
    except (rbac.ValidationError, rbac.PermissionDenied) as exc:
        flash(str(exc), "danger")
    return redirect(url_for("admin.users"))


@bp.post("/benutzer/<int:user_id>/sitzungen-beenden")
@permission_required("users.edit")
def user_revoke_sessions(user_id: int):
    conn = db()
    target = rbac.get_user(conn, user_id)
    if target is None:
        abort(404)
    rbac.guard_target_account(current_user, target)
    with transaction():
        count = session_store.revoke_all_for_user(conn, user_id, reason="admin")
        _audit(
            conn,
            "user.sessions_revoked",
            str(target["username"]),
            f"{count} Sitzungen",
            severity="notice",
        )
    flash(f"{count} Sitzung(en) beendet.", "success")
    return redirect(url_for("admin.users"))


@bp.get("/benutzer/verzeichnis-suche")
@permission_required("users.create")
def directory_search():
    conn = db()
    query = (request.args.get("q") or "").strip()
    existing = {
        str(row["username"]).lower() for row in rbac.list_users(conn, include_deleted=True)
    }
    results = [
        entry
        for entry in directory.search_directory_users(conn, query)
        if entry["username"].lower() not in existing
    ]
    return jsonify({"results": results})


@bp.post("/benutzer/verzeichnis-uebernehmen")
@permission_required("users.create")
def directory_adopt():
    """Legt ein Verzeichniskonto vorab an, damit Gruppen vor dem ersten Login sitzen."""
    conn = db()
    username = (request.form.get("username") or "").strip()
    if not username:
        flash("Kein Benutzername angegeben.", "danger")
        return redirect(url_for("admin.users"))
    try:
        with transaction():
            user_id = rbac.create_user(
                conn,
                username=username,
                password="",
                is_admin=False,
                first_name=request.form.get("first_name", ""),
                last_name=request.form.get("last_name", ""),
                email=request.form.get("email", ""),
                group_ids=_int_list("group_ids"),
                actor=current_user,
                tree=core().permissions,
                policy=core().password_policy,
                auth_source="ldap",
            )
            _audit(conn, "user.directory_adopted", username, severity="notice")
        flash(f"Verzeichniskonto {username} vorbereitet.", "success")
        return redirect(url_for("admin.users", highlight=user_id))
    except (rbac.ValidationError, rbac.PermissionDenied) as exc:
        flash(str(exc), "danger")
        return redirect(url_for("admin.users"))


# --------------------------------------------------------------------------- #
# Gruppen
# --------------------------------------------------------------------------- #


@bp.get("/gruppen")
@permission_required("groups.view")
def groups():
    conn = db()
    tree = core().permissions
    entries = rbac.list_groups(conn)
    return render_template(
        "tesm_core/groups.html",
        groups=entries,
        summaries={
            group.id: rbac.summarize_permissions(tree, group.permissions) for group in entries
        },
        tree=tree,
    )


@bp.get("/gruppen/<int:group_id>")
@permission_required("groups.view")
def group_detail(group_id: int):
    conn = db()
    group = rbac.get_group(conn, group_id)
    if group is None or group.deleted_at:
        abort(404)
    tree = core().permissions
    return render_template(
        "tesm_core/group_detail.html",
        group=group,
        tree=tree,
        state=rbac.matrix_state(tree, group.permissions),
        members=Database.all(
            conn,
            "SELECT u.id, u.username, u.first_name, u.last_name, u.is_admin, u.auth_source "
            "FROM user_groups ug JOIN users u ON u.id = ug.user_id "
            "WHERE ug.group_id=? AND u.deleted_at IS NULL ORDER BY u.username COLLATE NOCASE",
            (group_id,),
        ),
        can_edit=current_user.has("groups.edit"),
        unlocked=request.args.get("freischalten") == "1",
    )


@bp.post("/gruppen/neu")
@permission_required("groups.create")
def group_create():
    conn = db()
    try:
        with transaction():
            group_id = rbac.create_group(
                conn,
                name=request.form.get("name", ""),
                description=request.form.get("description", ""),
                permissions=request.form.getlist("permissions"),
                tree=core().permissions,
                actor=current_user,
            )
            _audit(conn, "group.created", request.form.get("name", ""), severity="notice")
        flash("Gruppe angelegt.", "success")
        return redirect(url_for("admin.group_detail", group_id=group_id))
    except (rbac.ValidationError, rbac.PermissionDenied) as exc:
        flash(str(exc), "danger")
        return redirect(url_for("admin.groups"))


@bp.post("/gruppen/<int:group_id>")
@permission_required("groups.edit")
def group_update(group_id: int):
    conn = db()
    try:
        with transaction():
            changes = rbac.update_group(
                conn,
                group_id,
                name=request.form.get("name"),
                description=request.form.get("description"),
                permissions=request.form.getlist("permissions")
                if "permissions_submitted" in request.form
                else None,
                tree=core().permissions,
                actor=current_user,
                unlock_system=request.form.get("unlock_system") == "1",
            )
            group = rbac.get_group(conn, group_id)
            if changes:
                _audit(
                    conn,
                    "group.updated",
                    group.name if group else str(group_id),
                    "; ".join(changes),
                    severity="notice",
                )
        flash("Aenderungen gespeichert." if changes else "Keine Aenderungen.", "success" if changes else "info")
    except (rbac.ValidationError, rbac.PermissionDenied) as exc:
        flash(str(exc), "danger")
    return redirect(url_for("admin.group_detail", group_id=group_id))


@bp.post("/gruppen/<int:group_id>/loeschen")
@permission_required("groups.delete")
def group_delete(group_id: int):
    conn = db()
    try:
        with transaction():
            group = rbac.get_group(conn, group_id)
            rbac.soft_delete_group(conn, group_id)
            _audit(
                conn,
                "group.deleted",
                group.name if group else str(group_id),
                severity="warning",
            )
        flash("Gruppe in den Papierkorb verschoben.", "success")
    except (rbac.ValidationError, rbac.PermissionDenied) as exc:
        flash(str(exc), "danger")
    return redirect(url_for("admin.groups"))


@bp.post("/gruppen/<int:group_id>/vorlage-anwenden")
@admin_required
def group_apply_preset(group_id: int):
    """Setzt eine Gruppe auf ihre Vorlage zurueck -- ausdruecklich, nie automatisch."""
    conn = db()
    group = rbac.get_group(conn, group_id)
    if group is None:
        abort(404)
    preset = next((p for p in core().role_presets if p.key == group.preset_key), None)
    if preset is None:
        flash("Fuer diese Gruppe ist keine Vorlage hinterlegt.", "info")
        return redirect(url_for("admin.group_detail", group_id=group_id))
    tree = core().permissions
    with transaction():
        rbac.update_group(
            conn,
            group_id,
            permissions=tree.expand_preset(preset),
            tree=tree,
            actor=current_user,
            unlock_system=True,
        )
        _audit(conn, "group.preset_applied", group.name, preset.key, severity="notice")
    flash(f"Rechte der Vorlage {preset.name} uebernommen.", "success")
    return redirect(url_for("admin.group_detail", group_id=group_id))


# --------------------------------------------------------------------------- #
# Sitzungen
# --------------------------------------------------------------------------- #


@bp.get("/sitzungen")
@admin_required
def sessions():
    conn = db()
    rows = Database.all(
        conn,
        "SELECT s.*, u.username FROM user_sessions s LEFT JOIN users u ON u.id = s.user_id "
        "WHERE s.revoked_at IS NULL AND s.idle_expires_at > datetime('now') "
        "ORDER BY s.last_seen_at DESC LIMIT 500",
    )
    return render_template("tesm_core/sessions.html", sessions=rows)


@bp.post("/sitzungen/<sid>/beenden")
@admin_required
def session_revoke(sid: str):
    with transaction() as conn:
        session_store.revoke(conn, sid, reason="admin")
        _audit(conn, "session.revoked", sid[:12], severity="notice")
    flash("Sitzung beendet.", "success")
    return redirect(url_for("admin.sessions"))


# --------------------------------------------------------------------------- #
# Navigationsreihenfolge (nur echte Administratoren)
# --------------------------------------------------------------------------- #


@bp.post("/navigation")
@admin_required
def nav_order():
    extension = core()
    order = extension.nav.sanitize_order(request.form.getlist("order"))
    child_order: dict[str, list[str]] = {}
    for item in extension.nav.items:
        if not item.children:
            continue
        submitted = request.form.getlist(f"child_order_{item.key}")
        if submitted:
            child_order[item.key] = extension.nav.sanitize_child_order(item.key, submitted)
    with transaction() as conn:
        extension.settings.set(conn, "nav_order", order, actor=current_user.username)
        extension.settings.set(conn, "nav_child_order", child_order, actor=current_user.username)
        _audit(conn, "system.nav_order_changed", severity="info")
    flash("Reihenfolge der Navigation gespeichert.", "success")
    return redirect(request.referrer or url_for("index"))


#: Kurzbezeichnungen der Aktionen -- von Templates und Audit-Texten genutzt.
ACTION_LABELS: dict[Action, str] = {
    action: meta[1] for action, meta in ACTION_META.items()
}
