"""Switche: Bestand pflegen, Host-Schluessel freigeben, Zustand abfragen."""

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.db import Database
from tesm_core.extension import core, db, transaction

from ..services import inventory, ssh, switchcli

bp = Blueprint("switches", __name__, url_prefix="/switche")


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 _form_values() -> dict[str, Any]:
    return {
        "hostname": request.form.get("hostname", ""),
        "ip": request.form.get("ip", ""),
        "ssh_port": request.form.get("ssh_port") or 22,
        "credential_id": request.form.get("credential_id") or None,
        "model": request.form.get("model", ""),
        "location": request.form.get("location", ""),
        "notes": request.form.get("notes", ""),
        "actor": current_user.username,
    }


@bp.get("/")
@permission_required("switches.view")
def overview():
    conn = db()
    return render_template(
        "tesm/switches.html",
        switches=inventory.list_switches(conn),
        credentials=inventory.list_credentials(conn),
        host_keys=Database.all(conn, "SELECT * FROM switch_host_keys ORDER BY host"),
        terminal_enabled=bool(core().settings.get(conn, "terminal_enabled")),
    )


@bp.post("/neu")
@permission_required("switches.create")
def create():
    try:
        with transaction() as conn:
            switch_id = inventory.create_switch(conn, **_form_values())
            _audit(
                conn,
                "switch.created",
                request.form.get("hostname", ""),
                f"IP {request.form.get('ip')}",
                severity="notice",
            )
        flash("Switch angelegt. Der Host-Schluessel muss vor dem ersten Zugriff freigegeben werden.", "success")
        return redirect(url_for("switches.detail", switch_id=switch_id))
    except inventory.ValidationError as exc:
        flash(str(exc), "danger")
        return redirect(url_for("switches.overview"))


