"""Rendszeradmin: a kétlépcsős hitelesítés (2FA) cégenkénti kapcsolója + a saját beállítás előre elvégzése
(2026-10-04, 2FA 4. fázis; leírás: docs/KETLEPCSOS_HITELESITES.md, 5d szakasz).

OLDAL:    GET  /rendszeradmin/ketlepcsos                       - cégek kapcsolói, érintett felhasználók, rendszeradminok állapota
KAPCSOLÓ: POST /rendszeradmin/ketlepcsos/ceg/{ceg_id}          - egy cég "2FA kötelező" kapcsolójának be-/kikapcsolása
SAJÁT:    POST /rendszeradmin/ketlepcsos/sajat/inditas         - a SAJÁT 2FA-beállítás indítása (QR + kézi kulcs, még nem kötelező)
          POST /rendszeradmin/ketlepcsos/sajat/megerosites     - a SAJÁT beállítás megerősítése az első kóddal

JOGOSULTSÁG: kizárólag a valódi rendszeradminisztrátor (security.is_rendszeradminisztrator: Admin ÉS company_id == 1). Minden
végpont külön ellenőrzi. Az állapotot módosító végpontok POST-ok (a SameSite=Lax süti miatt GET-tel nem lehetne védeni).

A KIZÁRÁS ELLENI VÉDELEM (a lényeg, megrendelői döntés: "a szuperadmin NEM zárhatja ki magát"):
    Egy cég kapcsolóját BEKAPCSOLNI csak akkor lehet, ha minden olyan aktív rendszeradminnak, akire a kapcsoló vonatkozna, a 2FA-beállítása
    már KÉSZ (mfa_utils.kizaras_vedelem). Ezért kell a "SAJÁT" beállítás: a rendszeradmin a kapcsoló bekapcsolása ELŐTT, önként is
    beállíthatja a hitelesítő alkalmazását, és a kapcsoló csak ezután kapcsolható be. KIKAPCSOLNI mindig lehet.

Az ide tartozó értesítések/üzenetek kódosak (?ok=, ?hiba=): az oldal sosem tükrözi vissza a kérésből érkező szöveget.
"""
from typing import Optional

from fastapi import APIRouter, Request, Depends, Form
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy.orm import Session

import login_vedelem
import mentokod_utils
import mfa_utils
import models
from config import ACCESS_TOKEN_EXPIRE_MINUTES
from database import get_db
from routers import mfa as mfa_router
from security import create_access_token, get_client_ip, get_current_user, is_rendszeradminisztrator, verify_password
from templates_env import templates

router = APIRouter()

OLDAL = "/rendszeradmin/ketlepcsos"
HIBAS_JELSZO_UZENET = "Hibás jelszó."

# A kódok -> felhasználónak szóló üzenetek (a lekérdezési paraméterből SOHA nem jelenítünk meg szabad szöveget).
JELZESEK_OK = {
    "be": "A kétlépcsős hitelesítés a cégnél KÖTELEZŐ lett. A cég felhasználói a következő belépéskor (a már belépettek legfeljebb 15 mp után) kódot kérnek, vagy beállítják a hitelesítő alkalmazásukat.",
    "ki": "A kétlépcsős hitelesítés a cégnél már NEM kötelező.",
    "nincs_valtozas": "A kapcsoló már ebben az állapotban volt - nem történt változás.",
    "sajat_kesz": "A saját kétlépcsős hitelesítésed beállítva. Mostantól a cégek kapcsolói biztonságosan bekapcsolhatók.",
}
JELZESEK_HIBA = {
    "vedelem": "A kapcsoló NEM kapcsolható be: legalább egy rendszeradminnak, akire vonatkozna, még nincs kész a kétlépcsős beállítása (lásd az alábbi táblázatot). Előbb mindegyikük állítsa be a sajátját - ez védi a rendszeradmint a kizárástól.",
    "megerosites": "A bekapcsoláshoz be kell jelölni a megerősítést (a cég minden felhasználójára kötelező lesz).",
    "nem_talalhato": "A cég nem található.",
    "csak_olvasas": "Csak megtekintési jogosultsággal rendelkezel, a kapcsolók módosítása nem lehetséges.",
    "nincs_inditas": "A beállítás nincs elindítva (vagy már megerősített): indítsd újra a QR-kód kérésével.",
    "eredet": "A kérés nem erről az oldalról érkezett, ezért nem hajtottuk végre. Használd az oldal saját gombjait.",
}


def _kapu(current_user) -> bool:
    return is_rendszeradminisztrator(current_user)


