"""Zugangsdaten: pflegen und -- mit eigenem Recht -- im Klartext ansehen."""

from __future__ import annotations

from typing import Any

from flask import Blueprint, abort, flash, redirect, render_template, request, url_for
from tesm_core import audit
from tesm_core.auth.models import current_user, permission_required, reauth_required
from tesm_core.auth.service import client_ip
from tesm_core.extension import core, db, transaction

from ..services import inventory

bp = Blueprint("credentials", __name__, url_prefix="/zugangsdaten")


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,
    )


@bp.get("/")
@permission_required("credentials.view")
def overview():
    conn = db()
    return render_template(
        "tesm/credentials.html",
        credentials=inventory.list_credentials(conn),
        categories=inventory.CREDENTIAL_CATEGORIES,
    )


@bp.post("/neu")
@permission_required("credentials.create")
def create():
    try:
        with transaction() as conn:
            inventory.create_credential(
                conn,
                core().keystore,
                name=request.form.get("name", ""),
                username=request.form.get("username", ""),
                secret=request.form.get("secret", ""),
                category=request.form.get("category", "switch"),
                description=request.form.get("description", ""),
                actor=current_user.username,
            )
            _audit(conn, "credential.created", request.form.get("name", ""), severity="notice")
        flash("Zugangsdaten angelegt.", "success")
    except inventory.ValidationError as exc:
        flash(str(exc), "danger")
    return redirect(url_for("credentials.overview"))


@bp.post("/<int:credential_id>")
@permission_required("credentials.edit")
def update(credential_id: int):
    try:
        with transaction() as conn:
            changes = inventory.update_credential(
                conn,
                core().keystore,
                credential_id,
                name=request.form.get("name", ""),
                username=request.form.get("username", ""),
                secret=request.form.get("secret") or None,
                category=request.form.get("category", "switch"),
                description=request.form.get("description", ""),
                actor=current_user.username,
            )
            if changes:
                _audit(
                    conn,
                    "credential.updated",
                    request.form.get("name", str(credential_id)),
                    "; ".join(changes),
                    severity="notice",
                )
        flash("Aenderungen gespeichert." if changes else "Keine Aenderungen.", "success" if changes else "info")
    except inventory.ValidationError as exc:
        flash(str(exc), "danger")
    return redirect(url_for("credentials.overview"))


@bp.post("/<int:credential_id>/loeschen")
@permission_required("credentials.delete")
def delete(credential_id: int):
    try:
        with transaction() as conn:
            entry = next(
                (c for c in inventory.list_credentials(conn) if c.id == credential_id), None
            )
            if entry is None:
                abort(404)
            inventory.delete_credential(conn, credential_id)
            _audit(conn, "credential.deleted", entry.name, severity="warning")
        flash("Zugangsdaten in den Papierkorb verschoben.", "success")
    except inventory.ValidationError as exc:
        flash(str(exc), "danger")
    return redirect(url_for("credentials.overview"))


@bp.post("/<int:credential_id>/anzeigen")
@permission_required("credentials.secrets")
@reauth_required
def reveal(credential_id: int):
    """Zeigt ein gespeichertes Passwort im Klartext.

    Drei Huerden, bewusst: eigenes Recht (``credentials.secrets``), frische
    Passwortbestaetigung und ein Eintrag im Aenderungsprotokoll mit Zeitpunkt,
    Benutzer und IP. Der Vorgaenger kannte keine dieser Huerden -- wer
    Zugangsdaten bearbeiten durfte, konnte sie exportieren und damit lesen.
    """
    conn = db()
    entry = next((c for c in inventory.list_credentials(conn) if c.id == credential_id), None)
    if entry is None:
        abort(404)
    try:
        username, secret = inventory.credential_secret(conn, core().keystore, credential_id)
    except inventory.ValidationError as exc:
        flash(str(exc), "danger")
        return redirect(url_for("credentials.overview"))

    with transaction():
        _audit(
            conn,
            "credential.secret_revealed",
            entry.name,
            "Klartextanzeige",
            severity="warning",
        )
    return render_template(
        "tesm/credential_reveal.html", entry=entry, username=username, secret=secret
    )
