"""A RENDSZERADMIN DASHBOARD adatai (2026-10-05, v30).  Részletes leírás Miklósnak: docs/RA_DASHBOARD.md

MIRE VAN?
    A /rendszeradmin oldal (routers/rendszeradmin_dashboard.py) ezzel a modullal állítja össze, amit kirak:
      1. a KÁRTYÁK (widgetek) számait: cégek, felhasználók, 2FA, levelezés, belépési tiltások, rendszer;
      2. az "AKTÍV PROBLÉMÁK" kártya tételeit: minden olyan állapot, amire a rendszeradminnak figyelnie kell.

SZABÁLYOK (miért néz ki így a kód):
    * CSAK OLVAS. Egyetlen függvény sem ír az adatbázisba - kivéve a `kartya_beallitasa()`, amit kizárólag egy
      POST kérés hív (a GET-nek nem szabad írnia: docs/GET_ATTEKINTES.md). A t_get_audit teszt ezt őrzi.
    * Meglévő segédeket használ (email_sor, email_beallitasok, mfa_utils, login_vedelem, licenc_utils), nem másolja
      a szabályaikat - így a dashboard ugyanazt mondja, mint a részletes oldalak.
    * Egy ellenőrzés hibája NEM ejti el az oldalt: `_biztonsagos()` a hibát egy "az ellenőrzés nem futott le"
      problématételként mutatja (és a konzolra naplózza), a többi kártya tovább működik.
    * Az "Aktív problémák" kártya MINDIG látszik, nem kapcsolható ki (megrendelői döntés). A többi kártya
      felhasználónként ki/be kapcsolható; a rejtett kártyák kulcsait a `rendszer_beallitasok` táblában tároljuk
      (kulcs: `ra_dashboard_rejtett_<user_id>`, érték: vesszővel elválasztott kártya-kulcsok).
      MIÉRT NEM A `Dictionary`-BEN? Mert a /rendszeradmin/cegek/uj az 1-es cég MINDEN szótársorát átmásolja az új
      cégekhez - a rendszeradmin saját beállítása nem való egy cég adatai közé.

A PROBLÉMATÉTEL FELÉPÍTÉSE (dict):
    kategoria - "levelezes" | "ketlepcsos" | "belepes" | "rendszer"   (a kártyán csoportosításhoz)
    szint     - "piros" (azonnali beavatkozás) | "sarga" (figyelmeztetés) | "info" (tájékoztatás, NEM számít problémának)
    darab     - az érintett elemek száma (összegezve: nem soronként listázunk)
    szoveg    - emberi leírás
    url       - ide visz a kattintás (a releváns beállító oldal)
"""
from datetime import datetime, timedelta, timezone

from sqlalchemy import func

import aktivalas_utils
import email_beallitasok
import email_sor
import licenc_utils
import login_vedelem
import mfa_utils
import models
import rendszernaplo_utils

# --- A kártyák katalógusa (a kapcsolók és a sablon is ebből dolgozik) -----------------------------------------------
# (kulcs, cím, ikon, cél-oldal). Az "problemak" kártya SZÁNDÉKOSAN nincs itt: az nem kapcsolható ki.
KARTYAK = [
    ("cegek", "Cégek", "fa-building", "/rendszeradmin/cegek"),
    ("felhasznalok", "Felhasználók", "fa-users", "/felhasznalok"),
    ("ketlepcsos", "Kétlépcsős hitelesítés", "fa-shield-halved", "/rendszeradmin/ketlepcsos"),
    ("levelezes", "Levelezés", "fa-envelope", "/rendszeradmin/email-beallitasok"),
    ("tiltasok", "Belépési tiltások", "fa-user-lock", "/rendszeradmin/kitiltasok"),
    ("rendszer", "Rendszer", "fa-server", "/rendszeradmin/rendszerinfo"),
    ("naplo", "Rendszernapló", "fa-clipboard-list", "/rendszeradmin/naplo"),     # v37: az utolsó 15 esemény; a teljes napló: rendszernaplo_utils.py
]
KARTYA_KULCSOK = [k[0] for k in KARTYAK]

# Az ütemező "életjelét" ezzel a percenkénti feladattal mérjük (main.py: email_kiurites, minden percben fut).
UTEMEZO_JELZO_FELADAT = "email_kiurites"
UTEMEZO_KESES_PERC = 10          # ennyi perc csend után "az ütemező nem fut"
SOR_MEGAKADAS_PERC = 30          # ennyi perce várakozó legrégebbi levél után "a sor megakadt"

