"""
Hromadne stazeni lekovych zaznamu z eReceptu pro registrovane pacienty Medicusu.

Spusteni:
    # pouze rodina (testovaci run)
    python 07StahnoutVsechny.py --prijmeni Buzalka,Buzalkova,Kusinova

    # vsichni registrovani pacienti
    python 07StahnoutVsechny.py

    # davkovani po castech
    python 07StahnoutVsechny.py --offset 100 --limit 50

Vystup:
    Konzole  — jeden stručny radek na pacienta
    Logs/    — kompletni log se vsemi detaily (UTF-8)

Logika poctu mesicu:
    - prvni stazeni pacienta  → 60 mesicu (maximum)
    - opakovane stazeni       → ceil(pocet_dni_od_posledniho / 30) + 1
      (prekryv 1 mesic pro jistotu, INSERT IGNORE zajisti bez duplikatu)

XML archiv:
    xml_archive/YYYY-MM-DD/{Prijmeni}_{Jmena}_{datnar}.xml
    Cesta ulozena take v zprava.xml_soubor pro snadne dohledani.
"""

import argparse
import importlib.util
import logging
import math
import random
import sys
import time
import uuid
from datetime import datetime, timezone, date
from pathlib import Path
from xml.sax.saxutils import escape as xml_escape

import pymysql.cursors
from requests import Session
from requests_pkcs12 import Pkcs12Adapter
from Knihovny.medicus_db import get_medicus_connection
from Knihovny.mysql_db import connect_mysql
from Knihovny.najdi_dropbox import get_dropbox_root

# Windows konzole — nahrad neunikatni znaky misto padu
if hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(errors="replace")

# ── Import parsovaci logiky z 06 ──────────────────────────────────────────────
_spec = importlib.util.spec_from_file_location(
    "m06", Path(__file__).parent / "06UlozitDoMySQL.py"
)
_m06 = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_m06)

parsuj_xml         = _m06.parsuj_xml
uloz               = _m06.uloz
inicializuj_schema = _m06.inicializuj_schema

# ── Konfigurace eRecept ───────────────────────────────────────────────────────
PFX_FILE   = Path(__file__).parent.parent / "AMBSUKL214235369G_31DEC2024.pfx"
PFX_PASS   = "Vlado7309208104++"
API_USER   = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379"
API_PASS   = "Buzalka@Vladimir2025"
UZIVATEL   = "E08C89C6-2B1A-4EBA-8ED9-4E3E63618379"
PRACOVISTE = "00214235367"
ENDPOINT   = "https://lekar-soap.erecept.sukl.cz/cuer/Lekar2"

POCET_ZNAKU_ATC  = 7
POCET_MESICU_MAX = 60
PAUZA_MIN = 10  # sekund
PAUZA_MAX = 20  # sekund

ICP = '09305001'
ODB = '001'

# ── Adresare ──────────────────────────────────────────────────────────────────
XML_DIR  = Path(get_dropbox_root()) / "Ordinace" / "Dokumentace_ke_zpracování" / "Zúčtovací zprávy" / "LékovýZáznamWithClaude" / "xml_archive"
LOGS_DIR = Path(__file__).parent / "Logs"


# ── Logging ───────────────────────────────────────────────────────────────────

def setup_logging(dnes_str, cas_str):
    """
    Dva handlery:
      - soubor (DEBUG)  → Logs/YYYY-MM-DD_HH-MM-SS.log  — vse vcetne detailu
      - konzole (INFO)  → stdout                         — jen souhrnne radky
    """
    LOGS_DIR.mkdir(exist_ok=True)
    log_soubor = LOGS_DIR / f"{dnes_str}_{cas_str}.log"

    log = logging.getLogger("lz")
    log.setLevel(logging.DEBUG)

    fh = logging.FileHandler(log_soubor, encoding="utf-8")
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(logging.Formatter("%(asctime)s  %(message)s", datefmt="%H:%M:%S"))

    ch = logging.StreamHandler(sys.stdout)
    ch.setLevel(logging.INFO)
    ch.setFormatter(logging.Formatter("%(message)s"))

    log.addHandler(fh)
    log.addHandler(ch)
    return log, log_soubor


# ── Firebird: nacteni registrovanych pacientu ─────────────────────────────────

