"""Anmelde-Routen: Login, zweiter Faktor, Abmeldung, erneute Bestaetigung."""

from __future__ import annotations

from urllib.parse import urlparse

from flask import Blueprint, flash, redirect, render_template, request, session, url_for

from ..extension import core, db
from . import directory, service
from .models import current_user, login_required

bp = Blueprint("auth", __name__)


def _safe_next(candidate: str | None, fallback_endpoint: str = "index") -> str:
    """Verhindert Open Redirects: nur pfad-relative Ziele derselben Anwendung."""
    default = url_for(fallback_endpoint)
    if not candidate:
        return default
    parsed = urlparse(candidate)
    if parsed.scheme or parsed.netloc or not candidate.startswith("/"):
        return default
    return candidate


@bp.route("/login", methods=["GET", "POST"])
def login():
    if current_user.is_authenticated:
        return redirect(_safe_next(session.pop("_after_login", None)))

    conn = db()
    username = ""
    if request.method == "POST":
        username = (request.form.get("username") or "").strip()
        password = request.form.get("password") or ""
        outcome = service.authenticate(
            conn, username, password, ip=service.client_ip()
        )

        if outcome.status == "needs_totp" and outcome.user_row:
            service.start_totp_challenge(outcome.user_row)
            return redirect(url_for("auth.second_factor"))

        if outcome.ok and outcome.user_row:
            user = service.complete_login(
                conn,
                outcome.user_row,
                ip=service.client_ip(),
                secret=password,
                group_dns=outcome.group_dns,
            )
            flash(f"Willkommen, {user.display_name}.", "success")
            if user.must_change_password:
                flash("Bitte legen Sie zuerst ein neues Passwort fest.", "warning")
                return redirect(url_for("account.security"))
            return redirect(_safe_next(session.pop("_after_login", None)))

        flash(outcome.message or service.GENERIC_FAILURE, "danger")

    return render_template(
        "tesm_core/login.html",
        username=username,
        directory_enabled=directory.is_enabled(conn),
        public_dashboard_url=core().registry.get("public_dashboard_url", ""),
    )


@bp.route("/login/zweiter-faktor", methods=["GET", "POST"])
def second_factor():
    conn = db()
    user_row = service.pending_totp_user(conn)
    if user_row is None:
        flash("Die Anmeldung ist abgelaufen. Bitte erneut versuchen.", "info")
        return redirect(url_for("auth.login"))

    if request.method == "POST":
        outcome = service.verify_second_factor(
            conn, user_row, request.form.get("code") or "", ip=service.client_ip()
        )
        if outcome.ok:
            # Kein ``secret``: das Passwort liegt seit dem ersten Schritt nicht
            # mehr vor, und es fuer den zweiten Schritt in der Sitzung
            # zwischenzulagern waere ein schlechter Tausch. Freigaben, die die
            # Anmeldung des Benutzers weiterreichen, binden sich bei aktivem
            # zweitem Faktor deshalb nicht ein -- Freigaben mit hinterlegtem
            # Konto schon.
            user = service.complete_login(conn, user_row, ip=service.client_ip())
            flash(f"Willkommen, {user.display_name}.", "success")
            return redirect(_safe_next(session.pop("_after_login", None)))
        flash(outcome.message, "danger")

    return render_template("tesm_core/second_factor.html")


@bp.post("/logout")
@login_required
def logout():
    service.logout(db())
    flash("Sie wurden abgemeldet.", "info")
    return redirect(url_for("auth.login"))


@bp.get("/logout")
def logout_get():
    """Abmeldung ueber einen Link -- fuer den Abbruch des 2FA-Schritts.

    Aendert nur die eigene Sitzung und ist damit kein CSRF-relevanter Vorgang
    mit Datenwirkung; die eigentliche Abmeldung im angemeldeten Bereich laeuft
    trotzdem ueber POST mit Token.
    """
    if current_user.is_authenticated:
        service.logout(db())
    else:
        session.clear()
    return redirect(url_for("auth.login"))


@bp.route("/bestaetigen", methods=["GET", "POST"])
@login_required
def reauth():
    target = _safe_next(session.get("_after_reauth"))
    if request.method == "POST":
        password = request.form.get("password") or ""
        if service.confirm_password(db(), current_user, password):
            service.mark_reauth()
            session.pop("_after_reauth", None)
            return redirect(target)
        flash("Das Passwort ist nicht korrekt.", "danger")

    return render_template(
        "tesm_core/reauth.html",
        cancel_url=url_for("index"),
        reauth_minutes=core().config.security.reauth_valid_minutes,
    )