URL_EMAIL_SOR = "/rendszeradmin/email-sor"
URL_EMAIL_BEALLITASOK = "/rendszeradmin/email-beallitasok"
URL_KETLEPCSOS = "/rendszeradmin/ketlepcsos"
URL_KITILTASOK = "/rendszeradmin/kitiltasok"
URL_FELHASZNALOK = "/felhasznalok"
URL_CEGEK = "/rendszeradmin/cegek"
URL_RENDSZERINFO = "/rendszeradmin/rendszerinfo"


def _utc_most() -> datetime:
    return datetime.now(timezone.utc).replace(tzinfo=None)      # a DB-ben tz nélküli UTC van


def _tetel(kategoria, szint, darab, szoveg, url):
    return {"kategoria": kategoria, "szint": szint, "darab": darab, "szoveg": szoveg, "url": url}


def _biztonsagos(db, kategoria, nev, fuggveny, url):
    """Lefuttat egy ellenőrzést; hiba esetén NEM dob, hanem (üres adat, [figyelmeztető tétel]) párt ad vissza."""
    try:
        return fuggveny()
    except Exception as e:                                       # pragma: no cover - védőháló
        print(f"❌ RA dashboard: a(z) {nev} ellenőrzés nem futott le: {type(e).__name__}: {e}")
        try:
            db.rollback()
        except Exception:
            pass
        return None, [_tetel(kategoria, "sarga", 1, f"A(z) {nev} ellenőrzése nem futott le (részletek a szerver naplójában).", url)]