_SQL_VSICHNI = """
    SELECT
        KAR.IDPAC,
        KAR.PRIJMENI,
        KAR.JMENO,
        KAR.DATNAR
    FROM KAR
    WHERE (vyrazen = 'N')
    AND EXISTS (
        SELECT id FROM registr r
        JOIN icp i ON r.idicp = i.idicp
        WHERE r.idpac = kar.idpac
          AND (r.datum <= ?)
          AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= ?)
          AND (r.priznak IN ('V','D','A'))
          AND (i.icp = ?)
          AND (i.odb = ?)
    )
    ORDER BY KAR.PRIJMENI_UP, KAR.RODCIS
"""

_SQL_FILTR = """
    SELECT
        KAR.IDPAC,
        KAR.PRIJMENI,
        KAR.JMENO,
        KAR.DATNAR
    FROM KAR
    WHERE (vyrazen = 'N')
    AND KAR.PRIJMENI IN ({ph})
    ORDER BY KAR.PRIJMENI_UP, KAR.RODCIS
"""


def nacti_pacienty(prijmeni_filtr=None):
    conn = get_medicus_connection()
    try:
        cur = conn.cursor()
        if prijmeni_filtr:
            ph = ",".join("?" * len(prijmeni_filtr))
            cur.execute(_SQL_FILTR.format(ph=ph), prijmeni_filtr)
        else:
            dnes = date.today().isoformat()
            cur.execute(_SQL_VSICHNI, (dnes, dnes, ICP, ODB))
        cols = [d[0].lower() for d in cur.description]
        return [dict(zip(cols, row)) for row in cur.fetchall()]
    finally:
        conn.close()


# ── MySQL: pacient UPSERT ─────────────────────────────────────────────────────

def upsert_pacient(cur, pac):
    cur.execute("""
        INSERT INTO pacient (idpac, prijmeni, jmena, datum_narozeni)
        VALUES (%s, %s, %s, %s)
        ON DUPLICATE KEY UPDATE
            prijmeni = VALUES(prijmeni),
            jmena    = VALUES(jmena)
    """, (pac["idpac"], pac["prijmeni"], pac["jmeno"], pac["datnar"]))
    cur.execute("SELECT id FROM pacient WHERE idpac = %s", (pac["idpac"],))
    return cur.fetchone()["id"]


def posledni_stazeni(cur, pacient_id):
    cur.execute(
        "SELECT MAX(stazeno) AS posledni FROM zprava WHERE pacient_id = %s",
        (pacient_id,)
    )
    row = cur.fetchone()
    return row["posledni"] if row and row["posledni"] else None


def vypocti_pocet_mesicu(posledni):
    if posledni is None:
        return POCET_MESICU_MAX
    delta_dni = (datetime.now() - posledni).days
    return min(math.ceil(delta_dni / 30) + 1, POCET_MESICU_MAX)


def uloz_poznamku(conn, pacient_id, poznamka):
    with conn.cursor() as cur:
        cur.execute(
            "UPDATE pacient SET poznamka = %s WHERE id = %s",
            (poznamka, pacient_id)
        )
    conn.commit()


# ── SOAP volani ───────────────────────────────────────────────────────────────

def extrahuj_soap_fault(xml_text):
    """Vraci text chyby pokud odpoved obsahuje SOAP Fault, jinak None."""
    try:
        import xml.etree.ElementTree as ET
        NS_SOAP = "http://schemas.xmlsoap.org/soap/envelope/"
        NS_SUKL = "http://www.sukl.cz/erp/201912"
        root = ET.fromstring(xml_text)
        body = root.find(f"{{{NS_SOAP}}}Body")
        if body is None:
            return "Chybejici SOAP Body"
        fault = body.find(f"{{{NS_SOAP}}}Fault") or body.find("Fault")
        if fault is not None:
            faultstring = (fault.findtext("faultstring")
                           or fault.findtext("faultcode")
                           or "Nezname SOAP Fault")
            detail = fault.find("detail")
            if detail is not None and detail.text:
                faultstring = f"{faultstring}: {detail.text.strip()[:200]}"
            return faultstring
        if body.find(f"{{{NS_SUKL}}}NacistLekovyZaznamOdpoved") is None:
            first = list(body)
            tag = first[0].tag if first else "prazdne Body"
            return f"Neocekavana odpoved: {tag}"
        return None
    except Exception as e:
        return f"Chyba pri parsovani odpovedi: {e}"


