﻿import logging
import os
import uuid
from logging.handlers import RotatingFileHandler
from typing import Awaitable, Callable

import jwt
from fastapi import FastAPI, Depends, Request, Response, status, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from sqlalchemy import text, func
from sqlalchemy.orm import Session
from apscheduler.schedulers.background import BackgroundScheduler  # pyright: ignore[reportMissingTypeStubs]

import models
from config import SECRET_KEY, ALGORITHM, API_DOCS_ENABLED
from database import engine, SessionLocal, get_db
from security import (
    get_password_hash, get_current_user, get_current_user_optional, get_user_modul_kodok,
    is_rendszeradminisztrator, get_working_company_id, modul_elerheto_a_munkakornyezetben,
    munkakornyezet_visszaallitasa_get,
)
from email_utils import napi_riport_lekerdezes_es_kuldes
from msz_logic import korlatozott_gepek_hataridejenek_ellenorzese
import email_sor
from utemezo_utils import vedett
import login_vedelem
import aktivalas_utils
import szabadsag_utils                    # v35: Szabadságon vagyok
import jelszo_hazirend                    # v36: jelszó-házirend (lejárat, bonyolultság, előzmény)
from templates_env import templates, HELYI_TZ

# --- AZ IDŐZÍTŐ (SCHEDULER) ELINDÍTÁSA ---
# Az időzónát EXPLICIT adjuk meg (Europe/Budapest): enélkül az APScheduler a tzlocal-lal a
# szerver rendszer-időzónáját olvasná ki (/etc/localtime, /etc/timezone) - ha ez UTC,
# így a "hajnal 6" valójában helyi 7/8 órakor futott volna, ráadásul a tzlocal figyelmeztetést
# is írt a logba ("/etc/timezone is deprecated ..."). Így a futási idő a szerver
# beállításától függetlenül mindig helyi idő szerinti, nyári/téli időszámítással együtt.
scheduler = BackgroundScheduler(timezone=HELYI_TZ)
# A "cron" beállítással megmondjuk, hogy minden nap (hour=6, minute=0) hajnal 6-kor fusson le.
#
# FONTOS (2026-10-04): az éles szerver több gunicorn workerrel fut, és minden worker elindítja a
# SAJÁT ütemezőjét - a feladatok ezért korábban workerenként (3x) lefutottak. A vedett() burkoló
# az adatbázisban lefoglalja a futást (utemezo_utils.py), így minden feladat pontosan EGYSZER fut,
# akárhány worker van. Részletek: docs/EMAIL_RENDSZER.md.
scheduler.add_job(vedett("napi_riport", napi_riport_lekerdezes_es_kuldes), 'cron', hour=6, minute=0)  # pyright: ignore[reportUnknownMemberType]
# ÚJ: "Korlátozással alkalmas" minősítésű, határidős hibajegyek automatikus kivonás-ellenőrzése.
scheduler.add_job(vedett("korlatozott_gepek", korlatozott_gepek_hataridejenek_ellenorzese), 'cron', hour=6, minute=5)  # pyright: ignore[reportUnknownMemberType]
# ÚJ (2026-10-04): a kimenő levelek sorának feldolgozása percenként (sebességkorláttal, újrapróbálással)
# és a napi takarítás. A max_instances=1/coalesce megakadályozza, hogy egy lassú futás átfedésbe kerüljön.
scheduler.add_job(vedett("email_kiurites", email_sor.kiurites, percenkenti=True), 'cron', minute='*',  # pyright: ignore[reportUnknownMemberType]
                  max_instances=1, coalesce=True)
scheduler.add_job(vedett("email_takaritas", email_sor.takaritas), 'cron', hour=3, minute=10)  # pyright: ignore[reportUnknownMemberType]
# ÚJ (2026-10-04, D szelet): a régi sikertelen bejelentkezési kísérletek és a lejárt tiltások napi takarítása.
scheduler.add_job(vedett("login_takaritas", login_vedelem.takaritas), 'cron', hour=3, minute=20)  # pyright: ignore[reportUnknownMemberType]
# ÚJ (2026-10-04, E szelet): a lejárt / felhasznált aktiváló tokenek napi takarítása.
scheduler.add_job(vedett("aktivalo_takaritas", aktivalas_utils.takaritas), 'cron', hour=3, minute=30)  # pyright: ignore[reportUnknownMemberType]
# ÚJ (v35): a "Szabadságon vagyok" automatikus vége (a megadott visszatérés napján) + az összegző levél; 5 percenként nézi.
scheduler.add_job(vedett("szabadsag_lejarat", szabadsag_utils.lejarat_ellenorzes, percenkenti=True), 'cron', minute='*/5',  # pyright: ignore[reportUnknownMemberType]
                  max_instances=1, coalesce=True)
# ÚJ (v36): a jelszó-lejárati emlékeztető levelek (a lejárat előtt 7 nappal; egyszer / lejárati ciklus) - óránként nézi.
scheduler.add_job(vedett("jelszo_emlekezteto", jelszo_hazirend.emlekeztetok, percenkenti=True), 'cron', minute=17,  # pyright: ignore[reportUnknownMemberType]
                  max_instances=1, coalesce=True)
scheduler.start()  # pyright: ignore[reportUnknownMemberType]

# --- ADATBÁZIS INICIALIZÁLÁS ---
def init_db():
    models.Base.metadata.create_all(bind=engine)
    db = SessionLocal()
    
    letezo_ceg = db.query(models.Company).first()
    
    if not letezo_ceg:
        alap_ceg = models.Company(
            nev="Alapértelmezett Cég", 
            adoszam="00000000-0-00"
        )
        db.add(alap_ceg)
        db.commit()
        db.refresh(alap_ceg)
        
        alap_telephely = models.Site(
            company_id=alap_ceg.id, 
            nev="Központi Telephely"
        )
        db.add(alap_telephely)
        
        admin = models.User(
            username="Rendszer Adminisztrátor",
            email="admin@ceg.hu",
            hashed_password=get_password_hash("admin123"),
            role="Admin",
            company_id=alap_ceg.id,
            hozzaferes_szint="CEG"
        )
        db.add(admin)
        db.commit()

        # A "Tipus" szótár (Beállítások > Gyártók és Típusok > Gép Típusok) feltöltése -
        # a /rendszeradmin/cegek/uj minden ÚJ céghez EZT a listát (az 1-es cégét) másolja át,
        # tehát ha ez itt üresen marad, minden azután létrehozott cég is üresen kapja meg.
        from geplista_feltolto import feltolt_geptipusokat
        feltolt_geptipusokat(alap_ceg.id, db=db)

    db.close()

