"""Sicherung und Uebertragung: verschluesselter Export, zweistufiger Import."""

from __future__ import annotations

from datetime import datetime

from flask import Blueprint, Response, flash, redirect, render_template, request, session, 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 tesm_core.licensing_client import LicenseError

from ..services import transfer

bp = Blueprint("backup", __name__, url_prefix="/sicherung")

MAX_IMPORT_BYTES = 64 * 1024 * 1024
PREVIEW_SESSION_KEY = "_import_token"


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


def _allowed_categories() -> list[transfer.Category]:
    allowed: list[transfer.Category] = []
    for category in transfer.CATEGORIES:
        if category.admin_only and not current_user.is_admin:
            continue
        if category.sensitive and not current_user.has("backup.secrets"):
            continue
        allowed.append(category)
    return allowed


@bp.get("/")
@permission_required("backup.view", "backup.export", "backup.execute", mode="any")
def overview():
    extension = core()
    conn = db()
    preview = None
    token = session.get(PREVIEW_SESSION_KEY, "")
    if token:
        try:
            payload = transfer.load_preview(conn, token, actor=current_user.username)
            preview = {"token": token, "summary": transfer.summarize(payload)}
        except transfer.TransferError:
            session.pop(PREVIEW_SESSION_KEY, None)

    license_hint = ""
    if extension.license is not None:
        try:
            extension.license.require("export")
        except LicenseError as exc:
            license_hint = str(exc)

    return render_template(
        "tesm/backup.html",
        categories=_allowed_categories(),
        all_categories=transfer.CATEGORIES,
        preview=preview,
        min_passphrase=transfer.MIN_PASSPHRASE_LENGTH,
        preview_ttl=transfer.PREVIEW_TTL_MINUTES,
        license_hint=license_hint,
        can_secrets=current_user.has("backup.secrets"),
    )


@bp.post("/export")
@permission_required("backup.export")
@reauth_required
def export():
    extension = core()
    conn = db()
    if extension.license is not None:
        try:
            extension.license.require("export")
        except LicenseError as exc:
            flash(str(exc), "danger")
            return redirect(url_for("backup.overview"))

    allowed = {category.key for category in _allowed_categories()}
    chosen = [key for key in request.form.getlist("categories") if key in allowed]
    if not chosen:
        flash("Es wurde keine Kategorie ausgewaehlt.", "warning")
        return redirect(url_for("backup.overview"))

    include_secrets = (
        request.form.get("include_secrets") == "1" and current_user.has("backup.secrets")
    )
    passphrase = request.form.get("passphrase") or ""
    repeat = request.form.get("passphrase_repeat") or ""
    if passphrase != repeat:
        flash("Die beiden Passphrasen stimmen nicht ueberein.", "danger")
        return redirect(url_for("backup.overview"))

    try:
        payload = transfer.build_export(
            conn, extension.keystore, categories=chosen, include_secrets=include_secrets
        )
        envelope = transfer.seal(payload, passphrase)
    except transfer.TransferError as exc:
        flash(str(exc), "danger")
        return redirect(url_for("backup.overview"))

    _audit(
        "backup.exported",
        f"Kategorien: {', '.join(chosen)}" + (" (mit Geheimnissen)" if include_secrets else ""),
        severity="warning" if include_secrets else "notice",
    )
    stamp = datetime.now().strftime("%Y%m%d-%H%M")
    return Response(
        envelope.encode("utf-8"),
        mimetype="application/json",
        headers={
            "Content-Disposition": f'attachment; filename="tesm-sicherung-{stamp}.json"',
            "Cache-Control": "no-store",
        },
    )


@bp.post("/import/vorschau")
@permission_required("backup.execute")
@reauth_required
def import_preview():
    conn = db()
    upload = request.files.get("backup_file")
    if upload is None or not upload.filename:
        flash("Keine Datei ausgewaehlt.", "danger")
        return redirect(url_for("backup.overview"))
    raw = upload.stream.read(MAX_IMPORT_BYTES + 1)
    if len(raw) > MAX_IMPORT_BYTES:
        flash("Die Datei ist zu gross.", "danger")
        return redirect(url_for("backup.overview"))

    try:
        payload, header = transfer.unseal(raw, request.form.get("passphrase") or "")
    except transfer.TransferError as exc:
        flash(str(exc), "danger")
        return redirect(url_for("backup.overview"))

    with transaction():
        token = transfer.store_preview(conn, payload, actor=current_user.username)
    session[PREVIEW_SESSION_KEY] = token
    _audit("backup.import_previewed", f"Erstellt {header.get('created_at')}", severity="notice")
    flash(
        f"Datei geoeffnet. Sie haben {transfer.PREVIEW_TTL_MINUTES} Minuten Zeit, die "
        "Uebernahme zu bestaetigen.",
        "success",
    )
    return redirect(url_for("backup.overview"))


@bp.post("/import/uebernehmen")
@permission_required("backup.execute")
@reauth_required
def import_apply():
    extension = core()
    conn = db()
    token = session.get(PREVIEW_SESSION_KEY, "")
    if not token:
        flash("Es liegt keine geoeffnete Sicherung vor.", "danger")
        return redirect(url_for("backup.overview"))

    allowed = {category.key for category in _allowed_categories()}
    chosen = [key for key in request.form.getlist("categories") if key in allowed]
    if not chosen:
        flash("Es wurde keine Kategorie ausgewaehlt.", "warning")
        return redirect(url_for("backup.overview"))

    try:
        payload = transfer.load_preview(conn, token, actor=current_user.username)
    except transfer.TransferError as exc:
        session.pop(PREVIEW_SESSION_KEY, None)
        flash(str(exc), "danger")
        return redirect(url_for("backup.overview"))

    # Wichtig: alles in **einer** Transaktion auf **einer** Verbindung. Der
    # Vorgaenger oeffnete hier ueber set_setting() eine zweite Verbindung und lief
    # dadurch in "database is locked".
    with transaction():
        report = transfer.apply_import(
            conn,
            extension.keystore,
            payload,
            categories=chosen,
            set_setting=lambda key, value: extension.settings.set(
                conn, key, value, actor=current_user.username
            ),
        )
        transfer.drop_preview(conn, token)
        audit.log(
            conn,
            actor=current_user.username,
            actor_id=current_user.id,
            action="backup.imported",
            detail="; ".join(
                f"{key}: {counts['created']} neu, {counts['updated']} aktualisiert, "
                f"{counts['skipped']} uebersprungen"
                for key, counts in report.items()
            ),
            ip=client_ip(),
            severity="warning",
        )
    session.pop(PREVIEW_SESSION_KEY, None)

    lines = [
        f"{transfer.CATEGORY_BY_KEY[key].label}: {counts['created']} neu, "
        f"{counts['updated']} aktualisiert"
        + (f", {counts['skipped']} uebersprungen" if counts["skipped"] else "")
        for key, counts in report.items()
        if key in transfer.CATEGORY_BY_KEY
    ]
    flash("Uebernahme abgeschlossen. " + " | ".join(lines), "success")
    return redirect(url_for("backup.overview"))


@bp.post("/import/verwerfen")
@permission_required("backup.execute")
def import_discard():
    token = session.pop(PREVIEW_SESSION_KEY, "")
    if token:
        with transaction() as conn:
            transfer.drop_preview(conn, token)
    flash("Zwischenstand verworfen.", "info")
    return redirect(url_for("backup.overview"))
