"""Der Befehl ``web-setup`` -- HTTPS einrichten, ohne nginx von Hand anzufassen.

Beide Anwendungen registrieren ihn; die Logik steht darum genau einmal hier.

Warum es diesen Befehl ueberhaupt gibt: die nginx-Site wird ausschliesslich aus
der Vorlage in :mod:`tesm_core.system.webserver` erzeugt -- das ist die
Konsequenz aus dem alias-Fehler des Vorgaengers, der jahrelang ueberlebte, weil
irgendwann jemand die Datei von Hand angelegt hatte. Ein Installationsskript,
das sich seine eigene TLS-Konfiguration zusammensetzt, waere derselbe Fehler in
neu. Stattdessen setzt dieser Befehl die Einstellungen und laesst die Anwendung
schreiben -- genau wie die Oberflaeche es tut.

Aufruf aus ``install.sh``:

    tesm-admin web-setup --https --self-signed --hsts --apply
    tesm-admin web-setup --https --domain tesm.example.org --acme-email a@b.c --apply
"""

from __future__ import annotations

import argparse
import sys
from typing import Any

from .system import webserver


def _config_from_settings(extension: Any, conn: Any) -> webserver.WebConfig:
    values = extension.settings.get_many(
        conn,
        [
            "web_domain",
            "web_https_enabled",
            "web_hsts_enabled",
            "web_redirect_http",
            "web_cert_path",
            "web_key_path",
            "web_acme_webroot",
            "web_max_body_mb",
            "web_http_port",
            "web_https_port",
        ],
    )
    return webserver.WebConfig(
        app_key=extension.config.site_key,
        upstream=str(extension.registry.get("upstream", "127.0.0.1:5000")),
        static_root=str(
            extension.registry.get("static_root", extension.config.base_dir / "static")
        ),
        domain=str(values["web_domain"] or ""),
        https_enabled=bool(values["web_https_enabled"]),
        hsts_enabled=bool(values["web_hsts_enabled"]),
        redirect_http=bool(values["web_redirect_http"]),
        cert_path=str(values["web_cert_path"] or ""),
        key_path=str(values["web_key_path"] or ""),
        acme_webroot=str(
            values["web_acme_webroot"] or f"/var/www/{extension.config.site_key}-acme"
        ),
        websocket_path=str(extension.registry.get("websocket_path", "")),
        max_body_mb=int(values["web_max_body_mb"] or 128),
        http_port=int(values["web_http_port"] or 80),
        https_port=int(values["web_https_port"] or 443),
    )