# --- ÚJ: ADATBÁZIS MIGRÁCIÓ ÉS UUID GENERÁLÁS (SMART QR ALAPOZÁS) ---
# FONTOS: ennek a blokknak init_db() ELŐTT kell lefutnia, mert init_db() egy teljes
# ORM lekérdezést indít a Company táblán (SELECT *), ami elszáll, ha időközben új oszlopot
# vezettünk be a modellben, de az még nincs hozzáadva a meglévő (nem friss) adatbázishoz.
with engine.connect() as conn:
    # Segédfüggvény, ami egyesével próbálja hozzáadni az oszlopokat, és ha már létezik, simán továbblép!
    def add_column_safe(table: str, column_def: str):
        try:
            conn.execute(text(f"ALTER TABLE {table} ADD COLUMN {column_def}"))
            conn.commit()
        except Exception as e:
            conn.rollback() # Ha már létezik az oszlop, visszavonjuk a tranzakciót és megyünk a következőre
            # Az "már létezik" a NORMÁLIS eset (minden induláskor): azt nem jelezzük. Bármi más hiba
            # (pl. jogosultság, zárolás időtúllépés) azt jelentené, hogy az oszlop HIÁNYZIK és a lekérdezések
            # elszállnak - ezt ne nyeljük el némán (2FA 1. fázis, külső átnézés javaslata).
            # Ugyanígy normális egy ÜRES (új telepítésű) adatbázison, hogy a tábla még nem létezik
            # (a táblákat a create_all hozza létre ezután) - azt sem jelezzük.
            uzenet = str(e).lower()
            if "duplicate column" not in uzenet and "no such table" not in uzenet and "doesn't exist" not in uzenet:
                print(f"⚠️ Oszlop-hozzáadás sikertelen ({table}: {column_def}): {type(e).__name__}: {str(e)[:200]}")

    # Korábbi Smart QR oszlopok
    add_column_safe("machines", "uuid VARCHAR(50)")
    add_column_safe("machines", "letiltva_napi_hiba_miatt BOOLEAN DEFAULT 0")
    
    # ÚJ: Teherfelvevő kapcsoló
    add_column_safe("machines", "is_teherfelvevo BOOLEAN DEFAULT 0")
    
    # ÚJ: HR Dátumok és Tanúsítvány száma a dolgozóknál
    add_column_safe("users", "tanusitvany_szam VARCHAR(100)")
    add_column_safe("users", "orvosi_alkalmassag_ervenyesseg DATE")
    add_column_safe("users", "jogositvany_ervenyesseg DATE")

    # ÚJ: Okos üzemóra bázispont (Mátrix kezdőpont) a napi átlag üzemidőhöz
    add_column_safe("machines", "napi_atlag_uzemido FLOAT DEFAULT 0.0")
    add_column_safe("machines", "matrix_kezdo_datum DATETIME")
    add_column_safe("machines", "matrix_kezdo_uzemora FLOAT")
    add_column_safe("machines", "uzemora_riasztas_szint VARCHAR(10)")
    add_column_safe("users", "jog_gep_szerkesztes BOOLEAN DEFAULT 0")
    add_column_safe("users", "jog_datum_szerkesztes BOOLEAN DEFAULT 0")

    # ÚJ: Felhasználó- és cégszintű inaktiválás (szerkesztési jogok felfüggesztése)
    add_column_safe("users", "aktiv BOOLEAN DEFAULT 1")
    add_column_safe("companies", "aktiv BOOLEAN DEFAULT 1")

    # ÚJ: Read-only fiók (könyvelő/auditor) + jogosultsági napló céghez kötése
    add_column_safe("users", "csak_olvasas BOOLEAN DEFAULT 0")
    add_column_safe("audit_logs", "company_id INTEGER")

    # ÚJ (2026-10-04, E szelet): aktiváló e-mail - a meglévő felhasználók a DEFAULT 1 miatt aktiváltak.
    add_column_safe("users", "aktivalva BOOLEAN DEFAULT 1")

    # ÚJ (2026-10-04, 2FA 1. fázis): kétlépcsős hitelesítés - felhasználói TOTP-adatok és céges kapcsoló.
    add_column_safe("users", "totp_secret VARCHAR(255)")
    add_column_safe("users", "totp_megerositve BOOLEAN DEFAULT 0")
    add_column_safe("users", "totp_utolso_lepes INTEGER")

    # ÚJ (2026-10-05, v32): egyidejű belépés tiltása - munkamenet-azonosító a felhasználón (docs/EGYIDEJU_BELEPES.md).
    add_column_safe("users", "aktiv_munkamenet VARCHAR(64)")
    add_column_safe("users", "munkamenet_kezdete_at DATETIME")
    add_column_safe("users", "munkamenet_ip VARCHAR(64)")
    # v35: "Szabadságon vagyok" (docs/PROFIL_ES_SZABADSAG.md)
    add_column_safe("users", "szabadsagon BOOLEAN DEFAULT 0")
    add_column_safe("users", "szabadsag_kezdete_at DATETIME")
    add_column_safe("users", "szabadsag_visszateres DATE")
    # v36: jelszó-házirend (docs/JELSZO_HAZIREND.md)
    add_column_safe("users", "jelszo_lejarat_at DATETIME")
    add_column_safe("users", "jelszo_emlekezteto_at DATETIME")
    add_column_safe("companies", "jelszo_ervenyesseg_nap INTEGER")
    add_column_safe("companies", "jelszo_min_hossz INTEGER")
    add_column_safe("companies", "jelszo_kell_kisbetu BOOLEAN DEFAULT 0")
    add_column_safe("companies", "jelszo_kell_nagybetu BOOLEAN DEFAULT 0")
    add_column_safe("companies", "jelszo_kell_szam BOOLEAN DEFAULT 0")
    add_column_safe("companies", "jelszo_kell_specialis BOOLEAN DEFAULT 0")
    add_column_safe("companies", "jelszo_elozmeny_db INTEGER")
    add_column_safe("companies", "jelszo_hazirend_valtozott_at DATETIME")
    add_column_safe("companies", "ketlepcsos_hitelesites_kotelezo BOOLEAN DEFAULT 0")
    add_column_safe("companies", "mfa_politika_valtozott_at DATETIME")

    # ÚJ: Tartozék / fő gép kapcsolat (informatív, szülő-gyerek hierarchia)
    add_column_safe("machines", "fo_gep_id INTEGER")

    # ÚJ: Bérelt eszköz (külső, a rendszert nem használó tulajdonostól bérelt gép)
    add_column_safe("machines", "berelt_eszkoz BOOLEAN DEFAULT 0")
    add_column_safe("machines", "berbeado_neve VARCHAR(255)")
    add_column_safe("machines", "berbeado_elerhetoseg VARCHAR(255)")
    add_column_safe("machines", "berlet_lejarat_datum DATE")

    # ÚJ: Tűzvédelmi modul - cég cím/elérhetőség (a tűzvédelmi napló fejlécéhez)
    add_column_safe("companies", "cim VARCHAR(500)")
    add_column_safe("companies", "elerhetoseg VARCHAR(255)")

    # ÚJ: Licenc-limitek cégenként (NULL = korlátlan)
    add_column_safe("companies", "licenc_max_gep INTEGER")
    add_column_safe("companies", "licenc_max_dolgozo INTEGER")
    add_column_safe("companies", "licenc_max_ugyintezo INTEGER")
    # licenc_max_szuperfelhasznalo sorát eltávolítottuk (2026-10-03, 4. fázis) - holt mező
    # volt, l. a models.py Company osztályánál lévő megjegyzést.

    # ÚJ: Tűzvédelmi modul - saját, az emelőgépes Site-tól független telephely-tábla
    add_column_safe("keszenleti_helyek", "telephely_id INTEGER")
    add_column_safe("tuzvedelmi_naplok", "telephely_id INTEGER")

    # ÚJ: Bérlet - a vizsgálati jegyzőkönyv esetleges bérleti időszakhoz kötése (alnapló)
    add_column_safe("inspections", "berleti_idoszak_id INTEGER")

    # ÚJ: Tűzvédelmi modul - Épület szint a telephely és a készenléti hely között
    add_column_safe("keszenleti_helyek", "epulet_id INTEGER")

    # ÚJ: Épület szintbeállítás (pinceszintek/földszint-változat/emeletek) + a készenléti hely szintje
    add_column_safe("tuzvedelmi_epuletek", "pinceszintek INTEGER DEFAULT 0")
    add_column_safe("tuzvedelmi_epuletek", "foldszint_valtozat VARCHAR(30) DEFAULT 'FOLDSZINT'")
    add_column_safe("tuzvedelmi_epuletek", "emeletek INTEGER DEFAULT 0")
    add_column_safe("keszenleti_helyek", "szint VARCHAR(50)")

    # ÚJ: Napló hiányos lezárása (auditbiztos hiányosság-jegyzés, visszamenőleges bejegyzés nélkül)
    add_column_safe("tuzvedelmi_naplok", "hianyosan_zarva BOOLEAN DEFAULT 0")
    add_column_safe("tuzvedelmi_naplok", "hianyossag_indoklas TEXT")

    # ÚJ: EBK ügyfél-katalógus - a projekt a kiválasztott ügyfél adatait pillanatkép
    # (snapshot) formájában kapja meg, ezért az ugyfel_id csak a nyomon-követhetőséghez kell
    add_column_safe("ebk_projektek", "ugyfel_id INTEGER")

    # ÚJ: EBK - munkavégzési engedély szám + engedély nélküli munkavégzés jelölése,
    # valamint az ezt automatikusan összesítő globális tétel jelzője
    add_column_safe("ebk_ellenorzes_tetelek", "munkavegzesi_engedely_szam VARCHAR(100)")
    add_column_safe("ebk_ellenorzes_tetelek", "engedely_nelkul BOOLEAN DEFAULT 0")
    add_column_safe("ebk_ellenorzesi_tetelek", "auto_engedely_osszesito BOOLEAN DEFAULT 0")
    add_column_safe("ebk_ellenorzes_tetelek", "engedely_ceg_nev VARCHAR(255)")
    add_column_safe("ebk_ellenorzes_tetelek", "engedely_ceg_szekhely VARCHAR(500)")
    add_column_safe("ebk_ellenorzes_tetelek", "engedely_felelos_vezeto VARCHAR(255)")
    add_column_safe("ebk_ellenorzes_tetelek", "engedely_helyi_vezeto VARCHAR(255)")
    add_column_safe("ebk_ellenorzes_tetelek", "engedely_megrendelo_neve VARCHAR(255)")
    add_column_safe("ebk_ellenorzes_tetelek", "engedely_tevekenyseg VARCHAR(500)")

    # ÚJ: MSZ 16226 üzemviteli dokumentum - géptípusonkénti kapcsoló + fájl-feltöltési megjegyzés
    add_column_safe("machine_type_configs", "msz16226_szukseges BOOLEAN DEFAULT 0")
    add_column_safe("machine_type_configs", "kezeloi_munkahely_szukseges BOOLEAN DEFAULT 0")
    add_column_safe("files", "megjegyzes TEXT")

    # ÚJ: MSZ 16226 3.10 (Időszakos vizsgálatok nyilvántartása) - üzemóra-pillanatfelvétel
    # a vizsgálat mentésekor + opcionális egyedi jegyzőkönyv-szám
    add_column_safe("inspections", "uzemora_a_vizsgalatkor FLOAT")
    add_column_safe("inspections", "jegyzokonyv_szam_egyedi BOOLEAN DEFAULT 0")
    add_column_safe("inspections", "jegyzokonyv_szam VARCHAR(50)")

    # ÚJ: Bérbeadás/kihelyezés cégenkénti engedélyezése + szabad szöveges (nem
    # rendszerhasználó) bérlő rögzítése kihelyezéskor
    add_column_safe("companies", "berbeadhat BOOLEAN DEFAULT 0")
    add_column_safe("berleti_idoszakok", "berlo_nev_kulso VARCHAR(255)")
    add_column_safe("berleti_idoszakok", "berlo_cim_kulso VARCHAR(500)")
    add_column_safe("berleti_idoszakok", "berlo_elerhetoseg_kulso VARCHAR(255)")
    # ÚJ: regisztrált bérlőnél a valódi hozzáférés a bérlő cég jóváhagyásához kötött - a
    # DEFAULT TRUE a meglévő (a funkció bevezetése előtti) bérleteket visszamenőleg jóváhagyottnak
    # tekinti, csak az EZUTÁN létrehozott, regisztrált-cégre szóló kihelyezések indulnak "függőben".
    add_column_safe("berleti_idoszakok", "jovahagyva BOOLEAN DEFAULT 1")

    # ÚJ: EBSZ 7.2.10 - a fővizsgálati/IBF jogosultság második (fizikai) feltétele: a kezelő
    # a kocsival/munkahellyel együtt emelkedik-e, és milyen magasra.
    add_column_safe("machines", "kezelo_egyutt_emelkedik BOOLEAN DEFAULT 0")
    add_column_safe("machines", "kezeloi_munkahely_magassaga INTEGER")

    # ÚJ: MSZ 9750 1. táblázat üzemóra-alapú vizsgálati időköze (a hónap-alapúval párhuzamosan,
    # amelyik előbb bekövetkezik) - az utolsó szerkezeti/fővizsgálatkori üzemóra-állás.
    add_column_safe("machines", "szerkezeti_vizsgalat_uzemora FLOAT")
    add_column_safe("machines", "fovizsgalat_uzemora FLOAT")

    # ÚJ: a "Rendkívüli vizsgálat" beolvad az Időszakos biztonsági felülvizsgálatba, mint
    # jelölő + indoklás (a régi, önálló típusú történeti sorok változatlanul megmaradnak).
    add_column_safe("inspections", "rendkivuli BOOLEAN DEFAULT 0")
    add_column_safe("inspections", "rendkivuli_indoklas TEXT")

    # ÚJ: a vizsgálat rögzítésének valódi időpontja (UTC) a gépnaplóhoz - a régi sorokhoz
    # egyszeri, idempotens visszatöltés a hivatalos jegyzőkönyv fájl-rekordjának feltöltési
    # idejéből (ugyanabban a kérésben jött létre, mint a vizsgálat). Második lefutáskor már
    # 0 sort érint (csak a letrehozva IS NULL sorokat nézi), biztonságos itt hagyni. A PDF
    # nélküli (egyszerű rögzítésű) régi vizsgálatok NULL-on maradnak - náluk a napló csak
    # a dátumot mutatja.
    add_column_safe("inspections", "letrehozva DATETIME")
    try:
        conn.execute(text(
            "UPDATE inspections SET letrehozva = ("
            " SELECT MIN(f.uploaded_at) FROM files f"
            " WHERE f.file_path = inspections.jegyzokonyv_pdf_path"
            "   AND f.machine_id = inspections.machine_id"
            ") WHERE letrehozva IS NULL AND jegyzokonyv_pdf_path IS NOT NULL"
        ))
        conn.commit()
    except Exception:
        conn.rollback()

    # ÚJ: EBSZ 3-szintű szerepkör-rendszer - a korábbi "Vizsgáló" szerepkör név félrevezető
    # volt (a névhasonlóság ellenére valójában korlátlan jogkörű "Emelőgép szakértőt" takart,
    # nem az EBSZ alsó "Emelőgép vizsgáló" szintjét) - egyszeri, idempotens adatjavítás:
    # második lefutáskor már 0 sort érint, biztonságos örökké itt hagyni.
    try:
        conn.execute(text("UPDATE users SET role = 'Emelőgép szakértő' WHERE role = 'Vizsgáló'"))
        conn.commit()
    except Exception:
        conn.rollback()

    # ÚJ: napi ellenőrzés kérdések géptípusonkénti szűkítése - vesszős lista, üres = minden géptípus
    add_column_safe("daily_check_questions", "geptipus_lista TEXT")

    # ÚJ: VIVÜ szabálytalanság szakterület-jelölése - egy adott hiba egyszerre több
    # szakterülethez (is) tartozhat, ezért esetenként (nem a tétel-katalógusban) jelölhető.
    add_column_safe("vivu_ellenorzes_tetelek", "szakterulet_iparbiztonsagi BOOLEAN DEFAULT 0")
    add_column_safe("vivu_ellenorzes_tetelek", "szakterulet_tuzvedelmi BOOLEAN DEFAULT 0")
    add_column_safe("vivu_ellenorzes_tetelek", "szakterulet_munkavedelmi BOOLEAN DEFAULT 0")
    add_column_safe("vivu_ellenorzes_tetelek", "szakterulet_kornyezetvedelmi BOOLEAN DEFAULT 0")

    # ÚJ: VIVÜ - a "Projekt" köztes fogalom megszűnt, a Telephely mostantól közvetlenül egy
    # Megrendelőhöz (VivuUgyfel) tartozik, nem cég-szintű megosztott katalógus. A régi
    # vivu_telephelyek.company_id és vivu_ellenorzesek.projekt_id oszlopok, valamint a
    # vivu_projektek/vivu_projekt_tetelek/vivu_projekt_telephelyek/user_vivu_projektek táblák
    # szándékosan nincsenek eldobva (a projekt ADD-only migrációs konvenciója) - némán árván
    # maradnak, a modell többé nem hivatkozik rájuk.
    add_column_safe("vivu_telephelyek", "ugyfel_id INTEGER")

    # ÚJ: VIVÜ - a helyszín mostantól az ellenőrzési pont (nem a bejárás fejrekord) szintjén
    # van rögzítve minden szabálytalanságnál - a fejrekord régi epulet_id/szint_id/osszes_szint
    # oszlopai (a fenti ADD-only konvenció szerint) némán árván maradnak.
    add_column_safe("vivu_ellenorzes_tetelek", "epulet_id INTEGER")

    # ÚJ: a szint megadása szabálytalanságnál nem kötelező (van épület, aminél nincs felvéve
    # szint), és mellé egy szabadszöveges "helyiség" mező is felvehető.
    add_column_safe("vivu_ellenorzes_tetelek", "helyiseg VARCHAR(200)")

    # ÚJ: "Korlátozással alkalmas" minősítésnél a hibajegy javítási határideje - eddig a gépet
    # ez a minősítés azonnal kivonta, mostantól csak eddig a napig használható (l. msz_logic.py
    # kezeli_sikertelen_vizsgalati_minositest / korlatozott_gepek_hataridejenek_ellenorzese).
    add_column_safe("maintenance_tickets", "hatarido DATE")

    # ÚJ (2026-10-10-i user-jelzés): a bérbe kiadott gép a bérbeadó saját telephelyét/"Készenléti
    # hely" adatát (megnevezes) örökölte a bérlő listájában is - a bérlő jóváhagyáskor megadja a
    # SAJÁT telephelyét és a gép ottani helyét, ez csak a megjelenítést módosítja (l. models.py).
    add_column_safe("berleti_idoszakok", "berlo_site_id INTEGER")
    add_column_safe("berleti_idoszakok", "berlo_keszenleti_hely VARCHAR(255)")

