"""Commande d'administration : `edcf <commande>` (voir docs/ADMINISTRATION.md)."""
from __future__ import annotations

import argparse
import getpass
import re
import sys

from sqlalchemy import select

from .core import audit, security
from .core.config import get_config
from .core.db import session_scope
from .models import User
from .services import auth as auth_service
from .services import settings as settings_service

USERNAME_RE = re.compile(r"^[a-z0-9][a-z0-9._-]{2,63}$")


def _ask_password(username: str) -> str:
    min_len = 12
    with session_scope() as db:
        min_len = int(settings_service.get(db, "password_min_length"))
    while True:
        pw = getpass.getpass("Mot de passe : ")
        problems = security.password_problems(pw, username, min_len)
        if problems:
            print("\n".join(problems), file=sys.stderr)
            continue
        if getpass.getpass("Confirmation : ") != pw:
            print("Les deux saisies diffèrent.", file=sys.stderr)
            continue
        return pw


def _user(db, username: str) -> User:  # type: ignore[no-untyped-def]
    u = db.execute(select(User).where(User.username == username.lower())).scalar_one_or_none()
    if u is None:
        sys.exit(f"Compte introuvable : {username}")
    return u


def cmd_migrate(_: argparse.Namespace) -> None:
    from alembic import command
    from alembic.config import Config
    from pathlib import Path
    root = Path(__file__).resolve().parent.parent
    cfg = Config(str(root / "alembic.ini"))
    cfg.set_main_option("script_location", str(root / "alembic"))
    command.upgrade(cfg, "head")
    print("Base de données à jour.")


def cmd_create_admin(a: argparse.Namespace) -> None:
    username = a.username.strip().lower()
    if not USERNAME_RE.match(username):
        sys.exit("Identifiant invalide (3 à 64 caractères : a-z, 0-9, point, tiret, tiret bas).")
    temp = a.generate_password
    password = security.generate_password() if temp else _ask_password(username)
    with session_scope() as db:
        if db.execute(select(User.id).where(User.username == username)).first():
            sys.exit("Cet identifiant existe déjà.")
        u = User(username=username, display_name=a.display_name.strip(), role_code=a.role, is_active=True,
                 can_export=False, password_hash=security.hash_password(password), must_change_password=bool(temp))
        db.add(u)
        db.flush()
        audit.record(db, "user.created", username="cli", target_type="user", target_id=u.id,
                     details={"identifiant": username, "role": a.role, "origine": "commande CLI"})
    print(f"Compte « {username} » créé ({'gestionnaire' if a.role == 'admin' else 'lecture seule'}).")
    if temp:
        print(f"Mot de passe provisoire (à changer à la première connexion) : {password}")
    print("La double authentification sera demandée à la première connexion (si exigée pour ce rôle).")


def cmd_set_password(a: argparse.Namespace) -> None:
    with session_scope() as db:
        u = _user(db, a.username)
        password = security.generate_password() if a.generate_password else _ask_password(u.username)
        u.password_hash = security.hash_password(password)
        u.must_change_password = bool(a.generate_password)
        n = auth_service.revoke_user_sessions(db, u.id, "mot de passe redéfini (CLI)")
        audit.record(db, "user.password_reset", username="cli", target_type="user", target_id=u.id,
                     details={"sessions_revoquees": n, "origine": "commande CLI"})
    print("Mot de passe redéfini ; sessions révoquées.")
    if a.generate_password:
        print(f"Mot de passe provisoire : {password}")


def cmd_reset_mfa(a: argparse.Namespace) -> None:
    with session_scope() as db:
        u = _user(db, a.username)
        u.mfa_enabled, u.mfa_secret_enc, u.mfa_pending_secret_enc, u.mfa_last_step = False, None, None, None
        auth_service.issue_recovery_codes(db, u)
        n = auth_service.revoke_user_sessions(db, u.id, "réinitialisation MFA (CLI)")
        audit.record(db, "user.mfa_reset", username="cli", target_type="user", target_id=u.id,
                     details={"sessions_revoquees": n, "origine": "commande CLI"})
    print("Double authentification réinitialisée : elle sera reconfigurée à la prochaine connexion.")


def cmd_set_active(a: argparse.Namespace, active: bool) -> None:
    with session_scope() as db:
        u = _user(db, a.username)
        u.is_active = active
        u.deactivated_at = None if active else auth_service.utcnow()
        n = 0 if active else auth_service.revoke_user_sessions(db, u.id, "compte désactivé (CLI)")
        audit.record(db, "user.activated" if active else "user.deactivated", username="cli", target_type="user",
                     target_id=u.id, details={"sessions_revoquees": n, "origine": "commande CLI"})
    print("Compte réactivé." if active else "Compte désactivé ; sessions révoquées.")