def nacti_lekovy_zaznam(sess, prijmeni, jmena, datum_narozeni, pocet_mesicu):
    id_zpravy = str(uuid.uuid4())
    odeslano  = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00")
    soap = (
        '<?xml version="1.0" encoding="UTF-8"?>'
        '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">'
        '<soapenv:Body>'
        f'<NacistLekovyZaznamLekarDotaz xmlns="http://www.sukl.cz/erp/201912">'
        f'<Doklad>'
        f'<Pristupujici>'
        f'<Uzivatel>{UZIVATEL}</Uzivatel>'
        f'<Pracoviste>{PRACOVISTE}</Pracoviste>'
        f'</Pristupujici>'
        f'<PocetZnakuATC>{POCET_ZNAKU_ATC}</PocetZnakuATC>'
        f'<PocetMesicu>{pocet_mesicu}</PocetMesicu>'
        f'<Pacient><Totoznost><Jmeno>'
        f'<Prijmeni>{xml_escape(prijmeni)}</Prijmeni>'
        f'<Jmena>{xml_escape(jmena)}</Jmena>'
        f'</Jmeno><DatumNarozeni>{datum_narozeni}</DatumNarozeni>'
        f'</Totoznost></Pacient>'
        f'</Doklad>'
        f'<Zprava>'
        f'<ID_Zpravy>{id_zpravy}</ID_Zpravy>'
        f'<Verze>202501A</Verze>'
        f'<Odeslano>{odeslano}</Odeslano>'
        f'<SW_Klienta>MEDICUS_____</SW_Klienta>'
        f'</Zprava>'
        f'</NacistLekovyZaznamLekarDotaz>'
        '</soapenv:Body>'
        '</soapenv:Envelope>'
    )
    headers = {
        "Content-Type": 'text/xml; charset="UTF-8"',
        "SOAPAction":   '"NacistLekovyZaznam"',
        "User-Agent":   "Medicus",
    }
    resp = sess.post(ENDPOINT, data=soap.encode("utf-8"), headers=headers, timeout=60)
    if resp.status_code != 200:
        raise RuntimeError(f"HTTP {resp.status_code}: {resp.text[:300]}")
    return resp.text


# ── XML archiv ────────────────────────────────────────────────────────────────

def uloz_xml_na_disk(xml_text, prijmeni, jmena, datnar_str, dnes_str):
    adr = XML_DIR / dnes_str
    adr.mkdir(parents=True, exist_ok=True)
    nazev = f"{prijmeni}_{jmena}_{datnar_str}.xml".replace(" ", "_")
    soubor = adr / nazev
    soubor.write_text(xml_text, encoding="utf-8")
    return str(soubor.relative_to(Path(__file__).parent))


# ── Hlavni smycka ─────────────────────────────────────────────────────────────