init_db()

# --- ÚJ: TŰZVÉDELMI ÉPÜLET SZINT ALAPOZÁSA ---
# Az Épület mostantól kötelező minden készenléti helynél - a meglévő (épület nélküli)
# helyeket egy telephelyenkénti alapértelmezett "Fő épület" rekordba soroljuk be, hogy a
# régi adatok is megfeleljenek az új kötelezettségnek anélkül, hogy bármi elveszne.
db_epulet = SessionLocal()
try:
    arva_helyek = db_epulet.query(models.KeszenletiHely).filter(models.KeszenletiHely.epulet_id == None).all()
    epulet_cache = {}
    for hely in arva_helyek:
        if hely.telephely_id is None:
            continue
        epulet = epulet_cache.get(hely.telephely_id)
        if not epulet:
            epulet = db_epulet.query(models.TuzvedelmiEpulet).filter(
                models.TuzvedelmiEpulet.telephely_id == hely.telephely_id
            ).first()
            if not epulet:
                epulet = models.TuzvedelmiEpulet(telephely_id=hely.telephely_id, nev="Fő épület")
                db_epulet.add(epulet)
                db_epulet.commit()
                db_epulet.refresh(epulet)
            epulet_cache[hely.telephely_id] = epulet
        hely.epulet_id = epulet.id
    db_epulet.commit()
