"""Kommandozeilenwerkzeuge: Administrator anlegen, Zustand pruefen, Wartung.

Ersetzt ``create_db.py``/``create_admin.py`` des Vorgaengers. Die Datenbank wird
nicht mehr von einem eigenen Skript erzeugt -- das Schema entsteht beim ersten
Start durch die Migrationen.
"""

from __future__ import annotations

import argparse
import getpass
import json
import sys
from typing import Any

from tesm_core import audit
from tesm_core.cli_web import add_web_setup, run_web_setup
from tesm_core.db import Database
from tesm_core.extension import core
from tesm_core.rbac import service as rbac
from tesm_core.security import passwords

from . import create_app


def _app_and_core() -> tuple[Any, Any]:
    app = create_app()
    return app, core(app)


def cmd_create_admin(args: argparse.Namespace) -> int:
    app, extension = _app_and_core()
    username = args.username or input("Benutzername: ").strip()
    if not username:
        print("Kein Benutzername angegeben.", file=sys.stderr)
        return 2

    password = args.password
    if not password:
        password = getpass.getpass("Passwort: ")
        if password != getpass.getpass("Passwort wiederholen: "):
            print("Die Passwoerter stimmen nicht ueberein.", file=sys.stderr)
            return 2

    problems = extension.password_policy.check(password, username=username)
    if problems and not args.force:
        for problem in problems:
            print(f"- {problem}", file=sys.stderr)
        print("Mit --force trotzdem setzen.", file=sys.stderr)
        return 2

    with app.app_context(), extension.database.session() as conn:
        existing = rbac.find_user_by_username(conn, username, include_deleted=True)
        with extension.database.transaction(conn):
            if existing is not None:
                conn.execute(
                    "UPDATE users SET password_hash=?, is_admin=1, is_locked=0, deleted_at=NULL, "
                    "must_change_password=0, updated_at=datetime('now') WHERE id=?",
                    (passwords.hash_password(password), existing["id"]),
                )
                action = "aktualisiert"
                user_id = int(existing["id"])
            else:
                cursor = conn.execute(
                    "INSERT INTO users (username, password_hash, is_admin, auth_source, "
                    "created_at, updated_at, password_changed_at) "
                    "VALUES (?,?,1,'local',datetime('now'),datetime('now'),datetime('now'))",
                    (username, passwords.hash_password(password)),
                )
                user_id = int(cursor.lastrowid or 0)
                action = "angelegt"
            audit.log(
                conn,
                actor="cli",
                actor_id=user_id,
                action="user.admin_created",
                target=username,
                detail=f"ueber die Kommandozeile {action}",
                severity="warning",
            )
    print(f"Administrator {username!r} {action}.")
    return 0


def cmd_status(args: argparse.Namespace) -> int:
    app, extension = _app_and_core()
    with app.app_context(), extension.database.session() as conn:
        chain = audit.verify_chain(conn, limit=5000)
        counts = {
            "benutzer": Database.value(
                conn, "SELECT COUNT(*) FROM users WHERE deleted_at IS NULL", default=0
            ),
            "administratoren": rbac.count_active_admins(conn),
            "gruppen": Database.value(
                conn, "SELECT COUNT(*) FROM groups WHERE deleted_at IS NULL", default=0
            ),
            "geraete": Database.value(
                conn, "SELECT COUNT(*) FROM devices WHERE deleted_at IS NULL", default=0
            ),
            "switche": Database.value(
                conn, "SELECT COUNT(*) FROM switches WHERE deleted_at IS NULL", default=0
            ),
            "protokollzeilen": audit.count(conn),
        }
        license_state = None
        if extension.license is not None:
            state = extension.license.reload(conn)
            license_state = {
                "vorhanden": state.present,
                "gueltig": state.valid,
                "aktiviert": state.activated,
                "rolle": state.role,
                "module": sorted(state.modules),
                "fehler": state.error,
            }

    payload = {
        "datenbank": str(extension.config.db_path),
        "protokolle": str(extension.config.log_dir),
        "migrationen": extension.migration_status(),
        "audit_kette": chain,
        "bestand": counts,
        "lizenz": license_state,
        "systemhelfer": bool(extension.sysops and extension.sysops.available),
    }
    if args.json:
        print(json.dumps(payload, indent=2, ensure_ascii=False, default=str))
        return 0

    print(f"Datenbank      : {payload['datenbank']}")
    print(f"Protokolle     : {payload['protokolle']}")
    print(f"Systemhelfer   : {'verfuegbar' if payload['systemhelfer'] else 'nicht verfuegbar'}")
    print(
        "Audit-Kette    : "
        + ("unversehrt" if chain["ok"] else f"GEBROCHEN bei Eintrag {chain['broken_at']}")
        + f" ({chain['checked']} geprueft)"
    )
    for key, value in counts.items():
        print(f"{key.capitalize():<15}: {value}")
    if license_state:
        print(
            "Lizenz         : "
            + ("aktiv" if license_state["aktiviert"] and license_state["gueltig"] else "nicht aktiv")
            + f", Rolle {license_state['rolle']}, Module {', '.join(license_state['module']) or '-'}"
        )
        if license_state["fehler"]:
            print(f"Lizenzfehler   : {license_state['fehler']}")
    if not counts["administratoren"]:
        print("\nWARNUNG: es existiert kein entsperrtes Administratorkonto.", file=sys.stderr)
        return 1
    return 0


