import calendar
from datetime import date, datetime, timedelta

from sqlalchemy.orm import Session

import models


def add_months(sourcedate, months):
    if not sourcedate:
        return None

    month = sourcedate.month - 1 + months
    year = sourcedate.year + month // 12
    month = month % 12 + 1
    day = min(sourcedate.day, calendar.monthrange(year, month)[1])

    return date(year, month, day)


# --- VIZSGÁLATI MÁTRIX LOGIKA ---
# MSZ 9750 1. táblázat: "Az időszakos vizsgálatok időközei" - csoportszámonként ÜZEMÓRÁBAN és
# HÓNAPBAN is meg van adva mindkét vizsgálattípus időköze; a lábjegyzet szerint "a vizsgálati
# időközök (üzemóra, hónap) közül az a mérvadó, amelyet az emelőgép az üzemeltetése során előbb
# ér el" - ha nincs mód az üzemóra pontos vezetésére, a hónap-oszlop a mértékadó (l. msz_logic.py
# compute_vizsgalati_esedekesseg).
MSZ_MATRIX = {
    "1": {"szerk_ho": 8, "szerk_ora": 1600, "fo_ho": 24, "fo_ora": 4800},
    "2": {"szerk_ho": 7, "szerk_ora": 1400, "fo_ho": 21, "fo_ora": 4200},
    "3": {"szerk_ho": 6, "szerk_ora": 1200, "fo_ho": 18, "fo_ora": 3600},
    "4": {"szerk_ho": 4, "szerk_ora": 800, "fo_ho": 12, "fo_ora": 2400},
    "5": {"szerk_ho": 3, "szerk_ora": 600, "fo_ho": 9, "fo_ora": 1800},
}

HU_HONAPOK = [
    "január", "február", "március", "április", "május", "június",
    "július", "augusztus", "szeptember", "október", "november", "december",
]


def hu_datum(datum):
    """Magyaros hosszú dátumforma a nyomtatott jegyzőkönyvhöz: 2027. március 25."""
    return f"{datum.year}. {HU_HONAPOK[datum.month - 1]} {datum.day}."


# A jegyzőkönyv "Vizsgálat során alkalmazott előírások és szabványok" sorának mindig szereplő
# alapja - a gép-/sablon-specifikus kiegészítések (pl. MSZ 9721-2/-4) ehhez adódnak hozzá.
ALAP_SZABVANYOK = [
    "1993. évi XCIII. törvény a munkavédelemről (Mvt.)",
    "47/1999. (VIII. 4.) GM rendelet az Emelőgép Biztonsági Szabályzat kiadásáról (EBSZ)",
    "10/2016. (IV. 5.) NGM rendelet a munkaeszközök és használatuk biztonsági és egészségügyi követelményeinek minimális szintjéről",
    "16/2008. (VIII. 30.) NFGM rendelet a gépek biztonsági követelményeiről és megfelelőségének tanúsításáról",
    "MSZ 9750",
    "MSZ 9721-1",
]