finally:
    db_epulet.close()
# --------------------------------------------------------------------

# --- ÚJ: TŰZOLTÓ KÉSZÜLÉK TÍPUSKATALÓGUS ALAPOZÁSA (fireg.hu gyártói műszaki adatok) ---
# Globális (company_id=NULL) induló katalógus az új készülék felviteléhez - kereséssel és
# automatikus adatkitöltéssel. A cégek saját, egyedi típusokkal bővíthetik (company_id kitöltve).
db_tipus = SessionLocal()
try:
    if not db_tipus.query(models.TuzoltoKeszulekTipus).filter(models.TuzoltoKeszulekTipus.company_id == None).first():
        tipus_seedek = [
            ("Total", "FX 6 Euro - S", "Por", "MSZ EN 3", "43A 233B C", 6.0),
            ("Total", "FX 1 Euro", "Por", "MSZ EN 3", "8A 34B", 1.0),
            ("Total", "FX 2 Euro", "Por", "MSZ EN 3", "13A 70B", 2.0),
            ("Total", "FX 12 Euro", "Por", "MSZ EN 3", "43A 233B C", 12.0),
            ("Total", "K 5 SE", "CO2", "MSZ EN 3", "55B", 5.0),
            ("Total", "K 2 SE", "CO2", "MSZ EN 3", "34B", 2.0),
            ("Total", "S 6 DN eco classic", "Hab", "MSZ EN 3", "21A 183B", 6.0),
            ("Total", "S 9 DN eco classic", "Hab", "MSZ EN 3", "27A 233B", 9.0),
            ("Maxima", "PKM 6A", "Por", "MSZ EN 3", "43A 233B C", 6.0),
            ("Maxima", "PKM 1A", "Por", "MSZ EN 3", "8A 34B", 1.0),
            ("Maxima", "CKM 2B", "CO2", "MSZ EN 3", "34B", 2.0),
            ("Rozmaring", "RH 4 (Clean Agent)", "Tiszta oltóanyag", "MSZ EN 3", "5A 34B C", 4.0),
            ("Rozmaring", "RH 2 (Clean Agent)", "Tiszta oltóanyag", "MSZ EN 3", "34B C", 2.0),
            ("Rozmaring", "Poroz 6", "Por", "MSZ EN 3", "43A 233B C", 6.0),
            ("Gloria", "PD 6 GA", "Por", "MSZ EN 3", "43A 233B C", 6.0),
            ("Bavaria", "BAV VOLE X - P 6 XL", "Por", "MSZ EN 3", "43A 233B C", 6.0),
            ("Cervinka", "P6 CeA", "Por", "MSZ EN 3", "43A 233B C", 6.0),
            ("Minimax", "PU 50 s", "Por (kerekes)", "MSZ EN 1866", "IV B", 50.0),
        ]
        for gyarto, tipus_nev, oltoanyag, szabvany, oe, kg in tipus_seedek:
            db_tipus.add(models.TuzoltoKeszulekTipus(
                company_id=None, gyarto=gyarto, tipus_nev=tipus_nev, oltoanyag_tipus=oltoanyag,
                gyartasi_szabvany=szabvany, oe_ertek=oe, toltettomeg_kg=kg,
            ))
        db_tipus.commit()
finally:
    db_tipus.close()
# --------------------------------------------------------------------

# --- MODUL-RENDSZER ALAPOZÁSA (Emelőgép / Tűzvédelem / EBK / VIVÜ) ---
# A modul-törzsadat hiányzó sorait pótoljuk (idempotens). A cégekhez/felhasználókhoz rendelést a rendszeradminisztrátor állítja be
# a /rendszeradmin felületen. Egyetlen kivétel: egy modul-rendszer ELŐTTI adatbázis első indításakor (sehol nincs hozzárendelés)
# senki ne záródjon ki abból, amit addig használt - ez a kezdeti feltöltés. J-08 (v67): a korábbi automatika MINDEN újraindításkor
# lefutott, és újratöltötte a szándékosan kiürített modulokat (új cég, takarítás, egy-modulos szerepkörök); a részletes indoklás
# és a szabály: modul_utils.py.
from modul_utils import modul_seedek_potlasa, kezdeti_modul_feltoltes
db_modul = SessionLocal()
try:
    modul_seedek_potlasa(db_modul)
    kezdeti_modul_feltoltes(db_modul)
finally:
    db_modul.close()
# --------------------------------------------------------------------

# Adunk a meglévő, régi gépeknek is egy egyedi QR kódot (UUID-t)
db_mig = SessionLocal()
try:
    regi_gepek = db_mig.query(models.Machine).filter(models.Machine.uuid == None).all()
    for g in regi_gepek:
        g.uuid = str(uuid.uuid4())  # pyright: ignore[reportAttributeAccessIssue]
    db_mig.commit()
finally:
    db_mig.close()
# --------------------------------------------------------------------

# --- ÚJ (2026-10-11-i user-jelzés): a kockázatértékelés PDF (MSZ 9750 1.2 pont) eddig kizárólag a
# gép saját mezőjébe (kockazatertekeles_pdf_path) mentődött, SOHA nem lett belőle File-sor - ezért
# nem jelent meg a "Csatolt dokumentumok" listájában és a Gépnaplóban sem (mindkettő kizárólag a
# File táblából dolgozik). Pótoljuk a meglévő gépeknél - idempotens, minden indításkor lefut. ---
db_koc = SessionLocal()
try:
    gepek_koc = db_koc.query(models.Machine).filter(models.Machine.kockazatertekeles_pdf_path != None).all()
    for g in gepek_koc:
        van_mar = db_koc.query(models.File).filter(
            models.File.machine_id == g.id, models.File.file_type == "Kockázatértékelés"
        ).first()
        if not van_mar:
            db_koc.add(models.File(machine_id=g.id, file_path=g.kockazatertekeles_pdf_path, file_type="Kockázatértékelés"))
    db_koc.commit()
finally:
    db_koc.close()
# --------------------------------------------------------------------