def _idegen_eredet(request: Request) -> bool:
    """Mélységi védelem a böngészős CSRF ellen (külső átnézés találata): a SameSite=Lax süti a külső (más "site") oldalakat kizárja, de
    egy testvér-aldomain vagy egy ott talált hiba még küldhetne kérést. A modern böngészők minden kéréshez küldenek `Sec-Fetch-Site`
    fejlécet: a `same-origin` (saját oldal) és a `none` (felhasználó közvetlen műveletéből) jó, minden más (`cross-site`, `same-site`) elutasítva.
    Ha a fejléc hiányzik (régi böngésző, egyszerű HTTP-kliens), a kérést nem utasítjuk el - a fő védelem a SameSite süti és a jelszó."""
    sfs = request.headers.get("sec-fetch-site")
    return sfs is not None and sfs not in ("same-origin", "none")


_sajat_allapot = mfa_utils.beallitasi_allapot      # a nyers titok oszlopot csak a mfa_utils érinti (a t_mfa M11 teszt ezt őrzi)


def _oldal(request: Request, db: Session, current_user: models.User, ok: str = None, hiba: str = None,
           beallitas: dict = None, hiba_szoveg: str = None):
    """Az áttekintő oldal. `ok`/`hiba`: üzenetkódok (a JELZESEK_* szótárak szerint; ismeretlen kód nem jelenik meg).
    `hiba_szoveg`: a SZERVER által előállított hibaüzenet (pl. hibás kód) - kérésből érkező szöveg ide soha nem kerül.
    `beallitas`: ha a saját beállítás folyamatban van, a QR és a kézi kulcs (a válasz ilyenkor no-store + no-referrer)."""
    cegek = db.query(models.Company).order_by(models.Company.nev).all()
    sorok = []
    for c in cegek:
        erintettek = mfa_utils.erintett_felhasznalok(db, c)
        kesz = sum(1 for u in erintettek if mfa_utils.beallitva_e(u))
        blokkolok = mfa_utils.kizaras_vedelem(db, c) if not c.ketlepcsos_hitelesites_kotelezo else []
        sorok.append({
            "ceg": c,
            "kotelezo": bool(c.ketlepcsos_hitelesites_kotelezo),
            "valtozott": login_vedelem.helyi_ido_szoveg(c.mfa_politika_valtozott_at) if c.mfa_politika_valtozott_at else None,
            "erintett_szam": len(erintettek),
            "kesz_szam": kesz,
            "hianyzo_szam": len(erintettek) - kesz,
            "blokkolok": [u.email for u in blokkolok],
        })

    rendszeradminok = [{
        "user": u,
        "allapot": _sajat_allapot(u),
        "sajat": u.id == current_user.id,
    } for u in mfa_utils.rendszeradminok(db)]

    sajat = _sajat_allapot(current_user)
    return templates.TemplateResponse(
        request=request,
        name="rendszeradmin_ketlepcsos.html",
        context={
            "user": current_user,
            "sorok": sorok,
            "rendszeradminok": rendszeradminok,
            "sajat_allapot": sajat,
            "beallitas": beallitas,
            "ok_uzenet": JELZESEK_OK.get(ok),
            "hiba_uzenet": hiba_szoveg or JELZESEK_HIBA.get(hiba),
            "gyorsitotar_mp": mfa_utils.GYORSITOTAR_MASODPERC,
        },
        headers=mfa_router._BEALLITAS_FEJLECEK if beallitas is not None else {"Cache-Control": "no-store"},
    )


@router.get(OLDAL, response_class=HTMLResponse)
def ketlepcsos_oldal(
    request: Request,
    ok: Optional[str] = None,
    hiba: Optional[str] = None,
    db: Session = Depends(get_db),
    current_user: models.User = Depends(get_current_user)
):
    if not _kapu(current_user):
        return RedirectResponse(url="/valassz-modult", status_code=303)
    return _oldal(request, db, current_user, ok=ok, hiba=hiba)