@bp.post("/<int:switch_id>")
@permission_required("switches.edit")
def update(switch_id: int):
    try:
        with transaction() as conn:
            changes = inventory.update_switch(conn, switch_id, **_form_values())
            if changes:
                _audit(
                    conn,
                    "switch.updated",
                    request.form.get("hostname", str(switch_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(request.referrer or url_for("switches.overview"))


@bp.post("/<int:switch_id>/loeschen")
@permission_required("switches.delete")
def delete(switch_id: int):
    try:
        with transaction() as conn:
            switch = inventory.get_switch(conn, switch_id)
            if switch is None:
                abort(404)
            inventory.delete_switch(conn, switch_id)
            _audit(conn, "switch.deleted", str(switch["hostname"]), severity="warning")
        flash("Switch in den Papierkorb verschoben.", "success")
    except inventory.ValidationError as exc:
        flash(str(exc), "danger")
    return redirect(url_for("switches.overview"))


@bp.get("/<int:switch_id>")
@permission_required("switches.view")
def detail(switch_id: int):
    conn = db()
    switch = inventory.get_switch(conn, switch_id)
    if switch is None:
        abort(404)
    host_key = ssh.known_key(conn, str(switch["ip"]), int(switch["ssh_port"]), "ssh-ed25519")
    all_keys = Database.all(
        conn,
        "SELECT * FROM switch_host_keys WHERE host=? AND port=? ORDER BY key_type",
        (str(switch["ip"]), int(switch["ssh_port"])),
    )
    devices = Database.all(
        conn,
        "SELECT d.id, d.name, d.port, d.ip, COALESCE(st.state,'unknown') AS state "
        "FROM devices d LEFT JOIN device_status st ON st.device_id = d.id "
        "WHERE d.switch_id=? AND d.deleted_at IS NULL ORDER BY d.port, d.name",
        (switch_id,),
    )
    return render_template(
        "tesm/switch_detail.html",
        switch=switch,
        host_key=host_key,
        host_keys=all_keys,
        devices=devices,
        credentials=inventory.list_credentials(conn),
        safe_commands=switchcli.SAFE_COMMANDS,
        terminal_enabled=bool(core().settings.get(conn, "terminal_enabled")),
    )


# --------------------------------------------------------------------------- #
# Host-Schluessel
# --------------------------------------------------------------------------- #


@bp.post("/<int:switch_id>/hostkey/pruefen")
@permission_required("switches.edit")
def probe_host_key(switch_id: int):
    conn = db()
    switch = inventory.get_switch(conn, switch_id)
    if switch is None:
        abort(404)
    try:
        record = ssh.probe_host_key(str(switch["ip"]), int(switch["ssh_port"]))
    except ssh.SshError as exc:
        flash(str(exc), "danger")
        return redirect(url_for("switches.detail", switch_id=switch_id))

    level, message = ssh.describe_probe(conn, record)
    flash(message, level)
    return redirect(
        url_for(
            "switches.detail",
            switch_id=switch_id,
            probed_type=record.key_type,
            probed_fingerprint=record.fingerprint,
        )
    )


@bp.post("/<int:switch_id>/hostkey/freigeben")
@permission_required("switches.edit")
@reauth_required
def trust_host_key(switch_id: int):
    """Gibt einen Host-Schluessel ausdruecklich frei.

    Nur ein Mensch mit frischer Passwortbestaetigung kann das -- kein
    Hintergrundprozess und kein Automatismus.
    """
    conn = db()
    switch = inventory.get_switch(conn, switch_id)
    if switch is None:
        abort(404)
    key_type = (request.form.get("key_type") or "").strip()
    fingerprint = (request.form.get("fingerprint") or "").strip()
    if not key_type or not ssh.valid_fingerprint(fingerprint):
        flash("Es wurde kein gueltiger Fingerprint uebergeben.", "danger")
        return redirect(url_for("switches.detail", switch_id=switch_id))

    with transaction():
        ssh.trust_key(
            conn,
            host=str(switch["ip"]),
            port=int(switch["ssh_port"]),
            key_type=key_type,
            fingerprint=fingerprint,
            actor=current_user.username,
        )
        _audit(
            conn,
            "switch.host_key_trusted",
            str(switch["hostname"]),
            f"{key_type} {fingerprint}",
            severity="notice",
        )
    flash("Host-Schluessel freigegeben.", "success")
    return redirect(url_for("switches.detail", switch_id=switch_id))


@bp.post("/<int:switch_id>/hostkey/verwerfen")
@permission_required("switches.edit")
def forget_host_key(switch_id: int):
    conn = db()
    switch = inventory.get_switch(conn, switch_id)
    if switch is None:
        abort(404)
    with transaction():
        ssh.forget_key(conn, str(switch["ip"]), int(switch["ssh_port"]))
        _audit(conn, "switch.host_key_forgotten", str(switch["hostname"]), severity="warning")
    flash("Gespeicherte Host-Schluessel entfernt.", "info")
    return redirect(url_for("switches.detail", switch_id=switch_id))


# --------------------------------------------------------------------------- #
# Kommandos
# --------------------------------------------------------------------------- #


@bp.post("/<int:switch_id>/kommando")
@permission_required("switches.execute")
@reauth_required
def run_command(switch_id: int):
    """Fuehrt ein Kommando aus der Positivliste aus.

    Der schnelle, sichere Weg fuer den Alltag: kein interaktives Terminal, nur
    lesende Kommandos aus einer festen Liste. Wer wirklich konfigurieren muss,
    nutzt das Terminal -- das ebenfalls Recht und Bestaetigung verlangt und
    zusaetzlich vollstaendig mitgeschrieben wird.
    """
    extension = core()
    conn = db()
    switch = inventory.get_switch(conn, switch_id)
    if switch is None:
        abort(404)
    command = (request.form.get("command") or "").strip()
    allowed = {entry[0] for entry in switchcli.SAFE_COMMANDS}
    if command not in allowed:
        flash("Dieses Kommando steht nicht auf der Positivliste.", "danger")
        return redirect(url_for("switches.detail", switch_id=switch_id))
    if not switch["credential_id"]:
        flash("Fuer diesen Switch sind keine Zugangsdaten hinterlegt.", "danger")
        return redirect(url_for("switches.detail", switch_id=switch_id))

    try:
        username, password = inventory.credential_secret(
            conn, extension.keystore, int(switch["credential_id"])
        )
        with switchcli.SwitchCli(
            conn,
            host=str(switch["ip"]),
            port=int(switch["ssh_port"]),
            username=username,
            password=password,
        ) as cli:
            output = cli.send(command, tolerate_error=True)
    except (ssh.SshError, inventory.ValidationError) as exc:
        flash(str(exc), "danger")
        return redirect(url_for("switches.detail", switch_id=switch_id))

    with transaction():
        _audit(
            conn,
            "switch.command",
            str(switch["hostname"]),
            command,
            severity="notice",
        )
        conn.execute(
            "UPDATE switches SET last_seen_at=datetime('now') WHERE id=?", (switch_id,)
        )
    return render_template(
        "tesm/switch_command.html", switch=switch, command=command, output=output
    )