# --- ÚJ: EBK ELLENŐRZÉSI TÉTEL-SABLON ALAPOZÁSA (4/2002. Korm. rendelet 4. melléklete) ---
# Globális (company_id=NULL) induló tétel-katalógus, hogy minden projektnél - meglévőnél és
# újnál is - alapból megjelenjen a jogszabályi ellenőrzési szempontrendszer. A cégek ezt saját
# tételekkel bővíthetik, és projektenként ki/bekapcsolhatják (lásd EbkProjektTetel), de magát a
# globális sablonsort nem szerkeszthetik/törölhetik.
db_ebk_tetel = SessionLocal()
try:
    if not db_ebk_tetel.query(models.EbkEllenorzesiTetel).filter(models.EbkEllenorzesiTetel.company_id == None).first():
        ebk_tetel_seedek = [
            ("NAPI", "Egyéni védőeszközök", "Előírt EVE (sisak, láthatósági, acélbetétes cipő, zuhanásgátló) megfelelő viselése a munkaterületen.", "Mvt. 42. §, 4/2002. sz. melléklet"),
            ("NAPI", "Villamos hálózat", "Ideiglenes elosztók, hosszabbítók sértetlensége, csatlakozók szabványossága, kábelvezetés biztonsága, érintésvédelem.", "Mvt. 40. §, 4/2002. sz. melléklet"),
            ("NAPI", "Leesés elleni védelem", "Védőkorlátok, lábdeszkák, födémnyílások és aknák stabil, elmozdulás elleni takarásának megléte.", "4/2002. 4. sz. melléklet"),
            ("NAPI", "Munkaterület rendje", "Közlekedési utak, menekülési útvonalak szabadon tartása, botlásveszély elhárítása, hulladékok eltakarítása.", "4/2002. 4. sz. melléklet"),
            ("NAPI", "Gépek, berendezések", "Munkagépek védőburkolatainak megléte, mozgó alkatrészek takarása, szemrevételezéses napi ellenőrzés.", "Mvt. 41. §"),
            ("NAPI", "Emelőgépek", "Emelőgépnapló bejegyzései, teherkötöző eszközök sértetlensége, teher alatti tartózkodás tilalmának betartása.", "4/2002. 4. sz. melléklet"),
            ("NAPI", "Környezeti feltételek", "Megfelelő megvilágítás, szükséges szellőzés megléte a zárt vagy szűk munkaterekben.", "4/2002. 4. sz. melléklet"),
            ("NAPI", "Beszállásos munkavégzés", "Zárt térbe (akna, tartály, árok) történő beszállás írásbeli engedélye, gázveszély folyamatos mérése, külső figyelő személy és mentési eszközök (pl. mentőháromszög, csörlő) helyszíni biztosítása.", "Mvt. 40. §, 4/2002. 4. sz. melléklet"),
            ("NAPI", "Veszélyes anyagok", "Vegyszerek, gázpalackok szabályos (kármentős, dőlésbiztos) tárolása, biztonsági adatlapok (MSDS) helyszíni elérhetősége, az anyaghoz előírt specifikus EVE (pl. légzésvédő, vegyszerálló kesztyű) tényleges használata.", "Mvt. 40. §, 2000. évi XXV. tv., 4/2002. 4. sz. melléklet"),
            ("NAPI", "Elsősegély", "Elsősegélynyújtó felszerelés könnyen elérhető, jól látható helyen tartása, tartalmának hiánytalansága.", "Mvt. 46. §, 4/2002. 4. sz. melléklet"),
            ("NAPI", "Tűzvédelem (napi)", "Tűzveszélyes hulladékok eltávolítása a melegmunkák környezetéből, napi oltóeszköz-készenlét biztosítása.", "4/2002. 4. sz. melléklet"),
            ("ATFOGO", "Szociális létesítmények", "Öltözők, mosdók, étkezők biztosítása, fűtése, higiéniai állapota (takarítás), ivóvíz elérhetősége.", "Mvt. 46. §, 4/2002. 3. sz. melléklet"),
            ("ATFOGO", "Dokumentáció (BET)", "Biztonsági és Egészségvédelmi Terv (BET) aktualizálása a munkafázisokhoz, építési naplóba történő kooperációs bejegyzések.", "4/2002. 5. §"),
            ("ATFOGO", "Kockázatértékelés", "Alvállalkozók érvényes munkavédelmi kockázatértékeléseinek megléte az adott technológiára vonatkozóan.", "Mvt. 54. §"),
            ("ATFOGO", "Oktatások", "Munkavédelmi oktatások (előzetes, ismétlődő) és rendkívüli oktatások dokumentáltsága, aláírt naplók.", "Mvt. 55. §"),
            ("ATFOGO", "Érintésvédelmi jegyzőkönyvek", "Villamos kéziszerszámok, elosztószekrények, hegesztőgépek időszakos biztonságtechnikai/érintésvédelmi felülvizsgálata.", "Mvt. 41. §"),
            ("ATFOGO", "Állványzatok", "Állványok építése utáni átadás-átvételi jegyzőkönyv, szakszerű kikötés, időszakos (heti/vihar utáni) felülvizsgálat.", "4/2002. 4. sz. melléklet"),
            ("ATFOGO", "Földmunkák, árkok", "Munkaárkok dúcolásának, rézsűkpézésének ellenőrzése, különös tekintettel az esőzések vagy fagyok utáni állapotra.", "4/2002. 4. sz. melléklet"),
            ("ATFOGO", "Beszállásos munkavégzés (Feltételek)", "Alapvető mentési terv megléte zárt téri munkákhoz, munkavállalók speciális orvosi alkalmassága és kiképzése a gázmérők és mentőeszközök használatára.", "Mvt. 40. §, 4/2002. 4. sz. melléklet"),
            ("ATFOGO", "Veszélyes anyagok (Adminisztráció)", "Kémiai kockázatbecslés megléte, veszélyes anyagok bejelentése az egészségügyi hatóság felé (OSZIR), dolgozók oktatásainak megtörténte a biztonsági adatlapokból.", "Mvt. 54. §, 2000. évi XXV. tv."),
            ("ATFOGO", "Tűzvédelmi eszközök", "Tűzoltó készülékek érvényes alap- és középfokú karbantartása, hitelesítési címkék, jelzőtáblák megléte.", "4/2002. 4. sz. melléklet"),
            ("ATFOGO", "Orvosi alkalmasság", "A területen munkát végzők érvényes foglalkozásegészségügyi alkalmassági igazolásainak (pl. magasban végzett munka) ellenőrzése.", "Mvt. 49. §"),
            ("ATFOGO", "Kizárások és Koordináció", "Párhuzamosan dolgozó vállalkozók térbeli/időbeli elválasztása, LOTO (kizárás/kötés) rendszerek ellenőrzése gépeken/hálózatokon.", "Mvt. 40. §, 4/2002. 4. sz. melléklet"),
        ]
        for tipus, kategoria, targy, jogszabaly in ebk_tetel_seedek:
            db_ebk_tetel.add(models.EbkEllenorzesiTetel(
                company_id=None, tipus=tipus, kotelezo=True,
                szoveg=f"{kategoria} – {targy} ({jogszabaly})",
            ))
        db_ebk_tetel.commit()

    # ÚJ: automatikusan összesítő tétel a napi lista végére - ha az ellenőrzés bármely más
    # tételénél be lett jelölve az "engedély nélkül végzett munka", ez automatikusan
    # szabálytalanná válik (lásd ebk_uj_ellenorzes.html JS logikáját), vagy önállóan is
    # rögzíthető, ha valaki egyébként szabályosan, de engedély nélkül dolgozik.
    if not db_ebk_tetel.query(models.EbkEllenorzesiTetel).filter(
        models.EbkEllenorzesiTetel.company_id == None,
        models.EbkEllenorzesiTetel.auto_engedely_osszesito == True
    ).first():
        db_ebk_tetel.add(models.EbkEllenorzesiTetel(
            company_id=None, tipus="NAPI", kotelezo=True,
            szoveg="Munkavégzési engedély megléte - minden, a helyszínen dolgozó alvállalkozó "
                   "rendelkezik érvényes munkavégzési engedéllyel.",
            auto_engedely_osszesito=True,
        ))
        db_ebk_tetel.commit()

    # ÚJ: takarítás - ha egy korábbi, részleges telepítés (pl. main.py hamarabb frissült, mint
    # models.py) miatt duplikáltan jött létre ez a tétel (egyszer auto_engedely_osszesito=True
    # jelzővel, egyszer anélkül, önállóan kapcsolható "normál" tételként), a felesleges
    # duplikátumo(ka)t inaktiváljuk és minden projektből eltávolítjuk - a hozzájuk esetlegesen
    # már rögzített korábbi ellenőrzési eredményeket NEM töröljük, csak a jövőbeli listákból
    # tűnnek el.
    kanonikus_engedely_tetel = db_ebk_tetel.query(models.EbkEllenorzesiTetel).filter(
        models.EbkEllenorzesiTetel.company_id == None,
        models.EbkEllenorzesiTetel.auto_engedely_osszesito == True
    ).order_by(models.EbkEllenorzesiTetel.id.asc()).first()
    if kanonikus_engedely_tetel:
        duplikatumok = db_ebk_tetel.query(models.EbkEllenorzesiTetel).filter(
            models.EbkEllenorzesiTetel.id != kanonikus_engedely_tetel.id,
            models.EbkEllenorzesiTetel.company_id == None,
            models.EbkEllenorzesiTetel.szoveg == kanonikus_engedely_tetel.szoveg,
            models.EbkEllenorzesiTetel.aktiv == True,
        ).all()
        for dup in duplikatumok:
            db_ebk_tetel.query(models.EbkProjektTetel).filter(models.EbkProjektTetel.tetel_id == dup.id).delete()
            dup.aktiv = False  # pyright: ignore[reportAttributeAccessIssue]
        if duplikatumok:
            db_ebk_tetel.commit()

    # ÚJ: takarítás - ha egy adott projekthez ugyanaz a tétel (tetel_id) TÖBBSZÖR is hozzá lett
    # rendelve (pl. párhuzamosan induló szerverfolyamatok versenyhelyzete miatt: mindkettő úgy
    # látta a lenti pótló ciklus lekérdezésekor, hogy a tétel még hiányzik, és mindkettő felvette),
    # a felesleges párokat eltávolítjuk - csoportonként a legkisebb id-jű sort tartjuk meg, de ha
    # a duplikátumok között van aktív, a megtartott sor is aktív marad.
    projekt_tetel_dup_csoportok = db_ebk_tetel.query(
        models.EbkProjektTetel.projekt_id, models.EbkProjektTetel.tetel_id
    ).group_by(
        models.EbkProjektTetel.projekt_id, models.EbkProjektTetel.tetel_id
    ).having(func.count(models.EbkProjektTetel.id) > 1).all()
    if projekt_tetel_dup_csoportok:
        for dup_projekt_id, dup_tetel_id in projekt_tetel_dup_csoportok:
            sorok = db_ebk_tetel.query(models.EbkProjektTetel).filter(
                models.EbkProjektTetel.projekt_id == dup_projekt_id,
                models.EbkProjektTetel.tetel_id == dup_tetel_id,
            ).order_by(models.EbkProjektTetel.id.asc()).all()
            megtartott, felesleges_sorok = sorok[0], sorok[1:]
            if any(s.aktiv for s in felesleges_sorok):
                megtartott.aktiv = True  # pyright: ignore[reportAttributeAccessIssue]
            for felesleges in felesleges_sorok:
                db_ebk_tetel.delete(felesleges)
        db_ebk_tetel.commit()

    # Egyedi index a (projekt_id, tetel_id) párra, hogy a fenti duplikáció a jövőben - pl. újbóli
    # párhuzamos induláskor - ne fordulhasson elő újra.
    try:
        db_ebk_tetel.execute(text(
            "CREATE UNIQUE INDEX IF NOT EXISTS ux_ebk_projekt_tetelek_projekt_tetel "
            "ON ebk_projekt_tetelek (projekt_id, tetel_id)"
        ))
        db_ebk_tetel.commit()
    except Exception:
        db_ebk_tetel.rollback()

    # Visszamenőleg is bekapcsoljuk a globális sablontételeket minden már létező projektnél,
    # ami még nem rendelkezik velük - hogy a meglévő projekteknél se maradjon ki egy jogszabályi
    # ellenőrzési szempont sem. Csak a hiányzókat pótoljuk, a projektek egyedi be/kikapcsolt
    # tételeit nem érintjük.
    globalis_tetelek = db_ebk_tetel.query(models.EbkEllenorzesiTetel).filter(
        models.EbkEllenorzesiTetel.company_id == None, models.EbkEllenorzesiTetel.aktiv == True
    ).all()
    if globalis_tetelek:
        for projekt in db_ebk_tetel.query(models.EbkProjekt).all():
            meglevo_tetel_idk = {pt.tetel_id for pt in projekt.projekt_tetelek}
            for tetel in globalis_tetelek:
                if tetel.id not in meglevo_tetel_idk:
                    db_ebk_tetel.add(models.EbkProjektTetel(projekt_id=projekt.id, tetel_id=tetel.id, aktiv=True))
                    meglevo_tetel_idk.add(tetel.id)
        try:
            db_ebk_tetel.commit()
        except Exception:
            # Versenyhelyzet: egy másik, párhuzamosan induló szerverfolyamat időközben már
            # felvette ugyanezeket a párokat, és a fenti egyedi index emiatt elutasította a
            # beszúrást - ez ártalmatlan, a másik folyamat munkáját nem kell megismételni.
            db_ebk_tetel.rollback()