def kovetkezo_vizsgalatok(gep, vizsgalat_tipusa, vizsgalat_datum):
    """A jegyzőkönyv végén (csak "Megfelelt" minősítésnél) kiírt, soron következő vizsgálati
    időpontok az MSZ 9750 1. táblázata (csoportszám) alapján. A hierarchia (MSZ 9721-1 3.1.):
    az IBF magában foglalja a fővizsgálatot, a fővizsgálat pedig a szerkezeti vizsgálatot - ezért
    magasabb rendű vizsgálat után mindkét alacsonyabb rendű időköz a mai vizsgálattól indul.
    Teherfelvevő eszköznél az EBSZ-ciklus (ebsz_ciklus_honap) az egyetlen irányadó időköz.
    None-t ad, ha nincs elég adat (nincs érvényes csoportszám).

    A gep.fovizsgalat_datum-ot a hívó a jegyzőkönyv kiállítása UTÁN frissíti, ezért itt még a
    korábbi (szerkezeti vizsgálatnál a továbbra is érvényes) fővizsgálati dátum áll rendelkezésre."""
    ciklus_honap = getattr(gep, 'ebsz_ciklus_honap', 60) or 60

    if getattr(gep, 'is_teherfelvevo', False):
        return {
            "teherfelvevo": True,
            "csoportszam": None,
            "sorok": [{
                "cimke": "Soron következő időszakos biztonsági felülvizsgálat",
                "datum": add_months(vizsgalat_datum, ciklus_honap),
                "honap": ciklus_honap,
                "ora": None,
            }],
        }

    csoportszam = str(gep.calculated_csoportszam or "")
    szabaly = MSZ_MATRIX.get(csoportszam)
    if not szabaly:
        return None

    sorok = []
    if vizsgalat_tipusa == "Szerkezeti vizsgálat":
        sorok.append({
            "cimke": "Soron következő szerkezeti vizsgálat",
            "datum": add_months(vizsgalat_datum, szabaly["szerk_ho"]),
            "honap": szabaly["szerk_ho"], "ora": szabaly["szerk_ora"],
        })
        sorok.append({
            "cimke": "Soron következő fővizsgálat",
            "datum": add_months(gep.fovizsgalat_datum, szabaly["fo_ho"]) if gep.fovizsgalat_datum else None,
            "honap": szabaly["fo_ho"], "ora": szabaly["fo_ora"],
            "megjegyzes": "" if gep.fovizsgalat_datum else "az utolsó fővizsgálat időpontja nincs rögzítve",
        })
    else:
        sorok.append({
            "cimke": "Soron következő szerkezeti vizsgálat",
            "datum": add_months(vizsgalat_datum, szabaly["szerk_ho"]),
            "honap": szabaly["szerk_ho"], "ora": szabaly["szerk_ora"],
        })
        sorok.append({
            "cimke": "Soron következő fővizsgálat",
            "datum": add_months(vizsgalat_datum, szabaly["fo_ho"]),
            "honap": szabaly["fo_ho"], "ora": szabaly["fo_ora"],
        })
        if vizsgalat_tipusa == "Időszakos biztonsági felülvizsgálat":
            sorok.append({
                "cimke": "Soron következő időszakos biztonsági felülvizsgálat",
                "datum": add_months(vizsgalat_datum, ciklus_honap),
                "honap": ciklus_honap, "ora": None,
            })

    return {"teherfelvevo": False, "csoportszam": csoportszam, "sorok": sorok}


# MSZ 9750 1. táblázat (M-fokozat x Terhelésfokozat -> Csoportszám) - tükrözi az index.html JS mszMatrix-át
MSZ_9750_CSOPORT_MATRIX = {
    "M1": {"T1": 1, "T2": 1, "T3": 2},
    "M2": {"T1": 1, "T2": 2, "T3": 3},
    "M3": {"T1": 2, "T2": 3, "T3": 4},
    "M4": {"T1": 3, "T2": 4, "T3": 5},
    "M5": {"T1": 4, "T2": 5, "T3": 5},
}


def _becsult_napi_ora_rata(gep, mai_nap):
    """A gép jelenlegi üzemóra-fogyási ütemének legjobb elérhető becslése: ha van elég adat a
    Mátrix bázispont óta, a TÉNYLEGES (mért) napi átlagot adja vissza, egyébként a géphez
    beállított TERVEZETT napi átlagot (napi_atlag_uzemido). 0.0-t ad, ha semmilyen becslés
    nem lehetséges (pl. vadonatúj gép, még nincs bázispont és nincs beállított terv sem)."""
    if gep.matrix_kezdo_datum and gep.matrix_kezdo_uzemora is not None:
        kezdo = gep.matrix_kezdo_datum
        if kezdo.tzinfo is not None:
            kezdo = kezdo.replace(tzinfo=None)
        elapsed_days = (datetime.combine(mai_nap, datetime.min.time()) - kezdo).total_seconds() / 86400.0
        if elapsed_days > 0:
            tenyleges_ora = (gep.uzemora or 0.0) - gep.matrix_kezdo_uzemora
            if tenyleges_ora > 0:
                return tenyleges_ora / elapsed_days
    return gep.napi_atlag_uzemido or 0.0