@router.post(OLDAL + "/ceg/{ceg_id}")
def ceg_kapcsolo(
    request: Request,
    ceg_id: int,
    ertek: str = Form(""),
    megerosites: str = Form(""),
    db: Session = Depends(get_db),
    current_user: models.User = Depends(get_current_user)
):
    """Egy cég "2FA kötelező" kapcsolója. `ertek`: "be" / "ki". Bekapcsoláshoz a `megerosites` jelölő és a kizárás elleni védelem kell."""
    if not _kapu(current_user):
        return RedirectResponse(url="/valassz-modult", status_code=303)
    if _idegen_eredet(request):
        return RedirectResponse(url=f"{OLDAL}?hiba=eredet", status_code=303)
    if current_user.csak_olvasas:
        return RedirectResponse(url=f"{OLDAL}?hiba=csak_olvasas", status_code=303)

    ceg = db.get(models.Company, ceg_id)
    if not ceg:
        return RedirectResponse(url=f"{OLDAL}?hiba=nem_talalhato", status_code=303)

    bekapcsol = (ertek == "be")
    if ertek not in ("be", "ki"):
        return RedirectResponse(url=OLDAL, status_code=303)

    if bool(ceg.ketlepcsos_hitelesites_kotelezo) == bekapcsol:
        return RedirectResponse(url=f"{OLDAL}?ok=nincs_valtozas", status_code=303)      # már ebben az állapotban van (dupla kattintás)

    if bekapcsol:
        if megerosites != "on":
            return RedirectResponse(url=f"{OLDAL}?hiba=megerosites", status_code=303)
        # KIZÁRÁS ELLENI VÉDELEM: a bekapcsolás előtt, a legfrissebb adatból (nem a gyorsítótárból).
        if mfa_utils.kizaras_vedelem(db, ceg):
            return RedirectResponse(url=f"{OLDAL}?hiba=vedelem", status_code=303)

    regi = "kötelező" if ceg.ketlepcsos_hitelesites_kotelezo else "nem kötelező"
    mfa_utils.ceg_kapcsolo_beallitasa(db, ceg, bekapcsol)

    uj = "kötelező" if bekapcsol else "nem kötelező"
    megjegyzes = " (inaktív cég: a beállítás él, a cég aktiválásakor lép életbe)" if not ceg.aktiv else ""
    db.add(models.AuditLog(
        company_id=ceg.id, user_id=current_user.id, field_name="Kétlépcsős hitelesítés kötelező",
        old_value=regi, new_value=f"{uj}{megjegyzes}"
    ))
    db.commit()
    return RedirectResponse(url=f"{OLDAL}?ok={'be' if bekapcsol else 'ki'}", status_code=303)


# ---------------------------------------------------------------------------------------------
# A rendszeradmin SAJÁT beállítása - előre, önként (a kapcsoló bekapcsolása előtt)
# ---------------------------------------------------------------------------------------------
def _sajat_beallitas_oldal(request: Request, db: Session, current_user: models.User, titok: str, hiba_szoveg: str = None):
    return _oldal(request, db, current_user, hiba_szoveg=hiba_szoveg, beallitas={
        "qr": mfa_utils.qr_png_data_uri(mfa_utils.provisioning_uri(current_user, titok)),
        "kulcs": mfa_router._kulcs_csoportositva(titok),
    })


@router.post(OLDAL + "/sajat/inditas")
def sajat_beallitas_inditasa(
    request: Request,
    jelszo: str = Form(""),
    db: Session = Depends(get_db),
    current_user: models.User = Depends(get_current_user)
):
    """Új (megerősítetlen) titok készítése a SAJÁT fiókra, és a QR mutatása. Minden indítás ÚJ titkot ad (ugyanaz az elv, mint
    a /mfa/beallitas/inditas végponton, 5c.2). Megerősített beállítást NEM ír felül (ellopott munkamenettel ne lehessen második faktort cserélni):
    ilyenkor a szerveren a mfa_felold.py kell.

    A SAJÁT JELSZÓ megadása kötelező (külső átnézés találata): enélkül egy ellopott (jelszó nélkül birtokolt) rendszeradmin-munkamenettel a
    támadó a SAJÁT telefonját állíthatná be, és "mfa" jelzésű munkamenetet kapna - a kizárás elleni védelem pont az ellenkezőjét érné el.
    A jelszó-próbálkozás ugyanazt a találgatás-védelmet kapja, mint a belépés (a sikeres jelszó itt NEM nullázza a számlálót: utána jön a kód)."""
    if not _kapu(current_user):
        return RedirectResponse(url="/valassz-modult", status_code=303)
    if _idegen_eredet(request):
        return RedirectResponse(url=f"{OLDAL}?hiba=eredet", status_code=303)
    if current_user.totp_megerositve:
        return RedirectResponse(url=OLDAL, status_code=303)

    client_ip = get_client_ip(request)
    user_id, email = current_user.id, current_user.email
    if login_vedelem.tiltva_e(db, client_ip, email):
        return _oldal(request, db, current_user, hiba_szoveg=login_vedelem.TILTOTT_UZENET)
    if not jelszo or not verify_password(jelszo, current_user.hashed_password):
        tiltas = login_vedelem.sikertelen_rogzitese(db, client_ip, email, current_user)
        user = db.get(models.User, user_id)
        return _oldal(request, db, user, hiba_szoveg=login_vedelem.TILTOTT_UZENET if tiltas is not None else HIBAS_JELSZO_UZENET)

    titok = mfa_utils.titok_inditasa(db, user_id)
    if titok is None:
        return RedirectResponse(url=OLDAL, status_code=303)
    return _sajat_beallitas_oldal(request, db, db.get(models.User, user_id), titok)