def cmd_revoke_sessions(a: argparse.Namespace) -> None:
    with session_scope() as db:
        if a.all:
            from sqlalchemy import update
            from .models import UserSession
            n = db.execute(update(UserSession).where(UserSession.revoked_at.is_(None))
                           .values(revoked_at=auth_service.utcnow(), revoked_reason="révocation globale (CLI)")).rowcount
            audit.record(db, "auth.all_sessions_revoked", username="cli", details={"nombre": n})
        else:
            u = _user(db, a.username)
            n = auth_service.revoke_user_sessions(db, u.id, "révocation (CLI)")
            audit.record(db, "user.sessions_revoked", username="cli", target_type="user", target_id=u.id,
                         details={"nombre": n})
    print(f"{n} session(s) révoquée(s).")


def cmd_list_users(_: argparse.Namespace) -> None:
    with session_scope() as db:
        for u in db.execute(select(User).order_by(User.role_code, User.username)).scalars():
            print(f"{u.username:24} {u.role_code:7} {'actif' if u.is_active else 'INACTIF':8} "
                  f"MFA={'oui' if u.mfa_enabled else 'non':4} dernière connexion : {u.last_login_at or '—'}")


def cmd_verify_audit(_: argparse.Namespace) -> None:
    with session_scope() as db:
        ok, count, bad = audit.verify_chain(db)
    if ok:
        print(f"Journal d'audit intègre ({count} entrées).")
    else:
        sys.exit(f"ALERTE : chaîne d'audit rompue à l'entrée {bad} ({count} entrées lues).")


def cmd_demo(a: argparse.Namespace, load: bool) -> None:
    from . import demo
    if get_config().is_production:
        sys.exit("Refusé : les données fictives ne peuvent pas être chargées ou supprimées en production.")
    with session_scope() as db:
        msg = demo.load(db) if load else demo.clear(db)
    print(msg)


def cmd_purge(_: argparse.Namespace) -> None:
    from .imports.service import purge_expired
    with session_scope() as db:
        n = purge_expired(db)
    print(f"{n} fichier(s) importé(s) purgé(s) (métadonnées et empreintes conservées).")


def cmd_worker(_: argparse.Namespace) -> None:
    from .imports.worker import run_forever
    run_forever()


def main(argv: list[str] | None = None) -> None:
    p = argparse.ArgumentParser(prog="edcf", description="Administration EDCF 52 — Bilan d'activité")
    sub = p.add_subparsers(dest="cmd", required=True)
    sub.add_parser("migrate", help="Appliquer les migrations")
    c = sub.add_parser("create-admin", help="Créer un compte gestionnaire (ou lecteur avec --role reader)")
    c.add_argument("--username", required=True)
    c.add_argument("--display-name", required=True)
    c.add_argument("--role", choices=["admin", "reader"], default="admin")
    c.add_argument("--generate-password", action="store_true",
                   help="Générer un mot de passe provisoire (changement obligatoire à la connexion)")
    s = sub.add_parser("set-password", help="Redéfinir un mot de passe")
    s.add_argument("--username", required=True)
    s.add_argument("--generate-password", action="store_true")
    for name, helptext in (("reset-mfa", "Réinitialiser la double authentification (perte du téléphone)"),
                           ("deactivate-user", "Désactiver un compte"), ("activate-user", "Réactiver un compte")):
        x = sub.add_parser(name, help=helptext)
        x.add_argument("--username", required=True)
    r = sub.add_parser("revoke-sessions", help="Révoquer les sessions d'un compte ou de tous")
    g = r.add_mutually_exclusive_group(required=True)
    g.add_argument("--username")
    g.add_argument("--all", action="store_true")
    sub.add_parser("list-users", help="Lister les comptes")
    sub.add_parser("verify-audit", help="Vérifier l'intégrité du journal d'audit")
    for name in ("demo-load", "demo-clear"):
        sub.add_parser(name, help="Charger / supprimer les DONNÉES FICTIVES de démonstration")
    sub.add_parser("purge-imports", help="Supprimer les fichiers importés au-delà de la durée de conservation")
    sub.add_parser("worker", help="Lancer le processus de traitement des imports (OCR, analyse)")
    a = p.parse_args(argv)
    {
        "migrate": cmd_migrate, "create-admin": cmd_create_admin, "set-password": cmd_set_password,
        "reset-mfa": cmd_reset_mfa, "deactivate-user": lambda x: cmd_set_active(x, False),
        "activate-user": lambda x: cmd_set_active(x, True), "revoke-sessions": cmd_revoke_sessions,
        "list-users": cmd_list_users, "verify-audit": cmd_verify_audit,
        "demo-load": lambda x: cmd_demo(x, True), "demo-clear": lambda x: cmd_demo(x, False),
        "purge-imports": cmd_purge, "worker": cmd_worker,
    }[a.cmd](a)


if __name__ == "__main__":
    main()