def run_web_setup(app: Any, extension: Any, args: argparse.Namespace) -> int:
    """Setzt die Webserver-Einstellungen und schreibt optional die Site-Datei."""
    sysops = extension.sysops
    with app.app_context(), extension.database.session() as conn:
        values: dict[str, Any] = {}

        if args.domain is not None:
            try:
                values["web_domain"] = webserver.validate_domain(args.domain)
            except ValueError as exc:
                print(str(exc), file=sys.stderr)
                return 2
        if args.acme_email:
            values["web_acme_email"] = args.acme_email.strip()
        if args.max_body_mb:
            values["web_max_body_mb"] = int(args.max_body_mb)
        for flag, key in (("http_port", "web_http_port"), ("https_port", "web_https_port")):
            port = getattr(args, flag, None)
            if port:
                if not 1 <= int(port) <= 65535:
                    print(f"Ungueltiger Port: {port}", file=sys.stderr)
                    return 2
                values[key] = int(port)

        with extension.database.transaction(conn):
            if values:
                extension.settings.set_many(conn, values, actor="install")

        # -- Zertifikat --------------------------------------------------- #
        config = _config_from_settings(extension, conn)
        cert: dict[str, Any] = {"ok": True, "message": "Zertifikat unveraendert."}

        if args.acme:
            if sysops is None or not sysops.available:
                print("Der Systemhelfer ist nicht verfuegbar.", file=sys.stderr)
                return 2
            cert = webserver.request_certificate(
                sysops,
                domain=config.domain,
                email=str(extension.settings.get(conn, "web_acme_email") or ""),
                webroot=config.acme_webroot,
            )
            if not cert["ok"] and args.self_signed:
                # Kein oeffentliches Zertifikat bekommen -- lieber
                # selbstsigniertes HTTPS als gar keins. Der Aufrufer erfaehrt
                # beides.
                print(f"Let's Encrypt fehlgeschlagen: {cert.get('detail', cert['message'])}")
                print("Weiche auf ein selbstsigniertes Zertifikat aus.")
                args.acme = False
            elif not cert["ok"]:
                print(cert.get("detail") or cert["message"], file=sys.stderr)
                return 1

        if args.self_signed and not args.acme:
            if sysops is None or not sysops.available:
                print("Der Systemhelfer ist nicht verfuegbar.", file=sys.stderr)
                return 2
            suggested = webserver.local_names()
            cert = webserver.create_self_signed(
                sysops,
                common_name=config.domain or (suggested[0] if suggested else "localhost"),
                alt_names=[*suggested, "localhost"],
            )
            if not cert["ok"]:
                print(cert.get("detail") or cert["message"], file=sys.stderr)
                return 1

        if cert.get("cert_path"):
            with extension.database.transaction(conn):
                extension.settings.set_many(
                    conn,
                    {"web_cert_path": cert["cert_path"], "web_key_path": cert["key_path"]},
                    actor="install",
                )
            print(cert["message"])

        # -- HTTPS-Schalter ------------------------------------------------ #
        config = _config_from_settings(extension, conn)
        if args.https:
            if not (config.cert_path and config.key_path):
                print(
                    "HTTPS verlangt Zertifikat und Schluessel -- bitte --self-signed "
                    "oder --acme angeben.",
                    file=sys.stderr,
                )
                return 2
            with extension.database.transaction(conn):
                extension.settings.set_many(
                    conn,
                    {
                        "web_https_enabled": True,
                        "web_hsts_enabled": bool(args.hsts),
                        "web_redirect_http": not args.no_redirect,
                    },
                    actor="install",
                )

        config = _config_from_settings(extension, conn)
        rendered = webserver.render(config)

        if args.print_config:
            print(rendered)
            return 0

        if not args.apply:
            print("Einstellungen gespeichert. Mit --apply wird nginx neu geschrieben.")
            return 0

        if sysops is None or not sysops.available:
            print("Der Systemhelfer ist nicht verfuegbar.", file=sys.stderr)
            return 2
        result = webserver.apply_config(sysops, rendered)
        print(result["message"])
        if not result["ok"]:
            print(result.get("detail", ""), file=sys.stderr)
            return 1
        if config.https_enabled:
            detail = f"HTTPS auf {config.https_port}, {config.http_port} leitet um"
        else:
            detail = f"HTTP auf {config.http_port}"
        print(f"nginx: {config.server_name} -> {config.upstream} -- {detail}")
        return 0


def add_web_setup(sub: Any, runner: Any) -> None:
    """Haengt ``web-setup`` an einen ``argparse``-Subparser."""
    parser = sub.add_parser(
        "web-setup",
        help="nginx und TLS einrichten (Port 80/443)",
        description=(
            "Setzt die Webserver-Einstellungen und erzeugt daraus die nginx-Site. "
            "Die Datei wird immer vollstaendig neu geschrieben -- eine von Hand "
            "bearbeitete Site-Datei gibt es in diesem Projekt bewusst nicht."
        ),
    )
    parser.add_argument("--https", action="store_true", help="HTTPS auf Port 443 aktivieren")
    parser.add_argument(
        "--self-signed",
        action="store_true",
        help="Selbstsigniertes Zertifikat erzeugen (geschlossene Netze ohne Domain)",
    )
    parser.add_argument(
        "--acme",
        action="store_true",
        help="Zertifikat per Let's Encrypt anfordern (Domain muss oeffentlich zeigen)",
    )
    parser.add_argument("--domain", help="Oeffentlicher Name dieser Installation")
    parser.add_argument("--acme-email", help="Kontaktadresse fuer Let's Encrypt")
    parser.add_argument("--hsts", action="store_true", help="HSTS-Header setzen")
    parser.add_argument(
        "--no-redirect",
        action="store_true",
        help="Port 80 nicht auf HTTPS umleiten (Sonderfall)",
    )
    parser.add_argument("--max-body-mb", type=int, help="Obergrenze fuer Uploads")
    parser.add_argument("--http-port", type=int, help="Abweichender HTTP-Port (Standard 80)")
    parser.add_argument("--https-port", type=int, help="Abweichender HTTPS-Port (Standard 443)")
    parser.add_argument("--apply", action="store_true", help="nginx schreiben, pruefen, neu laden")
    parser.add_argument(
        "--print-config", action="store_true", help="Nur die erzeugte Datei ausgeben"
    )
    parser.set_defaults(func=runner)


__all__ = ["add_web_setup", "run_web_setup"]