@router.post(OLDAL + "/sajat/megerosites")
def sajat_beallitas_megerositese(
    request: Request,
    kod: str = Form(""),
    db: Session = Depends(get_db),
    current_user: models.User = Depends(get_current_user)
):
    """A saját beállítás megerősítése az alkalmazás első kódjával. Ugyanaz a kód-találgatás elleni védelem, mint a belépésnél.
    (A jelszót az INDÍTÁS kéri: megerősíteni csak az indításkor készült, a támadó által nem ismert titokkal lehet.)"""
    if not _kapu(current_user):
        return RedirectResponse(url="/valassz-modult", status_code=303)
    if _idegen_eredet(request):
        return RedirectResponse(url=f"{OLDAL}?hiba=eredet", status_code=303)
    if current_user.totp_megerositve:
        return RedirectResponse(url=OLDAL, status_code=303)

    titok = mfa_utils.titok_olvasasa(current_user)
    if titok is None:
        return RedirectResponse(url=f"{OLDAL}?hiba=nincs_inditas", status_code=303)

    client_ip = get_client_ip(request)
    user_id, company_id, email = current_user.id, current_user.company_id, current_user.email

    if login_vedelem.tiltva_e(db, client_ip, email):
        return _sajat_beallitas_oldal(request, db, current_user, titok, login_vedelem.TILTOTT_UZENET)
    # A próbálkozást az ellenőrzés ELŐTT számoljuk (mint a /mfa-nál).
    if login_vedelem.sikertelen_rogzitese(db, client_ip, email, current_user) is not None:
        user = db.get(models.User, user_id)
        return _sajat_beallitas_oldal(request, db, user, mfa_utils.titok_olvasasa(user) or titok, login_vedelem.TILTOTT_UZENET)

    user = db.get(models.User, user_id)
    if not mfa_utils.megerosites(db, user, kod):
        db.rollback()
        user = db.get(models.User, user_id)
        return _sajat_beallitas_oldal(request, db, user, mfa_utils.titok_olvasasa(user) or titok, mfa_router.HIBAS_KOD_UZENET)

    db.add(models.AuditLog(
        company_id=company_id, user_id=user_id, field_name="Kétlépcsős hitelesítés",
        old_value="nincs beállítva", new_value=f"Beállítva (rendszeradmin, előre, hitelesítő alkalmazás, {client_ip})"
    ))
    db.commit()
    login_vedelem.sikeres_torlese(db, client_ip, email)
    mfa_router._ertesito_level(db, user_id, client_ip)

    # A kódot most igazolta: a munkamenetet "mfa" jelzéssel újra kiadjuk, hogy a későbbi bekapcsolás (a saját cégére is) ne
    # dobja ki azonnal. (Ugyanaz a token, mint a belépés utáni - routers/mfa.py _teljes_belepes.)
    # v34: a beállítás végén a rendszeradmin is megkapja a mentőkódjait (egyszer látja) - az ő elveszett telefonját egyébként csak a szerverről
    # (mfa_felold.py) lehetne visszaállítani. A "Tovább" gomb a 2FA-oldalra visz. Ha a készlet elbukik, a beállítás attól még kész.
    resp = RedirectResponse(url=f"{OLDAL}?ok=sajat_kesz", status_code=303)
    try:
        kodok = mentokod_utils.uj_keszlet(db, user_id)
        db.add(models.AuditLog(
            company_id=company_id, user_id=user_id, field_name="Kétlépcsős hitelesítés - mentőkódok", old_value="-",
            new_value=f"Új mentőkód-készlet ({mentokod_utils.DARAB} db, rendszeradmin, első beállítás, {client_ip})"
        ))
        db.commit()
        resp = mfa_router.mentokod_oldal(request, email=email, kodok=kodok, elso=True, tovabb=f"{OLDAL}?ok=sajat_kesz",
                                         kod_szoveg=mfa_router.mentokod_szoveg(email, kodok))
    except Exception as ex:                                       # pragma: no cover - védőháló
        db.rollback()
        print(f"⚠️ A rendszeradmin mentőkód-készletének elkészítése sikertelen: {type(ex).__name__}: {ex}")
    resp.set_cookie(
        key="access_token",
        value=create_access_token({"sub": email, "mfa": True}),
        httponly=True, secure=True, samesite="lax",
        max_age=ACCESS_TOKEN_EXPIRE_MINUTES * 60
    )
    return resp