def cmd_housekeeping(_args: argparse.Namespace) -> int:
    app, extension = _app_and_core()
    from tesm_core.security import ratelimit
    from tesm_core.security import sessions as session_store

    with app.app_context(), extension.database.session() as conn:
        with extension.database.transaction(conn):
            sessions = session_store.prune(conn)
            events = ratelimit.prune(conn)
            archived = audit.archive_old_rows(conn, extension.audit_archive_dir)
    print(
        f"{sessions} Sitzungen entfernt, {events} Ratenereignisse entfernt, "
        f"{len(archived)} Protokolltage archiviert."
    )
    return 0


def cmd_check_now(_args: argparse.Namespace) -> int:
    app, extension = _app_and_core()
    from .services import inventory, monitor

    with app.app_context():
        allowed = extension.license is None or extension.license.feature_allowed(
            "auto_poe_restart"
        )
        report = monitor.run_sweep(
            database=extension.database,
            live=extension.registry["live_log"],
            keystore=extension.keystore,
            license_allows_auto_restart=allowed,
        )
    print(
        f"{report.checked} geprueft, {report.online} online, {report.offline} offline, "
        f"{report.restarts_triggered} Neustarts ({report.restarts_ok} erfolgreich), "
        f"{report.duration_ms} ms"
    )
    return 0


def cmd_web_setup(args: argparse.Namespace) -> int:
    app, extension = _app_and_core()
    return run_web_setup(app, extension, args)


def cmd_convert_legacy(args: argparse.Namespace) -> int:
    """Uebersetzt einen Export des alten TESM in das neue Format.

    Bewusst ohne Datenbankzugriff: heraus kommt eine Datei, die ueber den
    vorhandenen Import eingelesen wird. So sieht man vorher, was ankommt, und
    es gibt nur einen Schreibpfad in die Datenbank.
    """
    from getpass import getpass
    from pathlib import Path

    from .services import legacy_transfer as legacy
    from .services import transfer

    quelle = Path(args.datei)
    if not quelle.is_file():
        print(f"Datei nicht gefunden: {quelle}")
        return 2
    ziel = Path(args.ausgabe) if args.ausgabe else quelle.with_name(f"{quelle.stem}-neu.json")
    if ziel.exists() and not args.force:
        print(f"{ziel} existiert bereits. Mit --force ueberschreiben.")
        return 2

    alt_pass = args.passphrase or getpass("Passphrase der alten Datei: ")
    try:
        rohdaten = legacy.open_legacy(quelle.read_bytes(), alt_pass)
        nutzlast, bericht = legacy.convert(rohdaten)
    except legacy.LegacyTransferError as exc:
        print(f"Fehlgeschlagen: {exc}")
        return 1

    print(bericht.as_text())
    if args.dry_run:
        print()
        print("--dry-run: keine Datei geschrieben.")
        return 0

    neu_pass = args.neue_passphrase or getpass("Passphrase fuer die neue Datei: ")
    try:
        ziel.write_text(transfer.seal(nutzlast, neu_pass), encoding="utf-8", newline="\n")
    except Exception as exc:  # noqa: BLE001 - Passphrase zu kurz, Pfad nicht schreibbar
        print(f"Schreiben fehlgeschlagen: {exc}")
        return 1
    print()
    print(f"Geschrieben: {ziel}")
    print("Einlesen unter Verwaltung -> Sicherung mit derselben Passphrase.")
    return 0


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="tesm-admin", description="Verwaltungswerkzeuge fuer TESM"
    )
    sub = parser.add_subparsers(dest="command", required=True)

    admin = sub.add_parser("create-admin", help="Administratorkonto anlegen oder zuruecksetzen")
    admin.add_argument("--username")
    admin.add_argument("--password", help="Nur fuer automatisierte Installationen")
    admin.add_argument("--force", action="store_true", help="Passwortregeln ignorieren")
    admin.set_defaults(func=cmd_create_admin)

    status = sub.add_parser("status", help="Zustand der Installation ausgeben")
    status.add_argument("--json", action="store_true")
    status.set_defaults(func=cmd_status)

    house = sub.add_parser("housekeeping", help="Sitzungen, Ratenereignisse, Protokolle aufraeumen")
    house.set_defaults(func=cmd_housekeeping)

    check = sub.add_parser("check-now", help="Einen Ueberwachungsdurchlauf sofort ausfuehren")
    check.set_defaults(func=cmd_check_now)

    legacy = sub.add_parser(
        "convert-legacy",
        help="Export des alten TESM in das neue Format uebersetzen",
        description=(
            "Liest eine Exportdatei des Vorgaengers und schreibt eine Datei im "
            "Format dieser Anwendung. Uebersetzt werden Clients, Switche, "
            "Zugangsdaten und die DHCP-Konfiguration."
        ),
    )
    legacy.add_argument("datei", help="Exportdatei des alten TESM")
    legacy.add_argument("--ausgabe", help="Zieldatei (Vorgabe: <name>-neu.json)")
    legacy.add_argument(
        "--passphrase", help="Passphrase der alten Datei (sonst wird gefragt)"
    )
    legacy.add_argument(
        "--neue-passphrase", dest="neue_passphrase", help="Passphrase der neuen Datei"
    )
    legacy.add_argument(
        "--dry-run", action="store_true", help="Nur den Bericht zeigen, nichts schreiben"
    )
    legacy.add_argument("--force", action="store_true", help="Zieldatei ueberschreiben")
    legacy.set_defaults(func=cmd_convert_legacy)

    add_web_setup(sub, cmd_web_setup)

    return parser


def main(argv: list[str] | None = None) -> int:
    parser = build_parser()
    args = parser.parse_args(argv)
    return int(args.func(args))


if __name__ == "__main__":  # pragma: no cover
    raise SystemExit(main())
