import json
import os
from datetime import date, datetime, timedelta
from urllib.parse import quote

from fastapi import APIRouter, Request, Depends, Form, BackgroundTasks, status
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from pypdf import PdfWriter
from fpdf.enums import MethodReturnValue, XPos, YPos
from sqlalchemy.orm import Session

import models
from database import get_db
from security import (
    safe_json_for_script, get_current_user, get_working_company_id, is_machine_allowed, require_modul, sanitize_filename, sugo_engedelyezve,
    irasi_tiltas_oka, IRASI_TILTAS_UZENETEK, IRASI_TILTAS_LISTA_URL, IRASI_TILTAS_INAKTIV_CEG,
)
from pdf_utils import JegyzokonyvPDF
from berles_utils import aktiv_berlet, is_gep_allowed_berlo_szamara
from msz_logic import kezeli_sikertelen_vizsgalati_minositest, eui_fovizsgalatra_jogosult_e, check_uzemora_drift_and_escalate
from msz16226_logic import gep_van_e_msz16226_szukseg
from msz_constants import ALAP_SZABVANYOK, kovetkezo_vizsgalatok, hu_datum
from hibakodok import hiba_uzenet
from templates_env import templates

router = APIRouter(dependencies=[Depends(require_modul("EMELOGEP"))])

# KARBANTARTÁS (2026-10-03-i biztonsági audit, 4. fázis): a vizsgalat_tipusa/eredmeny/
# vegso_ertekeles mezők eddig validálatlan szabad szövegként érkeztek a kliensből - a lenti
# listák a templates/vizsgalat.html <select>-jeinek (vizsgalat_tipusa, ertekeles_gomb)
# pontos option-értékeit tükrözik. Az Inspection.eredmeny mezőbe a /general-jegyzokonyv végpont
# ebből a 3 elemű minősítés-halmazból ír. (A korábbi, árva /uj-vizsgalat végpontot a J-12 javítás, v68 törölte.)
ENGEDELYEZETT_VIZSGALAT_TIPUSOK = {"Szerkezeti vizsgálat", "Fővizsgálat", "Időszakos biztonsági felülvizsgálat"}
ENGEDELYEZETT_VEGSO_ERTEKELESEK = {"Alkalmas / Megfelelt", "Korlátozással alkalmas", "Nem alkalmas / Nem felelt meg"}

# J-12 (v68): az írási kaput (aktív cég + csak_olvasas) a GÉP cégére (gep.company_id) futtatjuk, NEM a munkakörnyezetre:
# a külsős szakértő több cég között vált, és a munkakörnyezet-cookie nem feltétlenül a gép cége. Az inaktív cég üzenete
# itt vizsgálat-specifikus (a korábbi szöveg), a csak_olvasas üzenete a közös.
VIZSGALAT_TILTAS_UZENETEK = {**IRASI_TILTAS_UZENETEK, IRASI_TILTAS_INAKTIV_CEG: "Ez a cég inaktív státuszú, jegyzőkönyv nem rögzíthető!"}


@router.get("/vizsgalat-urlap/{uuid}", response_class=HTMLResponse)
def vizsgalat_urlap(
    request: Request,
    uuid: str,
    db: Session = Depends(get_db),
    current_user: models.User = Depends(get_current_user)
):
    working_company_id = get_working_company_id(current_user, request)

    if current_user.role not in ["Admin", "Emelőgép szakértő", "Emelőgép vizsgáló", "Emelőgép ügyintéző"]:
        return RedirectResponse(url="/lista")

    gep = db.query(models.Machine).filter(models.Machine.uuid == uuid).first()

    if not gep or not (is_machine_allowed(gep, current_user) or is_gep_allowed_berlo_szamara(gep, current_user, db)):
        return RedirectResponse(url="/lista")

    # J-12 (v68): az írási kapu a GÉP cégére fut (nem a munkakörnyezetére) - a külsős szakértő így nem rögzíthet inaktív
    # cég gépére, és a csak_olvasas fiók sem jut el az űrlapig. A gép-keresés ELŐTT nem szivárogtatunk ki semmit.
    tiltas = irasi_tiltas_oka(current_user, gep.company_id, db)
    if tiltas:
        return RedirectResponse(url=IRASI_TILTAS_LISTA_URL[tiltas])

    szigor_ki = db.query(models.Dictionary).filter(
        models.Dictionary.company_id == gep.company_id,
        models.Dictionary.kategoria == "RendeletSzigoritasKikapcsolva",
        models.Dictionary.ertek == "True"
    ).first()

    # KRITIKUS UX-hiba (2026-09-28-i user-jelzés): a listán az "Új Vizsgálat Rögzítése" gomb a
    # "Kivonva" státuszú gépeknél is megjelenik (l. templates/list.html - csak a 3 VÉGLEGES
    # státuszt tiltja le: Végleg kivonva/Selejtezve, Eladva, Bérleti idő lejárt/Visszaadva),
    # hiszen épp egy javítás utáni ismételt vizsgálathoz kellhet ide eljutni - de ehhez előbb
    # a gépet vissza kell állítani "Aktív" státuszba (a "Státusz váltása" gombbal). Korábban ez
    # az eset egy sima, indoklás nélküli "/lista"-ra visszairányítással végződött, a user csak
    # találgathatott, miért nem nyílik meg az űrlap - most konkrét, a gép nevét/státuszát is
    # tartalmazó üzenettel tér vissza.
    if gep.statusz != "Aktív":
        return RedirectResponse(
            url=f"/lista?gep_nem_aktiv={quote(gep.megnevezes or gep.gyari_szam or gep.uuid)}&gep_statusz={quote(gep.statusz)}"
        )

    sablonok_records = db.query(models.Dictionary).filter(
        models.Dictionary.company_id == gep.company_id,
        models.Dictionary.kategoria == "JegyzokonyvSablon"
    ).all()

    if not sablonok_records:
        sablonok_records = db.query(models.Dictionary).filter(
            models.Dictionary.company_id == 1,
            models.Dictionary.kategoria == "JegyzokonyvSablon"
        ).all()

    # Csak a gép géptípusához illeszkedő sablonok kerülnek a legördülőbe - ha egy sablonnál a
    # geptipus_lista üres/hiányzik, az általánosnak számít, minden géptípusnál megjelenik (visszamenőleg
    # kompatibilis a korábbi, taggeletlen sablonokkal is).
    sablon_lista = []
    for s in sablonok_records:
        ertek = json.loads(s.ertek)
        geptipus_lista = [g.strip() for g in (ertek.get("geptipus_lista") or "").split(",") if g.strip()]
        if geptipus_lista and gep.geptipus not in geptipus_lista:
            continue
        sablon_lista.append({
            "id": s.id,
            "ertek": ertek
        })

    return templates.TemplateResponse(
        request=request,
        name="vizsgalat.html",
        context={
            "user": current_user,
            "gep": gep,
            "sablonok_json": safe_json_for_script(sablon_lista),
            "eui_fovizsgalat_engedelyezve": bool(szigor_ki),
            "alap_szabvanyok": ALAP_SZABVANYOK + (["MSZ 16226"] if gep_van_e_msz16226_szukseg(gep, db) else []),
            "sugo_e": sugo_engedelyezve(working_company_id, db)
        }
    )