# =============================================================================================
# 1. LEVELEZÉS
# =============================================================================================
def _levelezes(db):
    stat = email_sor.statisztika(db)
    beall = email_beallitasok.betolt(db)
    tetelek = []

    if stat["hibas"]:
        tetelek.append(_tetel("levelezes", "piros", stat["hibas"],
                              f"{stat['hibas']} levél küldése sikertelen (hibás a sorban).", URL_EMAIL_SOR))
    if stat["lejart"]:
        tetelek.append(_tetel("levelezes", "sarga", stat["lejart"],
                              f"{stat['lejart']} levél lejárt, mielőtt kimehetett volna.", URL_EMAIL_SOR))
    if not beall.konfiguralt:
        tetelek.append(_tetel("levelezes", "piros", 1,
                              "Nincs SMTP-szerver beállítva: a levelek csak szimulálva mennek ki, valójában senkihez nem jutnak el.",
                              URL_EMAIL_BEALLITASOK))
    elif not beall.jelszo_visszafejtheto:
        tetelek.append(_tetel("levelezes", "piros", 1,
                              "Az SMTP-jelszó nem fejthető vissza (megváltozott SECRET_KEY?): a levélküldés nem működhet.",
                              URL_EMAIL_BEALLITASOK))
    legregebbi = stat["legregebbi_varakozo"]
    if legregebbi is not None and legregebbi < _utc_most() - timedelta(minutes=SOR_MEGAKADAS_PERC):
        perc = int((_utc_most() - legregebbi).total_seconds() // 60)
        tetelek.append(_tetel("levelezes", "sarga", stat["varakozik"],
                              f"A levélsor megakadhatott: a legrégebbi várakozó levél {perc} perce vár "
                              f"(az óránkénti/napi limit is okozhatja).", URL_EMAIL_SOR))

    adat = dict(stat)
    adat["konfiguralt"] = beall.konfiguralt
    adat["forras"] = beall.forras
    adat["jelszo_visszafejtheto"] = beall.jelszo_visszafejtheto
    return adat, tetelek


# =============================================================================================
# 2. KÉTLÉPCSŐS HITELESÍTÉS
# =============================================================================================
def _ketlepcsos(db):
    kotelezo = mfa_utils.kotelezo_ceg_idk(db)
    osszes_ceg = db.query(models.Company).filter(models.Company.id != 1).count()
    erintettek = {}
    if kotelezo:
        for ceg in db.query(models.Company).filter(models.Company.id.in_(list(kotelezo))).all():
            for u in mfa_utils.erintett_felhasznalok(db, ceg):
                erintettek[u.id] = u

    kesz = beallitando = 0
    for u in erintettek.values():
        if not aktivalas_utils.aktivalva_e(u):
            continue                                 # az aktiválásra váró fiók még nem tud belépni: a "belépés" kategória mutatja
        allapot = mfa_utils.beallitasi_allapot(u)
        if allapot == "kesz":
            kesz += 1
        elif allapot == "nincs":
            beallitando += 1

    # "Helyreállítandó": volt megerősített beállítás, de a titok nem olvasható (pl. megváltozott SECRET_KEY) - MINDEN aktív
    # felhasználóra nézzük, a céges kapcsolótól függetlenül (a titok attól még használhatatlan).
    helyreallitando = 0
    for u in db.query(models.User).filter(models.User.aktiv.is_(True), models.User.totp_megerositve.is_(True)).all():
        if mfa_utils.beallitasi_allapot(u) == "helyreallitando":
            helyreallitando += 1

    rendszeradminok = mfa_utils.rendszeradminok(db)
    ra_vedetlen = sum(1 for u in rendszeradminok if not mfa_utils.beallitva_e(u))

    tetelek = []
    if helyreallitando:
        tetelek.append(_tetel("ketlepcsos", "piros", helyreallitando,
                              f"{helyreallitando} felhasználó 2FA-titka nem olvasható (csak a szerverről állítható vissza).",
                              URL_KETLEPCSOS))
    if beallitando:
        tetelek.append(_tetel("ketlepcsos", "sarga", beallitando,
                              f"{beallitando} felhasználó még nem állította be a kötelező 2FA-t.", URL_KETLEPCSOS))
    if ra_vedetlen:
        tetelek.append(_tetel("ketlepcsos", "sarga", ra_vedetlen,
                              f"{ra_vedetlen} rendszeradminisztrátor fiókja nincs 2FA-val védve.", URL_KETLEPCSOS))

    adat = {
        "kotelezo_cegek": len(kotelezo), "osszes_ceg": osszes_ceg,
        "erintett": len(erintettek), "kesz": kesz, "beallitando": beallitando, "helyreallitando": helyreallitando,
        "ra_osszes": len(rendszeradminok), "ra_vedett": len(rendszeradminok) - ra_vedetlen,
    }
    return adat, tetelek


# =============================================================================================
# 3. BELÉPÉSI TILTÁSOK ÉS AKTIVÁLÁS
# =============================================================================================
def _tiltasok_es_aktivalas(db):
    tiltasok = login_vedelem.aktiv_tiltasok(db)
    valos_vegleges = [t for t in tiltasok if t.valos and t.vegleges]
    valos_ideiglenes = [t for t in tiltasok if t.valos and not t.vegleges]

    most = _utc_most()
    varakozok = [u for u in db.query(models.User).filter(models.User.aktiv.is_(True)).all()
                 if not aktivalas_utils.aktivalva_e(u)]
    ervenyes_token_user_idk = {
        r[0] for r in db.query(models.AktivaloToken.user_id).filter(
            models.AktivaloToken.cel == "aktivalas", models.AktivaloToken.lejar_at > most).all()
    }
    lejart_link = [u for u in varakozok if u.id not in ervenyes_token_user_idk]

    tetelek = []
    if valos_vegleges:
        tetelek.append(_tetel("belepes", "piros", len(valos_vegleges),
                              f"{len(valos_vegleges)} valós fiók végleges belépési tiltás alatt áll (kézi feloldás kell).",
                              URL_KITILTASOK))
    if valos_ideiglenes:
        tetelek.append(_tetel("belepes", "sarga", len(valos_ideiglenes),
                              f"{len(valos_ideiglenes)} valós fiók ideiglenesen ki van tiltva.", URL_KITILTASOK))
    if varakozok:
        tetelek.append(_tetel("belepes", "sarga", len(varakozok),
                              f"{len(varakozok)} fiók aktiválásra vár.", URL_FELHASZNALOK))
    if lejart_link:
        tetelek.append(_tetel("belepes", "sarga", len(lejart_link),
                              f"{len(lejart_link)} aktiválásra váró fiók linkje lejárt (új meghívó kell).", URL_FELHASZNALOK))

    adat = {
        "tiltasok": len(tiltasok), "valos": len(valos_vegleges) + len(valos_ideiglenes),
        "vegleges": len([t for t in tiltasok if t.vegleges]),
        "aktivalasra_var": len(varakozok), "lejart_link": len(lejart_link),
    }
    return adat, tetelek


# =============================================================================================
# 4. CÉGEK / LICENC / RENDSZER
# =============================================================================================
def _cegek_licenc_rendszer(db):
    cegek = db.query(models.Company).filter(models.Company.id != 1).order_by(models.Company.nev).all()
    aktiv_cegek = [c for c in cegek if c.aktiv]
    inaktiv_cegek = [c for c in cegek if not c.aktiv]

    felhasznalok = db.query(models.User.id, models.User.aktiv, models.User.role, models.User.company_id).all()
    ra_ek = [f for f in felhasznalok if f.role == "Admin" and f.company_id == 1]
    admin_cegek = {f.company_id for f in felhasznalok if f.aktiv and f.role == "Admin"}

    licenc_tullepve = []
    licenc_hataron = []
    for c in aktiv_cegek:
        ellenorzesek = [
            (licenc_utils.gep_letszam(c.id, db), c.licenc_max_gep),
            (licenc_utils.felhasznalo_letszam(c.id, "Felhasználó", db), c.licenc_max_dolgozo),
            (licenc_utils.felhasznalo_letszam(c.id, "Emelőgép ügyintéző", db), c.licenc_max_ugyintezo),
        ]
        # limit None = korlátlan; 0 limit = nem engedélyezett (a licenc_utils szerint is "0 férőhely")
        if any(limit is not None and db_szam > limit for db_szam, limit in ellenorzesek):
            licenc_tullepve.append(c)
        elif any(limit is not None and limit > 0 and db_szam == limit for db_szam, limit in ellenorzesek):
            licenc_hataron.append(c)

    admin_nelkuli = [c for c in aktiv_cegek if c.id not in admin_cegek]

    utolso = db.query(func.max(models.UtemezettFutas.futott_at)).filter(
        models.UtemezettFutas.feladat == UTEMEZO_JELZO_FELADAT).scalar()
    if utolso is not None and utolso.tzinfo is not None:
        utolso = utolso.astimezone(timezone.utc).replace(tzinfo=None)

    tetelek = []
    if licenc_tullepve:
        tetelek.append(_tetel("rendszer", "piros", len(licenc_tullepve),
                              f"{len(licenc_tullepve)} cég túllépte a licenc-keretét.", URL_CEGEK))
    if licenc_hataron:
        tetelek.append(_tetel("rendszer", "sarga", len(licenc_hataron),
                              f"{len(licenc_hataron)} cég pontosan a licenc-keretén áll (nem vehet fel újat).", URL_CEGEK))
    if admin_nelkuli:
        tetelek.append(_tetel("rendszer", "sarga", len(admin_nelkuli),
                              f"{len(admin_nelkuli)} aktív cégnek nincs aktív adminisztrátora.", URL_CEGEK))
    if inaktiv_cegek:
        tetelek.append(_tetel("rendszer", "info", len(inaktiv_cegek),
                              f"{len(inaktiv_cegek)} inaktív cég (tájékoztatás, nem hiba).", URL_CEGEK))
    if utolso is None:
        tetelek.append(_tetel("rendszer", "sarga", 1,
                              "Az ütemező még nem hagyott nyomot az adatbázisban (nem fut, vagy frissen indult).", URL_RENDSZERINFO))
    elif utolso < _utc_most() - timedelta(minutes=UTEMEZO_KESES_PERC):
        perc = int((_utc_most() - utolso).total_seconds() // 60)
        tetelek.append(_tetel("rendszer", "piros", 1,
                              f"Az ütemező {perc} perce nem futott (levélküldés, riportok, takarítás állhat).", URL_RENDSZERINFO))

    felh_aktiv = sum(1 for f in felhasznalok if f.aktiv and not (f.role == "Admin" and f.company_id == 1))
    felh_inaktiv = sum(1 for f in felhasznalok if not f.aktiv)
    adat = {
        "cegek": len(cegek), "cegek_aktiv": len(aktiv_cegek), "cegek_inaktiv": len(inaktiv_cegek),
        "felh_aktiv": felh_aktiv, "felh_inaktiv": felh_inaktiv, "ra_db": len(ra_ek),
        "licenc_tullepve": len(licenc_tullepve), "licenc_hataron": len(licenc_hataron),
        "admin_nelkuli": len(admin_nelkuli),
        "utemezo_utolso": login_vedelem.helyi_ido_szoveg(utolso),
        "utemezo_ok": bool(utolso is not None and utolso >= _utc_most() - timedelta(minutes=UTEMEZO_KESES_PERC)),
    }
    return adat, tetelek


# =============================================================================================
# 5. ÖSSZEÁLLÍTÁS
# =============================================================================================
_SZINT_SORREND = {"piros": 0, "sarga": 1, "info": 2}


def osszeallit(db) -> dict:
    """A dashboard teljes adata: {"adatok": {...kártyánként...}, "problemak": [...], "osszesito": {...}}. Csak olvas."""
    levelezes, t1 = _biztonsagos(db, "levelezes", "levelezés", lambda: _levelezes(db), URL_EMAIL_SOR) or ({}, [])
    ketlepcsos, t2 = _biztonsagos(db, "ketlepcsos", "kétlépcsős hitelesítés", lambda: _ketlepcsos(db), URL_KETLEPCSOS) or ({}, [])
    belepes, t3 = _biztonsagos(db, "belepes", "belépési tiltások és aktiválás", lambda: _tiltasok_es_aktivalas(db), URL_KITILTASOK) or ({}, [])
    rendszer, t4 = _biztonsagos(db, "rendszer", "cégek, licenc és ütemező", lambda: _cegek_licenc_rendszer(db), URL_CEGEK) or ({}, [])

    try:                                                           # v37: a Rendszernapló kártya; hibája nem ejti el az oldalt
        naplo = rendszernaplo_utils.legutobbi(db)
    except Exception as e:                                         # pragma: no cover - védőháló
        print(f"❌ RA dashboard: a rendszernapló kártya nem állt össze: {type(e).__name__}: {e}")
        try:
            db.rollback()
        except Exception:
            pass
        naplo = None
    problemak = sorted(t1 + t2 + t3 + t4, key=lambda t: (_SZINT_SORREND[t["szint"]], t["kategoria"]))
    piros = sum(1 for t in problemak if t["szint"] == "piros")
    sarga = sum(1 for t in problemak if t["szint"] == "sarga")
    info = sum(1 for t in problemak if t["szint"] == "info")
    return {
        "adatok": {"levelezes": levelezes or {}, "ketlepcsos": ketlepcsos or {}, "belepes": belepes or {}, "rendszer": rendszer or {}},
        "problemak": problemak,
        "naplo": naplo,
        "osszesito": {
            "piros": piros, "sarga": sarga, "info": info,
            "allapot": "piros" if piros else ("sarga" if sarga else "rendben"),
        },
    }


# =============================================================================================
# 6. KÁRTYÁK KI/BE KAPCSOLÁSA (felhasználónként)
# =============================================================================================
def _kulcs(user) -> str:
    return f"ra_dashboard_rejtett_{user.id}"


def rejtett_kartyak(db, user) -> set:
    """A felhasználó által elrejtett kártyák kulcsai (ismeretlen kulcsot nem ad vissza). Csak olvas."""
    sor = db.query(models.RendszerBeallitas).filter(models.RendszerBeallitas.kulcs == _kulcs(user)).first()
    if sor is None or not sor.ertek:
        return set()
    return {k for k in sor.ertek.split(",") if k in KARTYA_KULCSOK}


def kartya_beallitasa(db, user, kulcs: str, lathato: bool) -> bool:
    """Egy kártya megjelenítése/elrejtése a felhasználónak. Hamis, ha ismeretlen a kulcs (az "problemak" kártya
    SZÁNDÉKOSAN ismeretlen itt: azt nem lehet elrejteni). Csak POST kérésből hívható."""
    if kulcs not in KARTYA_KULCSOK:
        return False
    rejtett = rejtett_kartyak(db, user)
    if lathato:
        rejtett.discard(kulcs)
    else:
        rejtett.add(kulcs)
    ertek = ",".join(k for k in KARTYA_KULCSOK if k in rejtett)          # a katalógus sorrendjében: determinisztikus
    sor = db.query(models.RendszerBeallitas).filter(models.RendszerBeallitas.kulcs == _kulcs(user)).first()
    if sor is None:
        db.add(models.RendszerBeallitas(kulcs=_kulcs(user), ertek=ertek, titkositott=False,
                                        modositva_at=datetime.now(timezone.utc), modosito_user_id=user.id))
    else:
        sor.ertek = ertek
        sor.modositva_at = datetime.now(timezone.utc)
        sor.modosito_user_id = user.id
    db.commit()
    return True