def _ora_alapu_esedekesseg(gep, mai_nap, utolso_uzemora, ora_kuszob):
    """MSZ 9750 1. táblázat: az üzemóra-oszlop alapján megbecsüli, mikorra éri el (vagy érte el
    már) a gép a vizsgálattípus üzemóra-küszöbét az utolsó ilyen vizsgálat óta. None-t ad vissza,
    ha nincs elég adat a becsléshez (nincs rögzítve üzemóra-alapadat az utolsó vizsgálatkor -
    ilyenkor a lábjegyzet szerint a naptári (hónap-alapú) időköz marad a mértékadó)."""
    if utolso_uzemora is None or not ora_kuszob:
        return None
    hatralevo_ora = ora_kuszob - ((gep.uzemora or 0.0) - utolso_uzemora)
    rata = _becsult_napi_ora_rata(gep, mai_nap)
    if rata > 0:
        # Pozitív hátralévő óránál jövőbeli, negatívnál (már túllépte a küszöböt) múltbeli
        # dátumot ad - mindkettő a "esedekes < mai_nap" / "hamarosan" logikába illeszkedik.
        return mai_nap + timedelta(days=round(hatralevo_ora / rata))
    if hatralevo_ora <= 0:
        return mai_nap - timedelta(days=1)  # már túllépte, de az ütemet nem lehet megbecsülni
    return None  # nincs használható napi ütem-adat, előre nem lehet napra pontosan becsülni