finally:
    db_ebk_tetel.close()
# --------------------------------------------------------------------

# --- ÚJ: VIVÜ ELLENŐRZÉSI TÉTEL-SABLON ALAPOZÁSA (219/2011. (X. 20.) Korm. rendelet) ---
# Globális (company_id=NULL) induló tétel-katalógus - a VIVÜ modulnál NINCS per-Megrendelő
# be/kikapcsolás (lásd routers/vivu.py kommentjeit), tehát ellentétben a fenti EBK-blokkal itt
# nem kell semmilyen visszamenőleges hozzárendelő tábla-feltöltés, elég egyszer beszúrni a
# sablonsorokat - onnantól minden Megrendelőnél/ellenőrzésnél automatikusan megjelennek.
db_vivu_tetel = SessionLocal()
try:
    if not db_vivu_tetel.query(models.VivuEllenorzesiTetel).filter(models.VivuEllenorzesiTetel.company_id == None).first():
        vivu_tetel_seedek = [
            ("DOKUMENTACIO", "Dokumentáció", "Veszélyes anyagok nyilvántartása naprakész (mennyiség, minőség, CAS-szám, biztonsági adatlap)"),
            ("DOKUMENTACIO", "Dokumentáció", "Biztonsági Jelentés / Biztonsági Elemzés / SKET megléte, jóváhagyottsága, időszakos felülvizsgálata"),
            ("DOKUMENTACIO", "Dokumentáció", "A dokumentációban átvezetve minden technológiai módosítás (változásvezetés)"),
            ("DOKUMENTACIO", "Dokumentáció", "Belső Védelmi Terv aktualitása, gyártási/logisztikai (tárolás/szállítás) kockázatokkal"),

            ("DOKUMENTACIO", "Biztonsági Irányítási Rendszer", "Biztonságért felelős munkakörök/szervezeti felépítés rögzítve"),
            ("DOKUMENTACIO", "Biztonsági Irányítási Rendszer", "Rendszerszintű és folyamatorientált kockázatértékelések aktualitása"),
            ("DOKUMENTACIO", "Biztonsági Irányítási Rendszer", "Karbantartási tervek és ellenőrzési naplók vezetettsége"),
            ("DOKUMENTACIO", "Biztonsági Irányítási Rendszer", "Változásvezetési eljárás követése (technológiai/szervezeti módosításoknál)"),

            ("DOKUMENTACIO", "Felkészültség és képzés", "Éves részleges BVT-gyakorlat"),
            ("DOKUMENTACIO", "Felkészültség és képzés", "Háromévente kötelező, hatóság felügyelte teljes körű gyakorlat"),
            ("DOKUMENTACIO", "Felkészültség és képzés", "Munkavállalók iparbiztonsági/baleset-megelőzési oktatásához oktatási anyag készítése"),
            ("DOKUMENTACIO", "Felkészültség és képzés", "Külső alvállalkozók iparbiztonsági oktatásának igazolása"),
            ("DOKUMENTACIO", "Felkészültség és képzés", "Korábbi üzemzavarok/kvázi-balesetek kivizsgálása + levont intézkedések"),

            ("BEJARAS", "Raktározás", "Összeférhetőségi mátrix szerinti fizikai elkülönítés a helyszínen"),
            ("BEJARAS", "Raktározás", "Aktuális készletmennyiség a BJ-ben rögzített határértéken belül"),
            ("BEJARAS", "Raktározás", "Felfogó tálcák/kármentők fizikai épsége"),
            ("BEJARAS", "Raktározás", "Felfogó tálca kapacitása megfelelő-e"),
            ("BEJARAS", "Raktározás", "ATEX targoncák és töltőállomások megfelelősége"),
            ("BEJARAS", "Raktározás", "Raklapok rögzítése, állványrendszerek állapota"),
            ("DOKUMENTACIO", "Raktározás", "Állványrendszer időszakos felülvizsgálati jegyzőkönyve"),

            ("BEJARAS", "Gyártástechnológia", "Biztonsági reteszek/vészleállítók helyszíni működőképessége"),
            ("BEJARAS", "Gyártástechnológia", "Túlnyomás elleni védelmek (hasadótárcsa, biztonsági szelep) állapota"),
            ("DOKUMENTACIO", "Gyártástechnológia", "Reteszek/szelepek kalibrálási jegyzőkönyvei"),
            ("DOKUMENTACIO", "Gyártástechnológia", "Kritikus paraméterek DCS-naplózása, riasztási határértékek"),
            ("BEJARAS", "Gyártástechnológia", "Csővezetékek jelölése (áramlási irány, anyag) a helyszínen"),
            ("DOKUMENTACIO", "Gyártástechnológia", "Csővezetékek karbantartási/tömörségvizsgálati naplója"),

            ("BEJARAS", "Tárgyi/műszaki infrastruktúra és ATEX", "Gázveszély-jelzők, robbanásitöménység-mérők, tűzjelzők működőképessége/kalibrálása"),
            ("BEJARAS", "Tárgyi/műszaki infrastruktúra és ATEX", "Egyéni védőeszközök, semlegesítő anyagok, mentesítő felszerelések állapota, SKET láda tartalma"),
            ("BEJARAS", "Tárgyi/műszaki infrastruktúra és ATEX", "Vészelzárók rendelkezésre állása, hozzáférhetősége"),
            ("BEJARAS", "Tárgyi/műszaki infrastruktúra és ATEX", "Automata sprinkler/gázzal oltó berendezés és tűzgátló ajtók, tűzoltó készülékek, tüzivíz források állapota"),
            ("DOKUMENTACIO", "Tárgyi/műszaki infrastruktúra és ATEX", "ATEX zónabesorolási térkép megléte/naprakészsége"),
            ("DOKUMENTACIO", "Tárgyi/műszaki infrastruktúra és ATEX", "Elektrosztatikus feltöltődés elleni védelem földelési mérési jegyzőkönyve, VBF"),
        ]
        for tipus, kategoria, targy in vivu_tetel_seedek:
            db_vivu_tetel.add(models.VivuEllenorzesiTetel(
                company_id=None, tipus=tipus, kotelezo=True,
                szoveg=f"{kategoria} – {targy}",
            ))
        db_vivu_tetel.commit()