def main():
    ap = argparse.ArgumentParser(description="Hromadne stazeni lekovych zaznamu z eReceptu")
    ap.add_argument("--prijmeni", default=None,
                    help="Filtr prijmeni oddelena carkou, napr: Buzalka,Buzalkova,Kusinova")
    ap.add_argument("--limit",   type=int, default=None, help="Zpracuj pouze N pacientu")
    ap.add_argument("--offset",  type=int, default=0,
                    help="Preskoc prvnich N pacientu (pro postupne davkovani)")
    args = ap.parse_args()

    dnes_str = date.today().isoformat()
    cas_str  = datetime.now().strftime("%H-%M-%S")
    log, log_soubor = setup_logging(dnes_str, cas_str)

    prijmeni_filtr = None
    if args.prijmeni:
        prijmeni_filtr = [p.strip() for p in args.prijmeni.split(",")]
        log.info(f"Filtr prijmeni: {prijmeni_filtr}")

    log.info("Nacitam pacienty z Medicusu...")
    pacienti = nacti_pacienty(prijmeni_filtr)
    log.debug(f"Celkem registrovanych: {len(pacienti)}")

    if args.offset:
        pacienti = pacienti[args.offset:]
        log.debug(f"Preskoceno: {args.offset}")
    if args.limit:
        pacienti = pacienti[:args.limit]
        log.debug(f"Omezeno na: {len(pacienti)}")

    celkem = len(pacienti)
    log.info(f"Pacientu ke zpracovani: {celkem}  |  log: {log_soubor.name}")

    if not celkem:
        log.info("Zadni pacienti — konec.")
        return

    conn = connect_mysql(database="medicus", cursorclass=pymysql.cursors.DictCursor)
    inicializuj_schema(conn)
    log.debug("MySQL schema OK")

    sess = Session()
    sess.mount("https://", Pkcs12Adapter(pkcs12_filename=PFX_FILE, pkcs12_password=PFX_PASS))
    sess.auth = (API_USER, API_PASS)

    ok = 0
    chyby = 0

    try:
        for i, pac in enumerate(pacienti, 1):
            prijmeni   = pac["prijmeni"]
            jmena      = pac["jmeno"]
            datnar     = pac["datnar"]
            datnar_str = datnar.isoformat() if hasattr(datnar, "isoformat") else str(datnar)
            jmeno_str  = f"{prijmeni} {jmena}"

            log.debug(f"[{i:4}/{celkem}] {jmeno_str} (*{datnar_str})")

            with conn.cursor() as cur:
                pacient_id = upsert_pacient(cur, pac)
                posledni   = posledni_stazeni(cur, pacient_id)
            conn.commit()

            pocet_mesicu = vypocti_pocet_mesicu(posledni)
            log.debug(f"  stahuju {pocet_mesicu}m  "
                      f"(posledni: {posledni.strftime('%Y-%m-%d') if posledni else 'nikdy'})")

            # Zavolej API
            try:
                xml_text = nacti_lekovy_zaznam(sess, prijmeni, jmena, datnar_str, pocet_mesicu)
            except Exception as e:
                zprava_chyby = str(e)[:400]
                log.debug(f"  CHYBA API: {zprava_chyby}")
                log.info(f"[{i:4}/{celkem}] {jmeno_str:<30}  CHYBA  {zprava_chyby[:60]}")
                uloz_poznamku(conn, pacient_id, zprava_chyby)
                chyby += 1
                continue

            # Detekuj SOAP Fault
            soap_fault = extrahuj_soap_fault(xml_text)
            if soap_fault:
                log.debug(f"  SOAP FAULT: {soap_fault}")
                log.info(f"[{i:4}/{celkem}] {jmeno_str:<30}  CHYBA  {soap_fault[:60]}")
                uloz_poznamku(conn, pacient_id, soap_fault[:400])
                chyby += 1
                continue

            # Uloz XML
            xml_soubor = uloz_xml_na_disk(xml_text, prijmeni, jmena, datnar_str, dnes_str)
            xml_path   = Path(__file__).parent / xml_soubor
            kb = xml_path.stat().st_size // 1024
            log.debug(f"  XML: {xml_soubor}  ({kb} KB)")

            # Parsuj + uloz do MySQL
            try:
                zprava_d, predpisy, vydeji, predepisujici, vydavajici = parsuj_xml(xml_path)
                stats = uloz(conn, zprava_d, predpisy, vydeji, predepisujici, vydavajici,
                             pacient_id=pacient_id, xml_soubor=xml_soubor)
                uloz_poznamku(conn, pacient_id, None)
                log.debug(f"  predpisy: {stats['predpisy_novych']}n/{stats['predpisy_celkem']}  "
                          f"vydeji: {stats['vydeji_novych']}n/{stats['vydeji_celkem']}  "
                          f"slozky: {stats['predpis_slozka']}p/{stats['vydej_slozka']}v")
                log.info(f"[{i:4}/{celkem}] {jmeno_str:<30}  OK    "
                         f"{stats['predpisy_celkem']:4}p  {stats['vydeji_celkem']:4}v  {kb:4} KB")
                ok += 1
            except Exception as e:
                zprava_chyby = str(e)[:400]
                log.debug(f"  CHYBA parsovani/ulozeni: {zprava_chyby}")
                log.info(f"[{i:4}/{celkem}] {jmeno_str:<30}  CHYBA  {zprava_chyby[:60]}")
                uloz_poznamku(conn, pacient_id, zprava_chyby)
                chyby += 1

            if i < celkem:
                pauza = random.randint(PAUZA_MIN, PAUZA_MAX)
                log.debug(f"  cekam {pauza}s ...")
                time.sleep(pauza)

    finally:
        conn.close()
        sess.close()

    zhrnutí = f"Hotovo: {ok} OK  |  {chyby} chyb  |  celkem {celkem} pacientu"
    log.info("=" * 55)
    log.info(zhrnutí)


if __name__ == "__main__":
    main()