def compute_vizsgalati_esedekesseg(gep, mai_nap, config=None, datum_kotelezo_mod="AlapbolKotelezo"):
    """
    Egységesen visszaadja a gép EBSZ / MSZ 9750 vizsgálati esedékességeit (Szerkezeti,
    Fő-, Biztonsági felülvizsgálat), a "Joker" szabályt (Teherfelvevő eszközöknél
    mindhárom az EBSZ ciklushoz igazodik) figyelembe véve. Naptár-nézethez / API-hoz.

    A config (MachineTypeConfig) opcionális: ha meg van adva, és a géptípusnál az adott
    vizsgálati dátum mezője "rejtett" (a Beállításokban kikapcsolt, nem alkalmazandó erre a
    géptípusra), az a vizsgálattípus egyáltalán nem szerepel az eredményben - így üresen sem
    jelez hiányt, hiszen nála ez a vizsgálat nem is értelmezhető.

    A datum_kotelezo_mod (céges beállítás, l. get_vizsgalati_datum_kotelezo_mod) szabályozza,
    hogy egy NEM rejtett, de a géptípusnál kifejezetten kötelezőnek SEM jelölt dátum hiánya
    jelezzen-e: "AlapbolKotelezo" módban igen (csak a rejtett zár ki), "CsakBepipalt" módban
    csak azok a dátumtípusok kerülnek egyáltalán figyelembevételre, amiket a géptípusnál
    kifejezetten kötelezőnek jelöltek.
    """
    figyelmezteto_hatar = mai_nap + timedelta(days=30)
    ciklus_honap = getattr(gep, 'ebsz_ciklus_honap', 60) or 60

    if getattr(gep, 'is_teherfelvevo', False):
        szabaly = {"szerk_ho": ciklus_honap, "fo_ho": ciklus_honap}
    elif gep.calculated_csoportszam and str(gep.calculated_csoportszam) in MSZ_MATRIX:
        szabaly = MSZ_MATRIX[str(gep.calculated_csoportszam)]
    else:
        szabaly = None

    rejtett_lista = set()
    if config and config.rejtett_mezok:
        rejtett_lista = set(m.strip() for m in config.rejtett_mezok.split(",") if m.strip())

    kotelezo_lista = set()
    if config and config.kotelezo_mezok:
        kotelezo_lista = set(m.strip() for m in config.kotelezo_mezok.split(",") if m.strip())

    # JAVÍTÁS (2026-09-23): Teherfelvevő eszközöknél (heveder, lánc, sekli stb.) a Szerkezeti
    # vizsgálat és a Fővizsgálat mint ÖNÁLLÓAN vezetett dátum nem értelmezhető - a szerkesztőlap
    # ("Nem emelőgép" kapcsoló, l. toggleTeherfelvevo() a static/js/pages/index.js-ben) ezért
    # nem is kéri be, sőt bekapcsoláskor ürítve/elrejtve tartja a szerkezeti_vizsgalat_datum és
    # fovizsgalat_datum mezőket. A Joker szabály viszont (l. fent) mindhárom vizsgálatot
    # (Szerkezeti/Fő/EBSZ) az EBSZ ciklushoz igazítja ezeknél az eszközöknél - ezért itt a
    # Szerkezeti/Fővizsgálat esedékességét a ténylegesen karbantartott biztonsagi_vizsgalat_datum
    # (EBSZ) mezőből származtatjuk, nem a fenti okból üresen maradó saját mezőikből.
    #
    # Enélkül ("forras_mezonev" felülbírálás nélkül) a szerkezeti_vizsgalat_datum/
    # fovizsgalat_datum hiánya "hianyzik" állapotot adott vissza minden teherfelvevő eszközre,
    # ami a dashboardon (routers/dashboard.py) automatikusan "Lejárt"-nak jelölte őket -
    # függetlenül attól, hogy az EBSZ vizsgálatuk ténylegesen rendben volt-e. Ez a dashboard
    # "Lejárt/Közeledő/Rendben" diagramját és a három oszlopos listát is torzította.
    teherfelvevo = getattr(gep, 'is_teherfelvevo', False)

    def bejegyzes(nev, mezonev, honap, uzemora_mezonev=None, ora_kuszob=None, forras_mezonev=None):
        if mezonev in rejtett_lista:
            return None
        if datum_kotelezo_mod == "CsakBepipalt" and mezonev not in kotelezo_lista:
            return None

        utolso_datum = getattr(gep, forras_mezonev or mezonev)
        if not utolso_datum:
            return {"tipus": nev, "utolso": None, "esedekes": None, "allapot": "hianyzik"}
        if honap is None:
            return {"tipus": nev, "utolso": utolso_datum.strftime("%Y-%m-%d"), "esedekes": None, "allapot": "ismeretlen"}
        esedekes = add_months(utolso_datum, honap)

        # MSZ 9750 1. táblázat: a hónap- és az üzemóra-alapú időköz közül az a mérvadó, amelyiket
        # a gép ELŐBB éri el - ha van hozzá használható üzemóra-alapadat, és az korábbi dátumot
        # ad, azt vesszük figyelembe a naptári dátum helyett.
        if uzemora_mezonev:
            utolso_uzemora = getattr(gep, uzemora_mezonev, None)
            ora_alapu = _ora_alapu_esedekesseg(gep, mai_nap, utolso_uzemora, ora_kuszob)
            if ora_alapu is not None and ora_alapu < esedekes:
                esedekes = ora_alapu

        if esedekes < mai_nap:
            allapot = "lejart"
        elif esedekes <= figyelmezteto_hatar:
            allapot = "hamarosan"
        else:
            allapot = "rendben"
        return {
            "tipus": nev,
            "utolso": utolso_datum.strftime("%Y-%m-%d"),
            "esedekes": esedekes.strftime("%Y-%m-%d"),
            "allapot": allapot
        }

    bejegyzesek = [
        bejegyzes(
            "Szerkezeti vizsgálat", "szerkezeti_vizsgalat_datum", szabaly["szerk_ho"] if szabaly else None,
            "szerkezeti_vizsgalat_uzemora", szabaly.get("szerk_ora") if szabaly else None,
            forras_mezonev="biztonsagi_vizsgalat_datum" if teherfelvevo else None
        ),
        bejegyzes(
            "Fővizsgálat", "fovizsgalat_datum", szabaly["fo_ho"] if szabaly else None,
            "fovizsgalat_uzemora", szabaly.get("fo_ora") if szabaly else None,
            forras_mezonev="biztonsagi_vizsgalat_datum" if teherfelvevo else None
        ),
        bejegyzes("Időszakos biztonsági felülvizsgálat (EBSZ)", "biztonsagi_vizsgalat_datum", ciklus_honap),
    ]
    return [b for b in bejegyzesek if b is not None]


def get_vizsgalati_datum_kotelezo_mod(company_id: int, db: Session) -> str:
    """Visszaadja, hogy a cégnél a vizsgálati dátumok (Szerkezeti/Fő/Biztonsági) hiánya/lejárata
    mikor számítson jelzésre: "AlapbolKotelezo" (alapértelmezett - minden géptípusnál kötelező,
    kivéve amit kifejezetten "rejtett"-té tesznek) vagy "CsakBepipalt" (csak azok a dátumok
    jeleznek, amiket az adott géptípusnál kifejezetten kötelezőnek jelöltek a Beállításokban)."""
    beallitas = db.query(models.Dictionary).filter(
        models.Dictionary.company_id == company_id,
        models.Dictionary.kategoria == "VizsgalatiDatumKotelezoMod"
    ).first()
    if beallitas and beallitas.ertek == "CsakBepipalt":
        return "CsakBepipalt"
    return "AlapbolKotelezo"