finally:
    db_vivu_tetel.close()
# --------------------------------------------------------------------

# --- FASTAPI ALAPOK ---
# Swagger UI (/docs), ReDoc (/redoc) és a nyers OpenAPI séma (/openapi.json) élesben ki van
# kapcsolva (l. config.py API_DOCS_ENABLED) - ezek nélkül egy kívülálló nem tudja egy kattintással
# feltérképezni az összes route-ot/mezőnevet. Helyi fejlesztéskor a .env-ben API_DOCS_ENABLED=true
# sorral visszakapcsolható.
#
# FIGYELEM - CORS: szándékosan NINCS CORSMiddleware, és NEM is szabad bevezetni anélkül, hogy a docs/GET_ATTEKINTES.md 4.7 pontját
# újragondolnánk. A DELETE-es végpontok (törlések) külső oldalról azért nem válthatók ki, mert a keresztoldali DELETE CORS-előzetest
# (preflight) igényel, amelyre itt nem adunk engedélyt, és a SameSite=Lax süti keresztoldali fetch-nél amúgy sem megy el.
app = FastAPI(
    title="Emelőgép Nyilvántartó SaaS Pro",
    docs_url="/docs" if API_DOCS_ENABLED else None,
    redoc_url="/redoc" if API_DOCS_ENABLED else None,
    openapi_url="/openapi.json" if API_DOCS_ENABLED else None,
)
app.mount("/static", StaticFiles(directory="static"), name="static")

# --- TEVÉKENYSÉGNAPLÓ: IP-cím / cég / felhasználó / végzett művelet gyűjtése egy szerver
# oldali log fájlba, biztonsági és nyomon-követhetőségi célból. ---
os.makedirs("logs", exist_ok=True)
tevekenyseg_logger = logging.getLogger("tevekenyseg")
tevekenyseg_logger.setLevel(logging.INFO)
_tevekenyseg_handler = RotatingFileHandler("logs/tevekenyseg.log", maxBytes=5_000_000, backupCount=5, encoding="utf-8")
_tevekenyseg_handler.setFormatter(logging.Formatter("%(asctime)s | %(message)s"))
tevekenyseg_logger.addHandler(_tevekenyseg_handler)
tevekenyseg_logger.propagate = False


# --- FÁJLFELTÖLTÉSI MÉRETKORLÁT: eddig egyetlen feltöltő route sem korlátozta a fájlméretet,
# egy nagyon nagy fájl (kép, PDF, Excel) feltöltése memória-/lemez-kimerítést okozhatott. A
# Content-Length fejléc alapján, minden kérésnél egységesen ellenőrizzük - ez nem helyettesíti
# a webszerver (nginx) szintű limitet, de az alkalmazás önmagában is védett marad. ---
MAX_UPLOAD_SIZE_BYTES = 25 * 1024 * 1024  # 25 MB


class FajlmeretKorlatozoMiddleware:
    """A Content-Length fejléc alapú ellenőrzés (l. lent) `Transfer-Encoding: chunked` kéréssel
    (nincs Content-Length fejléc) megkerülhető volt - 2026-09-24-i külső audit találata. Ez a
    middleware a kérés törzsét MAGA olvassa be, darabonként számolva a méretét, és a limit felett
    azonnal megszakítja (nem várja meg a teljes törzs beérkezését), utána egy "lejátszó" receive
    callable-lel adja tovább a már beolvasott bájtokat a route-oknak.

    SZÁNDÉKOSAN NYERS ASGI middleware (nem @app.middleware("http")/BaseHTTPMiddleware-alapú): az
    utóbbi egy Starlette Request-példányon (`request.stream()`) keresztül olvasná be a törzset,
    ami a Request `_stream_consumed` belső állapotát TRUE-ra állítja - mivel a downstream
    route-ok UGYANEZT a Request-példányt kapják tovább, a saját form()/body() hívásuk
    "Stream consumed" hibával elszállt volna (élőben ki is derült teszteléskor: minden POST
    422 Field required hibát adott). A nyers ASGI receive-csatorna becsomagolásával ez nem
    probléma, mert a downstream Request egy TELJESEN ÚJ példány, ami a mi becsomagolt receive
    függvényünkkel dolgozik, nem a miénkkel."""

    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        if scope["type"] != "http" or scope["method"] not in ("POST", "PUT", "PATCH"):
            await self.app(scope, receive, send)
            return

        buffer = bytearray()
        tul_nagy = False
        more_body = True
        while more_body:
            message = await receive()
            if message["type"] != "http.request":
                break
            buffer.extend(message.get("body", b""))
            more_body = message.get("more_body", False)
            if len(buffer) > MAX_UPLOAD_SIZE_BYTES:
                tul_nagy = True
                break

        if tul_nagy:
            # KÖZEPES (2026-10-03-i külső biztonsági audit, 2. kör): a nyers Response() alapból
            # text/plain-t ad - a frontend hiba-kezelő JS kódja viszont minden helyen
            # response.json()-t hív a hibaüzenetre, ami ezen a plain-text törzsön elszállt
            # ("Unexpected token" parse hiba), így a felhasználó sosem látta a valódi okot.
            response = JSONResponse(
                content={"message": "A feltöltött adat mérete meghaladja a megengedett 25 MB-os korlátot!"},
                status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
            )
            await response(scope, receive, send)
            return

        body_bytes = bytes(buffer)
        mar_lejatszva = False

        async def _ujra_lejatszo_receive():
            nonlocal mar_lejatszva
            if not mar_lejatszva:
                mar_lejatszva = True
                return {"type": "http.request", "body": body_bytes, "more_body": False}
            return await receive()

        await self.app(scope, _ujra_lejatszo_receive, send)


app.add_middleware(FajlmeretKorlatozoMiddleware)


@app.middleware("http")
async def fajlmeret_korlatozo_content_length_middleware(request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
    content_length = request.headers.get("content-length")
    if content_length:
        try:
            if int(content_length) > MAX_UPLOAD_SIZE_BYTES:
                return JSONResponse(
                    content={"message": "A feltöltött adat mérete meghaladja a megengedett 25 MB-os korlátot!"},
                    status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
                )
        except ValueError:
            pass
    return await call_next(request)


@app.middleware("http")
async def valasz_fejlecek_middleware(request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
    """Gyorsítótár- és tartalom-típus fejlécek (2026-10-05, GET-átvizsgálás S-3 szelet, docs/GET_ATTEKINTES.md 8. fejezet).

    MIÉRT? A bejelentkezett felhasználó személyes/üzleti adatot tartalmazó válaszait (JSON-ok, exportok, PDF-ek) a böngésző
    alapértelmezetten gyorsítótárba teheti (vissza-gomb, megosztott gép). Ezért:
      - JSON-válasz és minden `Content-Disposition`-ös (letöltés/PDF/export) válasz: `Cache-Control: private, no-store`.
      - A /uploads/... fájlok (hitelesített fájlkiszolgálók): `private, no-cache` - tárolható, de minden használat előtt
        újraellenőrzi a szerver (ETag/Last-Modified -> 304), így a jogosultság minden megnyitásnál érvényesül, és a képek nem
        töltődnek le újra feleslegesen. Ezeken `X-Content-Type-Options: nosniff` is van: a böngésző ne találgassa a típust.
    SZÁNDÉKOSAN NEM érinti: a /static/ fájlokat és a /sw.js-t (azok gyorsítótárazása kívánatos), a HTML-oldalakat (a no-store
    elvenné a vissza-gomb űrlap-állapotát) és minden olyan választ, amely már maga beállított `Cache-Control`-t (az a kezelő döntése)."""
    response = await call_next(request)
    ut = request.url.path
    if ut.startswith("/static/") or ut == "/sw.js":
        return response
    fejlec = response.headers
    if "cache-control" not in fejlec:
        if ut.startswith("/uploads/") and response.status_code == 200:
            fejlec["Cache-Control"] = "private, no-cache"
        elif fejlec.get("content-type", "").startswith("application/json") or "content-disposition" in fejlec:
            fejlec["Cache-Control"] = "private, no-store"
    if ut.startswith("/uploads/"):
        fejlec["X-Content-Type-Options"] = "nosniff"
    return response


@app.middleware("http")
async def tevekenyseg_naplozo_middleware(request: Request, call_next: Callable[[Request], Awaitable[Response]]) -> Response:
    response = await call_next(request)

    if request.url.path.startswith("/static/"):
        return response

    try:
        ip = request.client.host if request.client else "-"
        felhasznalo = "-"
        ceg_nev = "-"

        token = request.cookies.get("access_token")
        if token:
            try:
                payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])  # pyright: ignore[reportArgumentType]
                email = payload.get("sub")
                if email:
                    log_db = SessionLocal()
                    try:
                        felhasznalo_obj = log_db.query(models.User).filter(models.User.email == email).first()
                        if felhasznalo_obj:
                            felhasznalo = felhasznalo_obj.email
                            ceg_nev = felhasznalo_obj.ceg.nev if felhasznalo_obj.ceg else "-"
                    finally:
                        log_db.close()
            except Exception:
                pass

        naplozott_ut = request.url.path
        if naplozott_ut.startswith("/aktivalas/"):
            # Az aktiváló link tokenje titok (aki ismeri, beállíthatja a fiók jelszavát) - a naplóba nem kerülhet
            naplozott_ut = "/aktivalas/***"
        tevekenyseg_logger.info(
            f"IP={ip} | Felhasznalo={felhasznalo} | Ceg={ceg_nev} | {request.method} {naplozott_ut} | Status={response.status_code}"
        )
    except Exception:
        pass

    return response