KORAI_VIZSGALAT_HATARIDO_NAP = 30


@router.get("/gep/{machine_id}/utolso-vizsgalat-friss-e")
def utolso_vizsgalat_friss_e(
    machine_id: int,
    tipus: str = "",
    db: Session = Depends(get_db),
    current_user: models.User = Depends(get_current_user)
):
    """ÚJ (2026-10-10-i user-kérés): vizsgálat indításakor figyelmeztet, ha ennek a gépnek a
    KIVÁLASZTOTT típusból már van 30 napnál frissebb (tehát még biztosan érvényes) vizsgálata -
    ez valószínűleg véletlen kettőzés (rossz gép/típus kiválasztása), nem egy ténylegesen
    esedékes új vizsgálat. Csak olvas, klasszikus ORM lekérdezéssel. A tényleges kikényszerítést
    a POST /general-jegyzokonyv végzi."""
    if tipus not in ENGEDELYEZETT_VIZSGALAT_TIPUSOK:
        return {"van": False}

    gep = db.query(models.Machine).filter(models.Machine.id == machine_id).first()
    if not gep or not (is_machine_allowed(gep, current_user) or is_gep_allowed_berlo_szamara(gep, current_user, db)):
        return {"van": False}

    utolso_datum = {
        "Szerkezeti vizsgálat": gep.szerkezeti_vizsgalat_datum,
        "Fővizsgálat": gep.fovizsgalat_datum,
        "Időszakos biztonsági felülvizsgálat": gep.biztonsagi_vizsgalat_datum,
    }.get(tipus)
    if not utolso_datum:
        return {"van": False}

    napok = (date.today() - utolso_datum).days
    if napok < 0 or napok >= KORAI_VIZSGALAT_HATARIDO_NAP:
        return {"van": False}

    return {"van": True, "datum": utolso_datum.strftime("%Y-%m-%d"), "napok": napok}