from routers import auth as auth_router
from routers import mfa as mfa_router
from routers import mfa_mentokod as mfa_mentokod_router  # v34: 2FA mentőkódok
from routers import profil as profil_router  # v35: Profilom (jelszó, értesítések, Szabadságon vagyok)
from routers import jelszo_hazirend as jelszo_hazirend_router  # v36: a cég jelszó-házirendje (cégadmin)
from routers import rendszernaplo as rendszernaplo_router  # v37: a rendszeradmin összes-naplója
from routers import dashboard as dashboard_router
from routers import machines as machines_router
from routers import excel_import as excel_import_router
from routers import inspections as inspections_router
from routers import szakerto_dashboard as szakerto_dashboard_router
from routers import partner_cegek as partner_cegek_router
from routers import files as files_router
from routers import settings as settings_router
from routers import users as users_router
from routers import rendszeradmin_felhasznalok as rendszeradmin_felhasznalok_router  # 2026-10-05 (v24, 2. szelet): RA: szerepkör/cég módosítása
from routers import mobile_qr as mobile_qr_router
from routers import ai_scan as ai_scan_router
from routers import offline_sync as offline_sync_router
from routers import tuzvedelem as tuzvedelem_router
from routers import tuzvedelem_import as tuzvedelem_import_router
from routers import ebk as ebk_router
from routers import vivu as vivu_router
from routers import modulok as modulok_router
from routers import rendszeradmin as rendszeradmin_router
from routers import dokumentum_import as dokumentum_import_router
from routers import msz16226 as msz16226_router
from routers import rendszeradmin_email as rendszeradmin_email_router
from routers import rendszeradmin_kitiltasok as rendszeradmin_kitiltasok_router
from routers import rendszeradmin_mfa as rendszeradmin_mfa_router
from routers import rendszeradmin_dashboard as rendszeradmin_dashboard_router  # 2026-10-05 (v30): RA áttekintő dashboard
from routers import aktivalas as aktivalas_router
from routers import ertesitesek as ertesitesek_router
app.include_router(auth_router.router)
app.include_router(dashboard_router.router)
app.include_router(machines_router.router)
app.include_router(excel_import_router.router)
app.include_router(inspections_router.router)
app.include_router(szakerto_dashboard_router.router)
app.include_router(partner_cegek_router.router)  # 2026-10-06: partner-cégek kódos összekapcsolása (bérbeadás bérlő-választójának szűkítése)
app.include_router(files_router.router)
app.include_router(settings_router.router)
app.include_router(users_router.router)
app.include_router(rendszeradmin_felhasznalok_router.router)  # 2026-10-05 (v24, 2. szelet): RA: szerepkör/cég/extra cégek módosítása
app.include_router(mobile_qr_router.router)
app.include_router(ai_scan_router.router)
app.include_router(offline_sync_router.router)
app.include_router(tuzvedelem_router.router)
app.include_router(tuzvedelem_import_router.router)
app.include_router(ebk_router.router)
app.include_router(vivu_router.router)
app.include_router(modulok_router.router)
app.include_router(rendszeradmin_router.router)
app.include_router(dokumentum_import_router.router)
app.include_router(msz16226_router.router)
app.include_router(rendszeradmin_email_router.router)  # 2026-10-04: SMTP-beállítás a rendszeradmin felületről
app.include_router(rendszeradmin_kitiltasok_router.router)  # 2026-10-04 (D): bejelentkezési tiltások kezelése
app.include_router(rendszeradmin_dashboard_router.router)  # 2026-10-05 (v30): GET /rendszeradmin + kártya-kapcsolók
app.include_router(rendszeradmin_mfa_router.router)  # 2026-10-04 (2FA 4. fázis): cégkapcsoló + a rendszeradmin kizárás elleni védelme
app.include_router(aktivalas_router.router)  # 2026-10-04 (E): fiók-aktiválás e-mailben küldött linkkel
app.include_router(ertesitesek_router.router)  # 2026-10-04 (C): levél-feliratkozások (Értesítéseim + céges szabályok)
app.include_router(mfa_router.router)  # 2026-10-04 (2FA 2. fázis): a belépés 2. lépése, a 6 jegyű kód bekérése
app.include_router(mfa_mentokod_router.router)  # v34: mentőkódok (belépés mentőkóddal, a saját készlet kezelése)
app.include_router(profil_router.router)  # v35: Profilom
app.include_router(jelszo_hazirend_router.router)  # v36: jelszó-házirend
app.include_router(rendszernaplo_router.router)  # v37: rendszernapló (rendszeradmin)


@app.get("/", response_class=HTMLResponse)
def gyoker_oldal(request: Request, db: Session = Depends(get_db)):
    """A "/" a bejelentkezés állapotától függően más-más oldalt mutat: bejelentkezetlen
    látogatónak a nyilvános marketing kezdőlapot (a "Bejelentkezés" gombja a /login-ra visz),
    bejelentkezett felhasználónak pedig - változatlanul - az Emelőgép vezérlőpultot (vagy a
    modulválasztót, ha a cégének/fiókjának nincs Emelőgép-hozzáférése). Ezért ez a route
    SZÁNDÉKOSAN nem a dashboard_router-en belül él, mert azt a router-szintű
    require_modul("EMELOGEP") függőség feltétel nélkül bejelentkezéshez kötné."""
    current_user = get_current_user_optional(request, db)
    if not current_user:
        return templates.TemplateResponse(request=request, name="marketing.html", context={})

    if "EMELOGEP" not in get_user_modul_kodok(current_user, db):
        return RedirectResponse(url="/valassz-modult", status_code=status.HTTP_303_SEE_OTHER)

    # J-15 (v72): a kiválasztott munkakörnyezet cégének is elő kell fizetnie az Emelőgép modulra (mint a require_modul-nál);
    # ha nem, a munkakörnyezet a felhasználó saját cégére áll vissza és a "/" újratöltődik (nincs hurok: a saját cég rendben van).
    if not modul_elerheto_a_munkakornyezetben(current_user, "EMELOGEP", get_working_company_id(current_user, request), db):
        cel, sutik = munkakornyezet_visszaallitasa_get(request, current_user)
        valasz = RedirectResponse(url=cel, status_code=status.HTTP_303_SEE_OTHER)
        valasz.headers["Set-Cookie"] = sutik
        return valasz

    return dashboard_router.dashboard(request, db, current_user)


# --- TITKOS TESZTELŐ VÉGPONT (Csak fejlesztéshez!!!) ---
# MAGAS (2026-10-03-i külső biztonsági audit, 2. kör): GET volt (CSRF-fel, pl. egy <img>-gel
# kiváltható - a mellékhatás itt minden cég adminjának azonnali e-mail kiküldése), és
# current_user.role != "Admin" bármelyik cég Admin-jának engedte, nem csak a rendszergazdának.
# Most POST + is_rendszeradminisztrator() (Admin szerepkör ÉS company_id==1).
@app.post("/teszt-riport")
def teszt_riport_futtatasa(current_user: models.User = Depends(get_current_user)):
    """Kényszeríti a reggeli riport azonnali lefutását, mintha 6:00 lenne!"""
    if not is_rendszeradminisztrator(current_user):
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Nincs jogosultságod ehhez a művelethez!")
    # Kézi indítás: azonnal fut (nem megy át az egyszeri-futás védelmen), MINDEN tételt tartalmaz, és az
    # ismétlődő hibák szűrésének állapotát nem módosítja. A levelek a kimenő sorba kerülnek, és a
    # sebességkorlátnak megfelelően, legfeljebb 1 percen belül mennek ki.
    napi_riport_lekerdezes_es_kuldes(teszt=True)
    return {"message": "A riport generálás lefutott, a levelek a kimenő sorba kerültek (Rendszeradmin / E-mail beállítások / Kimenő levelek)."}