@router.post("/general-jegyzokonyv")
async def general_jegyzokonyv(
    request: Request,
    background_tasks: BackgroundTasks,
    machine_id: int = Form(...),
    vizsgalat_tipusa: str = Form(...),
    vizsgalat_datum: str = Form(...),
    pontszam: int = Form(...),
    osszes_tetel: int = Form(...),
    vegso_ertekeles: str = Form(...),
    hibak_1: str = Form(""),
    hibak_2: str = Form(""),
    hibak_3: str = Form(""),
    kerdesek_json: str = Form(...),
    csoportszam_modositas: str = Form(None),
    rendkivuli: bool = Form(False),
    rendkivuli_indoklas: str = Form(""),
    hatarido: str = Form(""),
    szabvanyok: str = Form(""),
    uzemora_vizsgalatkor: str = Form(""),
    bizonyitvany_csatolasa: bool = Form(False),
    korai_vizsgalat_tudomasul_veve: str = Form("nem"),
    db: Session = Depends(get_db),
    current_user: models.User = Depends(get_current_user)
):
    if current_user.role not in ["Admin", "Emelőgép szakértő", "Emelőgép vizsgáló", "Emelőgép ügyintéző"]:
        return JSONResponse(
            status_code=403,
            content={"message": "Nincs vizsgálat-rögzítési jogosultságod!"}
        )

    if vizsgalat_tipusa not in ENGEDELYEZETT_VIZSGALAT_TIPUSOK:
        return JSONResponse(status_code=400, content={"message": "Érvénytelen vizsgálat-típus!"})

    if vegso_ertekeles not in ENGEDELYEZETT_VEGSO_ERTEKELESEK:
        return JSONResponse(status_code=400, content={"message": "Érvénytelen végső minősítés!"})

    gep = db.query(models.Machine).filter(models.Machine.id == machine_id).first()

    if not gep or not (is_machine_allowed(gep, current_user) or is_gep_allowed_berlo_szamara(gep, current_user, db)):
        return JSONResponse(
            status_code=404,
            content={"message": "Hiba történt vagy nincs jogosultság!"}
        )

    # J-12 (v68): az írási kapu (aktív cég + csak_olvasas), a szigorítás-kapcsoló, az arculat és a PDF mappája mind a
    # GÉP cégére (gep.company_id) vonatkozik - nem a munkakörnyezetére. Így a külsős szakértő által rögzített jegyzőkönyv
    # a gép cégének mappájába kerül, és a bérlő/cég le tudja tölteni (a fájl-kiszolgáló a (cég, gép) párt ellenőrzi).
    gep_ceg_id = gep.company_id
    tiltas = irasi_tiltas_oka(current_user, gep_ceg_id, db)
    if tiltas:
        return JSONResponse(status_code=403, content={"message": VIZSGALAT_TILTAS_UZENETEK[tiltas]})

    szigor_ki = db.query(models.Dictionary).filter(
        models.Dictionary.company_id == gep_ceg_id,
        models.Dictionary.kategoria == "RendeletSzigoritasKikapcsolva",
        models.Dictionary.ertek == "True"
    ).first()

    # ÚJ (2026-09-28-i user-kérés): a vizsgálatot végző a vizsgálat felületén adja meg a gép
    # AKTUÁLIS üzemóra-állását - eddig a jegyzőkönyv (és az abból számolt szerkezeti_vizsgalat_
    # uzemora/fovizsgalat_uzemora, l. [[project_vizsgalati_idokoz_uzemora]]) csendben a gépen
    # már korábban, EGY MÁSIK úton (pl. "Gyors üzemóra rögzítés", routers/files.py) rögzített,
    # akár elavult gep.uzemora értéket használta - ami hamis MSZ 9750 üzemóra-alapú esedékesség-
    # számításhoz vezethetett, ha az üzemóra a vizsgálat óta nem lett frissítve. A mező a
    # teherfelvevő eszközöknél (nincs értelmezhető óraszámlálójuk) nem jelenik meg az űrlapon,
    # ezért itt üresen is érkezhet - ilyenkor a gép üzemórája változatlan marad.
    if uzemora_vizsgalatkor.strip():
        try:
            uzemora_ertek = float(uzemora_vizsgalatkor.strip().replace(",", "."))
        except ValueError:
            return JSONResponse(status_code=400, content={"message": "Érvénytelen üzemóra érték!"})

        if uzemora_ertek < 0:
            return JSONResponse(status_code=400, content={"message": "Az üzemóra nem lehet negatív szám!"})

        if uzemora_ertek != (gep.uzemora or 0.0):
            # A frissítés UGYANAZT a mintát követi, mint a "Gyors üzemóra rögzítés"
            # (AuditLog + drift-ellenőrzés).
            regi_uzemora = gep.uzemora
            gep.uzemora = uzemora_ertek
            db.add(models.AuditLog(
                machine_id=gep.id,
                user_id=current_user.id,
                field_name="Üzemóra (vizsgálatkor rögzítve)",
                old_value=str(regi_uzemora),
                new_value=str(uzemora_ertek),
            ))
            check_uzemora_drift_and_escalate(gep, db, background_tasks)

    if current_user.role == "Emelőgép vizsgáló" and vizsgalat_tipusa != "Szerkezeti vizsgálat":
        return JSONResponse(
            status_code=403,
            content={"message": "Emelőgép vizsgáló kizárólag szerkezeti vizsgálatot rögzíthet!"}
        )

    if current_user.role == "Emelőgép ügyintéző" and vizsgalat_tipusa in ("Fővizsgálat", "Időszakos biztonsági felülvizsgálat"):
        if not szigor_ki:
            return JSONResponse(
                status_code=403,
                content={"message": "Az ügyintézői fővizsgálat nincs engedélyezve a beállításokban!"}
            )
        if not eui_fovizsgalatra_jogosult_e(gep):
            return JSONResponse(
                status_code=403,
                content={"message": "Emelőgép ügyintéző nem rögzíthet fővizsgálatot/időszakos biztonsági felülvizsgálatot ehhez a géphez a jogszabály értelmében (teherbírás vagy kezelői munkahely miatt)!"}
            )

    # ÚJ (2026-10-10-i user-kérés): ha ennek a gépnek a KIVÁLASZTOTT típusból már van 30 napnál
    # frissebb (tehát még érvényes) vizsgálata, valószínűleg véletlen kettőzésről van szó (rossz
    # gép/típus kiválasztása) - a kliens (GET /gep/{id}/utolso-vizsgalat-friss-e, l. fent) ilyenkor
    # egy kötelező jelölőnégyzettel figyelmeztet, de ez önmagában csak kliens-oldali UX - a szerver
    # itt, a mentés pillanatában, a gép AKTUÁLIS adatából (nem a kliens állításából) újraszámolja
    # ugyanazt, és 400-zal elutasítja, ha a jóváhagyás elmaradt. Szándékosan a jogosultsági
    # ellenőrzések UTÁN fut: egy amúgy sem engedélyezett mentésnél a jogosultsági hiba a mérvadó.
    utolso_datum_korabbi_tipus = {
        "Szerkezeti vizsgálat": gep.szerkezeti_vizsgalat_datum,
        "Fővizsgálat": gep.fovizsgalat_datum,
        "Időszakos biztonsági felülvizsgálat": gep.biztonsagi_vizsgalat_datum,
    }.get(vizsgalat_tipusa)
    if utolso_datum_korabbi_tipus:
        napok_az_utolso_ota = (date.today() - utolso_datum_korabbi_tipus).days
        if 0 <= napok_az_utolso_ota < KORAI_VIZSGALAT_HATARIDO_NAP and korai_vizsgalat_tudomasul_veve != "igen":
            return JSONResponse(status_code=400, content={
                "message": f"Ennek a gépnek már van \"{vizsgalat_tipusa}\" vizsgálata "
                           f"{utolso_datum_korabbi_tipus.strftime('%Y-%m-%d')}-án rögzítve ({napok_az_utolso_ota} napja) - "
                           "kérjük erősítsd meg a jelölőnégyzettel, ha ennek ellenére új vizsgálatot rögzítesz!"
            })

    # --- Csoportszám módosítása a vizsgáló/EÜI szakmai jogköre alapján ---
    # FONTOS: ez NEM azonos a gép szerkesztésén elérhető, kockázatértékelésen alapuló
    # "egyedi_csoportszam_kockazat_alapjan" felülbírálással - ez a vizsgáló saját, a
    # vizsgálat helyszínén gyakorolt joga a csoportszám újbóli meghatározásához, ezért egy
    # ÖNÁLLÓ `vizsgaloi_csoportszam_felulbiralas` mezőt kapcsolunk be, NEM azt. (Az
    # "egyedi_csoportszam_kockazat_alapjan" mezőt a gép-szerkesztő űrlap MINDEN mentéskor a
    # saját jelölőnégyzete alapján felülírja - ha azt használnánk, a vizsgáló módosítása a
    # következő, akár teljesen független gép-szerkesztéskor csendben elveszne. A külön
    # mező viszont NEM némítja el az üzemóra-alapú automatikus szigorítást/figyelmeztetést
    # sem, mert az egy tőle független, folyamatos biztonsági ellenőrzés.)
    if csoportszam_modositas and csoportszam_modositas in ("1", "2", "3", "4", "5"):
        if current_user.role == "Emelőgép ügyintéző":
            if not szigor_ki:
                return JSONResponse(
                    status_code=403,
                    content={"message": "Nincs jogosultságod a csoportszám módosításához!"}
                )
            if not eui_fovizsgalatra_jogosult_e(gep):
                return JSONResponse(
                    status_code=403,
                    content={"message": "Emelőgép ügyintéző nem módosíthatja a csoportszámot ehhez a géphez a jogszabály értelmében (teherbírás vagy kezelői munkahely miatt)!"}
                )

        if csoportszam_modositas != (gep.calculated_csoportszam or ""):
            regi_csoportszam = gep.calculated_csoportszam or "-"
            gep.calculated_csoportszam = csoportszam_modositas
            gep.vizsgaloi_csoportszam_felulbiralas = True
            db.add(models.AuditLog(
                machine_id=gep.id,
                user_id=current_user.id,
                field_name="Csoportszám (vizsgálói módosítás)",
                old_value=regi_csoportszam,
                new_value=csoportszam_modositas,
            ))

    if rendkivuli and vizsgalat_tipusa == "Időszakos biztonsági felülvizsgálat" and not rendkivuli_indoklas.strip():
        return JSONResponse(
            status_code=400,
            content={"message": "Rendkívüli felülvizsgálat esetén az indoklás megadása kötelező!"}
        )

    try:
        vizsg_datum_obj = datetime.strptime(vizsgalat_datum, "%Y-%m-%d").date()
    except ValueError:
        return JSONResponse(status_code=400, content={"message": "Érvénytelen vizsgálati dátum formátum!"})

    # A user 2026-09-28-i jelzése szerint véletlenül (elgépeléssel) jövőbeli dátummal is
    # rögzíthető volt vizsgálat - egy vizsgálat definíció szerint a jelenben/múltban történik,
    # ezért a mai napnál későbbi dátumot a szerver visszautasítja (a dátummező max attribútuma
    # az űrlapon is a mai napra van korlátozva, de ez a valódi, megkerülhetetlen védelem).
    if vizsg_datum_obj > date.today():
        return JSONResponse(
            status_code=400,
            content={"message": "A vizsgálat dátuma nem lehet jövőbeli (a mai napnál későbbi)!"}
        )

    # ÚJ (2026-09-30-i user-kérés): "Korlátozással alkalmas" minősítésnél a gép mostantól NEM
    # vonódik ki azonnal, hanem a megadott határidőig használható marad (l. msz_logic.py
    # kezeli_sikertelen_vizsgalati_minositest) - ezért itt a hiba leírása és a határidő megadása
    # kötelezővé válik, hogy sose maradjon "lebegő", határidő nélküli korlátozás.
    if "Korlátozással" in vegso_ertekeles:
        if not hibak_2.strip():
            return JSONResponse(
                status_code=400,
                content={"message": "\"Korlátozással alkalmas\" minősítésnél a \"2. Soron kívül javítani, pótolni kell\" mező kitöltése kötelező!"}
            )
        if not hatarido.strip():
            return JSONResponse(
                status_code=400,
                content={"message": "\"Korlátozással alkalmas\" minősítésnél a javítási határidő megadása kötelező!"}
            )
        try:
            datetime.strptime(hatarido.strip(), "%Y-%m-%d")
        except ValueError:
            return JSONResponse(status_code=400, content={"message": "Érvénytelen határidő dátum formátum!"})

    arculat_sorok = db.query(models.Dictionary).filter(
        models.Dictionary.company_id == gep_ceg_id,
        models.Dictionary.kategoria.in_(["JegyzokonyvLogoUtvonal", "JegyzokonyvFejlecSzoveg", "JegyzokonyvLablecMarka"])
    ).all()
    arculat = {}
    for sor in arculat_sorok:
        if sor.kategoria == "JegyzokonyvLogoUtvonal":
            arculat["logo_utvonal"] = sor.ertek
        elif sor.kategoria == "JegyzokonyvFejlecSzoveg":
            arculat["fejlec_szoveg"] = sor.ertek
        elif sor.kategoria == "JegyzokonyvLablecMarka":
            arculat["lablec_marka"] = sor.ertek

    try:
        tenyleges_rendkivuli = rendkivuli and vizsgalat_tipusa == "Időszakos biztonsági felülvizsgálat"

        # "Soron kívül javítani, pótolni kell" határideje: a vizsgálat dátumához képest alapból 30 nap
        # (az űrlap is ezt tölti elő) - ha üresen/hibásan érkezne, "Korlátozással alkalmas" minősítésnél
        # ugyanide esik vissza, hogy a záró megállapításban sose maradjon üres a határidő.
        hatarido_obj = None
        if hatarido.strip():
            try:
                hatarido_obj = datetime.strptime(hatarido.strip(), "%Y-%m-%d").date()
            except ValueError:
                hatarido_obj = None
        if hatarido_obj is None and "Korlátozással" in vegso_ertekeles:
            hatarido_obj = vizsg_datum_obj + timedelta(days=30)

        pdf = JegyzokonyvPDF(
            gep, current_user.ceg, vizsgalat_tipusa, current_user, arculat=arculat,
            rendkivuli=tenyleges_rendkivuli,
            rendkivuli_indoklas=rendkivuli_indoklas if tenyleges_rendkivuli else None,
            szabvanyok=szabvanyok
        )
        pdf.alias_nb_pages()
        pdf.add_page()

        kerdesek = json.loads(kerdesek_json)
        pdf.set_font("DejaVu", "", 8)

        for k in kerdesek:
            if k.get('mod') == 'Nem releváns':
                continue

            sor_magassag = 6
            SZEL_PONT, SZEL_TARGY, SZEL_MOD, SZEL_MEGALLAPITAS, SZEL_ERTEKELES = 15, 90, 15, 50, 20

            pont_szoveg = k.get('pont') or '-'
            targy_szoveg = k.get('targy') or '-'
            mod_szoveg = k.get('mod') or '-'
            megallapitas_szoveg = k.get('megallapitas') or '-'
            ertekeles_szoveg = k.get('ertekeles') or '-'

            # Mind a négy szöveges oszlop (Szabv., tárgy, Módja, Mért érték/megállapítás) hosszabb
            # szöveg esetén több sorra törhet - előre (rajzolás NÉLKÜL, dry_run-nal) lekérdezzük
            # mindegyik tényleges magasságát, és a sor VÉGLEGES magassága a legmagasabbik oszlopé
            # lesz. Enélkül több hiba is adódott: (1) ha egy oszlop szövege pont az oldal aljához
            # közel tördelődött több sorra, a multi_cell menet közben automatikus oldaltörést
            # váltott ki, és a sor fele az egyik, fele a másik oldalra (egy majdnem üres, csak a
            # fejlécet tartalmazó oldalra) került; (2) a fix 6mm-es sor_magassag-gal rajzolt
            # oszlopok kerete nem igazodott a ténylegesen 2-3 sorosra nőtt oszlopokéhoz, ami
            # szétcsúszott kereteket, illetve a kereten kívülre lógó/nem tördelt szöveget
            # eredményezett a "Módja" és "Szabv." oszlopoknál.
            tervezett_magassag = max(
                sor_magassag,
                pdf.multi_cell(SZEL_PONT, sor_magassag, pont_szoveg, dry_run=True, output=MethodReturnValue.HEIGHT),
                pdf.multi_cell(SZEL_TARGY, sor_magassag, targy_szoveg, dry_run=True, output=MethodReturnValue.HEIGHT),
                pdf.multi_cell(SZEL_MOD, sor_magassag, mod_szoveg, dry_run=True, output=MethodReturnValue.HEIGHT),
                pdf.multi_cell(SZEL_MEGALLAPITAS, sor_magassag, megallapitas_szoveg, dry_run=True, output=MethodReturnValue.HEIGHT),
            )

            # Ha a teljes sor nem férne ki a jelenlegi oldalon, MOST, a sor elején váltunk
            # oldalt (ez újrarajzolja a táblázat fejlécét is) - nem hagyjuk, hogy a multi_cell
            # menet közben, a sor derekán törje ketté az oldalt.
            if pdf.get_y() + tervezett_magassag > pdf.page_break_trigger:
                pdf.add_page()

            x_start = pdf.get_x()
            y_start = pdf.get_y()

            # 1. lépés: a teljes sor kerete egységesen a végleges (legmagasabbik oszlophoz
            # igazított) magassággal - enélkül egy rövidebb szövegű oszlop kerete nem érne le
            # a sor aljáig.
            pdf.set_xy(x_start, y_start)
            pdf.cell(SZEL_PONT, tervezett_magassag, "", border=1)
            pdf.cell(SZEL_TARGY, tervezett_magassag, "", border=1)
            pdf.cell(SZEL_MOD, tervezett_magassag, "", border=1)
            pdf.cell(SZEL_MEGALLAPITAS, tervezett_magassag, "", border=1)
            pdf.set_font("DejaVu", "B", 8)
            pdf.cell(SZEL_ERTEKELES, tervezett_magassag, "", border=1)
            pdf.set_font("DejaVu", "", 8)

            # 2. lépés: a (törhető) szöveg keret NÉLKÜL, a már megrajzolt kereten belülre írva.
            x = x_start
            pdf.set_xy(x, y_start)
            pdf.multi_cell(SZEL_PONT, sor_magassag, pont_szoveg, align="C")
            x += SZEL_PONT

            pdf.set_xy(x, y_start)
            pdf.multi_cell(SZEL_TARGY, sor_magassag, targy_szoveg)
            x += SZEL_TARGY

            pdf.set_xy(x, y_start)
            pdf.multi_cell(SZEL_MOD, sor_magassag, mod_szoveg, align="C")
            x += SZEL_MOD

            pdf.set_xy(x, y_start)
            pdf.multi_cell(SZEL_MEGALLAPITAS, sor_magassag, megallapitas_szoveg, align="C")
            x += SZEL_MEGALLAPITAS

            pdf.set_font("DejaVu", "B", 8)
            pdf.set_xy(x, y_start)
            pdf.multi_cell(SZEL_ERTEKELES, sor_magassag, ertekeles_szoveg, align="C")
            pdf.set_font("DejaVu", "", 8)

            pdf.set_y(y_start + tervezett_magassag)

        pdf.tablazat_fejlec = False
        pdf.ln(5)
        pdf.set_font("DejaVu", "B", 10)
        if pdf.will_page_break(40):
            pdf.add_page()
        pdf.cell(0, 6, "Összefoglaló megállapítások:", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
        pdf.set_font("DejaVu", "", 9)

        if not hibak_1:
            hibak_1 = "-"
        pdf.multi_cell(0, 6, f"1. Fokozottan ellenőrizni kell: {hibak_1}", new_x=XPos.LMARGIN, new_y=YPos.NEXT)

        # A határidő csak akkor kerül a 2. tételhez, ha tényleg van mit javítani/pótolni (vagy a
        # minősítés korlátozott) - "Alkalmas" minősítésnél egy üres tétel mellett értelmetlen lenne.
        hibak_2_szoveg = hibak_2 or "-"
        if hatarido_obj and (hibak_2 or "Korlátozással" in vegso_ertekeles):
            hibak_2_szoveg += f" (Határidő: {hu_datum(hatarido_obj)})"
        pdf.multi_cell(0, 6, f"2. Soron kívül javítani, pótolni kell: {hibak_2_szoveg}", new_x=XPos.LMARGIN, new_y=YPos.NEXT)

        if not hibak_3:
            hibak_3 = "-"
        pdf.multi_cell(0, 6, f"3. Elhárításig az emelőgép NEM ÜZEMELTETHETŐ: {hibak_3}", new_x=XPos.LMARGIN, new_y=YPos.NEXT)

        pdf.ln(3)
        pdf.set_font("DejaVu", "B", 11)
        pdf.cell(0, 8, f"Az emelőgép minősítése: {vegso_ertekeles}", border=1, align="C", new_x=XPos.LMARGIN, new_y=YPos.NEXT)

        pdf.minositesi_megallapitas(vegso_ertekeles, hatarido_obj)

        # Soron következő vizsgálatok: kizárólag "Megfelelt" minősítésnél értelmes.
        if "Alkalmas / Megfelelt" in vegso_ertekeles:
            pdf.kovetkezo_vizsgalat_blokk(kovetkezo_vizsgalatok(gep, vizsgalat_tipusa, vizsg_datum_obj))

        pdf.ln(3)
        szazalek = round(pontszam / osszes_tetel * 100) if osszes_tetel else 0
        pdf.set_font("DejaVu", "", 10)
        pdf.cell(0, 6, f"Összpontszám: {pontszam}/{osszes_tetel} tétel megfelelt ({szazalek}%)", new_x=XPos.LMARGIN, new_y=YPos.NEXT)
        pdf.cell(0, 6, f"Kiállítás dátuma: {vizsgalat_datum}", new_x=XPos.LMARGIN, new_y=YPos.NEXT)

        # Az aláírás-blokk egyben marad: a vonalak és a felirataik nem kerülhetnek külön oldalra.
        if pdf.will_page_break(28):
            pdf.add_page()
        pdf.ln(8)
        pdf.cell(95, 6, "..................................................", ln=False, align="C")
        pdf.cell(95, 6, "..................................................", ln=True, align="C")
        pdf.cell(95, 6, "Üzemeltető képviselője", ln=False, align="C")
        pdf.cell(95, 6, "Emelőgép vizsgáló / szakértő", ln=True, align="C")

        mappa = f"uploads/{gep_ceg_id}/{machine_id}"
        os.makedirs(mappa, exist_ok=True)

        # A vizsgálat típusa/dátuma szabad szöveges Form mező - a fájlnévbe illesztés előtt
        # a felhasználó-vezérelt fájlnevekkel megegyező módon tisztítani kell, különben egy
        # ".."-t vagy "/"-t tartalmazó érték könyvtár-áthágást (path traversal) tenne lehetővé.
        biztonsagos_tipus = sanitize_filename(vizsgalat_tipusa.replace(' ', '_'))
        biztonsagos_datum = sanitize_filename(vizsgalat_datum)

        # JAVÍTVA (2026-10-10-i user-jelzés): a fájlnév eddig KIZÁRÓLAG a típusból és a
        # (beírt, akár visszadátumozott) vizsgálati dátumból épült - ha ugyanazon a gépen,
        # ugyanarra a napra, ugyanolyan típussal már volt mentett jegyzőkönyv, a pdf.output()
        # csendben FELÜLÍRTA a régi fájlt (miközben az adatbázisban mindkét Inspection-sor
        # megmaradt, immár ugyanarra - a felülírt - fájlra mutatva: az első vizsgálat eredeti
        # PDF-je visszavonhatatlanul elveszett). Mostantól, ha a név már foglalt, egy sorszámot
        # fűzünk hozzá - UGYANAZT a sorszámot használjuk az esetleges bizonyítvánnyal
        # összefűzött "Hivatalos_Jkv_..." változatnál is, hogy a kettő mindig összetartozzon.
        jelzo = ""
        szamlalo = 1
        while os.path.exists(f"{mappa}/Jkv_{biztonsagos_tipus}_{biztonsagos_datum}{jelzo}.pdf") or \
                os.path.exists(f"{mappa}/Hivatalos_Jkv_{biztonsagos_tipus}_{biztonsagos_datum}{jelzo}.pdf"):
            szamlalo += 1
            jelzo = f"_{szamlalo}"

        alap_pdf_path = f"{mappa}/Jkv_{biztonsagos_tipus}_{biztonsagos_datum}{jelzo}.pdf"
        vegso_pdf_path = alap_pdf_path

        pdf.output(alap_pdf_path)

        # ÚJ (2026-09-30-i user-kérés): a bizonyítvány csatolása mostantól a vizsgáló saját
        # döntése az űrlapon lévő jelölőnégyzettel - eddig, ha volt feltöltött bizonyítvány,
        # az MINDIG automatikusan a jegyzőkönyv PDF-hez lett fűzve.
        if bizonyitvany_csatolasa and hasattr(current_user, 'bizonyitvany_path'):
            if current_user.bizonyitvany_path:
                bizonyitvany_fizikai_ut = current_user.bizonyitvany_path.lstrip('/')
                if os.path.exists(bizonyitvany_fizikai_ut):
                    vegso_pdf_path = f"{mappa}/Hivatalos_Jkv_{biztonsagos_tipus}_{biztonsagos_datum}{jelzo}.pdf"
                    merger = PdfWriter()
                    merger.append(alap_pdf_path)
                    merger.append(bizonyitvany_fizikai_ut)
                    merger.write(vegso_pdf_path)
                    merger.close()
                    os.remove(alap_pdf_path)

        aktiv_berlet_obj = aktiv_berlet(gep, db)

        uj_vizsgalat = models.Inspection(
            machine_id=machine_id,
            user_id=current_user.id,
            vizsgalat_tipusa=vizsgalat_tipusa,
            datum=vizsg_datum_obj,
            eredmeny=vegso_ertekeles,
            megjegyzes=(
                f"Részletes ellenőrzőlista mentve az adatbázisban. Pontszám: {pontszam}/{osszes_tetel} ({szazalek}%)"
                + (f" Hiba-elhárítási határidő: {hatarido_obj.strftime('%Y-%m-%d')}." if hatarido_obj and "Korlátozással" in vegso_ertekeles else "")
            ),
            jegyzokonyv_pdf_path="/" + vegso_pdf_path,
            berleti_idoszak_id=aktiv_berlet_obj.id if aktiv_berlet_obj else None,
            uzemora_a_vizsgalatkor=gep.uzemora,
            rendkivuli=tenyleges_rendkivuli,
            rendkivuli_indoklas=rendkivuli_indoklas if tenyleges_rendkivuli else None,
        )

        if hasattr(models.Inspection, 'reszletes_eredmenyek'):
            uj_vizsgalat.reszletes_eredmenyek = kerdesek_json

        db.add(uj_vizsgalat)

        if vizsgalat_tipusa == "Szerkezeti vizsgálat":
            gep.szerkezeti_vizsgalat_datum = vizsg_datum_obj
            gep.szerkezeti_vizsgalat_uzemora = gep.uzemora
        elif vizsgalat_tipusa == "Fővizsgálat":
            gep.fovizsgalat_datum = vizsg_datum_obj
            gep.fovizsgalat_uzemora = gep.uzemora
        elif vizsgalat_tipusa == "Időszakos biztonsági felülvizsgálat":
            gep.biztonsagi_vizsgalat_datum = vizsg_datum_obj
            if hasattr(gep, 'kornyezet_megvaltozott'):
                gep.kornyezet_megvaltozott = False

        jkv_file = models.File(
            machine_id=machine_id,
            file_path="/" + vegso_pdf_path,
            file_type="Vizsgálati jegyzőkönyv"
        )
        db.add(jkv_file)

        jkv_log = models.AuditLog(
            machine_id=machine_id,
            user_id=current_user.id,
            field_name="HIVATALOS JEGYZŐKÖNYV",
            old_value="-",
            new_value=vizsgalat_tipusa
        )
        db.add(jkv_log)

        # "Nem alkalmas" minősítésnél a gép AZONNAL "Kivonva" státuszba kerül; "Korlátozással
        # alkalmas" esetén a gép a hatarido_obj-ig HASZNÁLHATÓ marad, csak egy határidős hibajegy
        # nyílik rá (l. msz_logic.py kezeli_sikertelen_vizsgalati_minositest) - mindkét esetben
        # a cég beállítása szerint e-mail is kimegy az Admin/EÜI-nek.
        kezeli_sikertelen_vizsgalati_minositest(
            gep, vegso_ertekeles, f"{vizsgalat_tipusa} eredménye", current_user, db, background_tasks, hatarido_obj
        )

        # ÚJ (2026-10-02-i user-kérés, l. routers/szakerto_dashboard.py): ha a szakértő előzőleg
        # ütemezett egy tervezett dátumot ehhez a gép+vizsgálat-típus pároshoz, a tényleges
        # vizsgálat rögzítésével a feladat elkészült - a tervezett sor automatikusan törlődik.
        db.query(models.TervezettVizsgalat).filter(
            models.TervezettVizsgalat.machine_id == machine_id,
            models.TervezettVizsgalat.vizsgalat_tipusa == vizsgalat_tipusa
        ).delete(synchronize_session=False)

        db.commit()
    except Exception as e:
        db.rollback()
        print(f"❌ Hiba a jegyzőkönyv generálása közben: {type(e).__name__}: {e}")
        return JSONResponse(
            status_code=400,
            content={"message": hiba_uzenet("Hiba történt a jegyzőkönyv generálása közben!", "E-JKV-01")}
        )

    return RedirectResponse(url="/lista", status_code=status.HTTP_303_SEE_OTHER)


# --- VIZSGÁLÓI RIPORT (időszakos elszámoláshoz) ---

def _ertelmezi_riport_idoszakot(mod: str, nap: str, tol: str, ig: str):
    """A 'Nap' vagy 'Időszak' módból és a kapott dátum-paraméterekből kiszámolja a
    ténylegesen szűrendő (tól, ig) dátumtartományt. Hibás/hiányzó bemenetnél
    értelmes alapértéket ad: 'Nap' módban a mai napot, 'Időszak' módban a folyó hónapot."""
    ma = date.today()

    if mod == "nap":
        try:
            valasztott_nap = datetime.strptime(nap, "%Y-%m-%d").date() if nap else ma
        except ValueError:
            valasztott_nap = ma
        return valasztott_nap, valasztott_nap

    try:
        tol_datum = datetime.strptime(tol, "%Y-%m-%d").date() if tol else ma.replace(day=1)
    except ValueError:
        tol_datum = ma.replace(day=1)

    try:
        ig_datum = datetime.strptime(ig, "%Y-%m-%d").date() if ig else ma
    except ValueError:
        ig_datum = ma

    if ig_datum < tol_datum:
        tol_datum, ig_datum = ig_datum, tol_datum

    return tol_datum, ig_datum


def _epit_vizsgaloi_riport_adatok(vizsgalatok):
    """Napi bontású csoportosítás (nyomtatáshoz és a 'Napi bontás' nézethez), valamint
    a riport végi összesítő (darabszám típus és eredmény szerint)."""
    napi_csoportok = []
    aktualis_datum = None
    aktualis_csoport = None
    tipus_szerint = {}
    eredmeny_szerint = {}

    for v in vizsgalatok:
        if v.datum != aktualis_datum:
            aktualis_datum = v.datum
            aktualis_csoport = {"datum": aktualis_datum, "tetelek": []}
            napi_csoportok.append(aktualis_csoport)
        aktualis_csoport["tetelek"].append(v)

        tipus_szerint[v.vizsgalat_tipusa] = tipus_szerint.get(v.vizsgalat_tipusa, 0) + 1
        eredmeny_szerint[v.eredmeny] = eredmeny_szerint.get(v.eredmeny, 0) + 1

    osszesito = {
        "osszesen": len(vizsgalatok),
        "napok_szama": len(napi_csoportok),
        "tipus_szerint": tipus_szerint,
        "eredmeny_szerint": eredmeny_szerint,
    }
    return napi_csoportok, osszesito


def _vizsgalo_elerheto_cegek(current_user, db: Session):
    """A cégszűrő legördülő listájához: azok a cégek, amelyeknél az Emelőgép szakértőnek/
    vizsgálónak ténylegesen van rögzített vizsgálata (nem az aktuális szűrt időszaktól függően)."""
    ceg_id_lista = [
        r[0] for r in db.query(models.Machine.company_id)
        .join(models.Inspection, models.Inspection.machine_id == models.Machine.id)
        .filter(models.Inspection.user_id == current_user.id)
        .distinct()
        .all()
    ]
    if not ceg_id_lista:
        return []
    return db.query(models.Company).filter(models.Company.id.in_(ceg_id_lista)).order_by(models.Company.nev).all()


def _vizsgalo_riport_lekerdezes(current_user, db: Session, tol_datum, ig_datum, ceg_id: str):
    query = db.query(models.Inspection).join(
        models.Machine, models.Inspection.machine_id == models.Machine.id
    ).filter(
        models.Inspection.user_id == current_user.id,
        models.Inspection.datum >= tol_datum,
        models.Inspection.datum <= ig_datum
    )
    if ceg_id:
        try:
            query = query.filter(models.Machine.company_id == int(ceg_id))
        except ValueError:
            pass
    return query.order_by(models.Inspection.datum.asc(), models.Inspection.id.asc()).all()


@router.get("/vizsgaloi-riport", response_class=HTMLResponse)
def vizsgaloi_riport(
    request: Request,
    mod: str = "idoszak",
    nap: str = "",
    tol: str = "",
    ig: str = "",
    ceg_id: str = "",
    db: Session = Depends(get_db),
    current_user: models.User = Depends(get_current_user)
):
    """Egy Emelőgép szakértő/vizsgáló saját, adott időszakban (vagy egy adott napon) elvégzett
    vizsgálatainak listája - az időszakos elszámoláshoz. Alapból az összes cégnél, opcionálisan
    egy kiválasztott cégre szűkítve (mivel egy Emelőgép szakértő/vizsgáló több cégnek is
    dolgozhat)."""
    if current_user.role not in ("Emelőgép szakértő", "Emelőgép vizsgáló"):
        return RedirectResponse(url="/lista")

    tol_datum, ig_datum = _ertelmezi_riport_idoszakot(mod, nap, tol, ig)
    vizsgalatok = _vizsgalo_riport_lekerdezes(current_user, db, tol_datum, ig_datum, ceg_id)
    napi_csoportok, osszesito = _epit_vizsgaloi_riport_adatok(vizsgalatok)
    elerheto_cegek = _vizsgalo_elerheto_cegek(current_user, db)

    return templates.TemplateResponse(
        request=request,
        name="vizsgaloi-riport.html",
        context={
            "user": current_user,
            "mod": mod if mod == "nap" else "idoszak",
            "nap": (tol_datum if mod == "nap" else date.today()).strftime("%Y-%m-%d"),
            "tol": tol_datum.strftime("%Y-%m-%d"),
            "ig": ig_datum.strftime("%Y-%m-%d"),
            "ceg_id": ceg_id,
            "elerheto_cegek": elerheto_cegek,
            "vizsgalatok": vizsgalatok,
            "napi_csoportok": napi_csoportok,
            "osszesito": osszesito
        }
    )


@router.get("/vizsgaloi-riport-nyomtatas", response_class=HTMLResponse)
def vizsgaloi_riport_nyomtatas(
    request: Request,
    mod: str = "idoszak",
    nap: str = "",
    tol: str = "",
    ig: str = "",
    ceg_id: str = "",
    db: Session = Depends(get_db),
    current_user: models.User = Depends(get_current_user)
):
    if current_user.role not in ("Emelőgép szakértő", "Emelőgép vizsgáló"):
        return RedirectResponse(url="/lista")

    tol_datum, ig_datum = _ertelmezi_riport_idoszakot(mod, nap, tol, ig)
    vizsgalatok = _vizsgalo_riport_lekerdezes(current_user, db, tol_datum, ig_datum, ceg_id)
    napi_csoportok, osszesito = _epit_vizsgaloi_riport_adatok(vizsgalatok)

    valasztott_ceg = None
    if ceg_id:
        try:
            valasztott_ceg = db.query(models.Company).filter(models.Company.id == int(ceg_id)).first()
        except ValueError:
            pass

    return templates.TemplateResponse(
        request=request,
        name="vizsgaloi-riport-nyomtatas.html",
        context={
            "user": current_user,
            "tol": tol_datum.strftime("%Y-%m-%d"),
            "ig": ig_datum.strftime("%Y-%m-%d"),
            "valasztott_ceg": valasztott_ceg,
            "napi_csoportok": napi_csoportok,
            "osszesito": osszesito,
            "nyomtatva": date.today().strftime("%Y-%m-%d")
        }
    )
