"""Klasör alıcısı: klasör izleyen bir haber yazılımının benzeticisi (Python, yalnızca standart kitaplık).

Ajansın FTP/SFTP çıkışının (ya da ajans FTP kutusunun) yazdığı klasörü izler, dosyaları okur ve SQLite'a yazar;
küçük bir HTML önizleme üretir. Kılavuzlar ve kabul adımları bunu "test kurulumu" olarak kullanır.

Okunan biçimler: NewsML-G2, NITF 3.x, IPTC 7901 düz metin, ajans XML'i (yha_xml), ajans JSON'u (yha_json).
Kurallar:
  - `.yha-tmp` ile biten dosya yazılıyor demektir: atlanır (ajans yazmayı bitirince yeniden adlandırır).
  - Dosya adında `_IPTAL` varsa ya da içerik iptal diyorsa kayıt "iptal" olur (yayından kaldırılır).
  - Aynı haberin büyük sürümü küçüğünün üzerine yazar; küçük sürüm yok sayılır.
  - Fotoğraf ve video dosyaları yalnızca listelenir.

Kullanım: python klasor_alici.py <klasör> [--veri alici.db] [--onizleme onizleme.html] [--tek-sefer]
"""
from __future__ import annotations

import argparse
import html
import json
import pathlib
import re
import sqlite3
import time
import xml.etree.ElementTree as ET

NEWSML = "{http://iptc.org/std/nar/2006-10-01/}"
SEMA = """
create table if not exists haber (no text primary key, surum integer, durum text, baslik text, bicim text, dosya text,
  zaman real);
create table if not exists medya (dosya text primary key, zaman real);
create table if not exists okunan (dosya text primary key, boyut integer, degisme real);
"""


def kod_coz(veri: bytes) -> str:
    for kod in ("utf-8", "cp1254"):
        try:
            return veri.decode(kod)
        except UnicodeDecodeError:
            continue
    return veri.decode("latin-1")


def xml_ayristir(veri: bytes) -> ET.Element:
    """XML bildirimindeki kodlamayı (UTF-8, ISO-8859-9, Windows-1254) Python çözücüsüyle uygular."""
    m = re.match(rb'<\?xml[^>]*encoding="([^"]+)"', veri)
    kod = (m.group(1).decode().lower() if m else "utf-8").replace("windows-", "cp")
    metin = veri.decode(kod)
    metin = re.sub(r'^<\?xml[^>]*\?>', "", metin, count=1)
    return ET.fromstring(metin)


def newsml(kok: ET.Element) -> dict | None:
    for item in kok.iter(f"{NEWSML}newsItem"):
        sinif = item.find(f"{NEWSML}itemMeta/{NEWSML}itemClass")
        if sinif is None or sinif.get("qcode") != "ninat:text":
            continue
        durum = item.find(f"{NEWSML}itemMeta/{NEWSML}pubStatus")
        baslik = item.find(f".//{NEWSML}headline")
        return {"no": item.get("guid", "").split(":")[-1], "surum": int(item.get("version", "1")),
                "baslik": (baslik.text if baslik is not None else "") or "",
                "iptal": durum is not None and durum.get("qcode") == "stat:canceled"}
    return None


def nitf(kok: ET.Element) -> dict:
    doc = kok.find(".//doc-id")
    du = kok.find(".//du-key")
    docdata = kok.find(".//docdata")
    hl = kok.find(".//hedline/hl1")
    return {"no": doc.get("id-string") if doc is not None else "", "surum": int(du.get("version", "1")) if du is not None else 1,
            "baslik": "".join(hl.itertext()) if hl is not None else "",
            "iptal": docdata is not None and docdata.get("management-status") == "canceled"}


def yha_xml(kok: ET.Element) -> dict:
    return {"no": kok.get("no", ""), "surum": int(kok.get("surum", "1")), "baslik": kok.findtext("baslik") or "",
            "iptal": kok.get("durum") == "iptal_edildi"}


def iptc_7901(metin: str) -> dict:
    """IPTC 7901: SOH, ileti üstbilgisi, STX, anahtar satırı (`<no> ...`), [düzeltme/ambargo/tanıtım satırları], başlık."""
    govde = metin.split("\x02", 1)[-1].split("\x03", 1)[0]
    satirlar = [s.rstrip("\r") for s in govde.split("\n")]
    satirlar = [s for s in satirlar if s.strip()]
    if not satirlar:
        raise ValueError("boş IPTC 7901 dosyası")
    anahtar = satirlar[0].split()
    iptal = "IPTAL" in anahtar or any(s.startswith(("İPTAL", "IPTAL")) for s in satirlar[1:3])
    no = next((p for p in anahtar if re.match(r"^[HFV]-\d+$", p)), anahtar[0])
    baslik = next((s for s in satirlar[1:] if not re.match(r"^(DÜZELTME|DUZELTME|AMBARGO|\[TANITIM\]|İPTAL|IPTAL)", s)), "")
    surum = 2 if ("DUZELTME" in anahtar or "DÜZELTME" in anahtar) else 1
    return {"no": no, "surum": surum, "baslik": baslik, "iptal": iptal}


def oku(yol: pathlib.Path) -> tuple[str, dict] | None:
    """Dosyayı biçimine göre okur: (biçim, {no, surum, baslik, iptal}); medya dosyası için None."""
    uzanti = yol.suffix.lower()
    if uzanti in (".jpg", ".jpeg", ".png", ".mp4", ".mov"):
        return None
    veri = yol.read_bytes()
    if uzanti == ".json":
        j = json.loads(kod_coz(veri))
        ic = j.get("haber") or j.get("varlik") or j  # dosyada olay sarmalı (DisOlay) ya da doğrudan nesne
        return "yha_json", {"no": ic.get("no", ""), "surum": int(ic.get("surum_no", 1)),
                            "baslik": ic.get("baslik") or ic.get("altyazi") or "",
                            "iptal": j.get("olay") == "iptal" or ic.get("durum") in ("iptal_edildi", "geri_cekildi")}
    if veri.lstrip().startswith(b"<"):
        kok = xml_ayristir(veri)
        ad = kok.tag.split("}")[-1]
        if ad in ("newsMessage", "newsItem", "packageItem"):
            k = newsml(kok)
            return ("newsml_g2", k) if k else None
        if ad == "nitf":
            return "nitf", nitf(kok)
        if ad == "haber":
            return "yha_xml", yha_xml(kok)
        if ad == "varlik":  # tek başına servis edilen fotoğraf ya da video
            return "yha_xml", {"no": kok.get("no", ""), "surum": 1, "baslik": kok.findtext("altyazi") or "",
                               "iptal": kok.get("durum") == "geri_cekildi"}
        raise ValueError(f"tanınmayan XML kökü: {ad}")
    return "iptc_7901", iptc_7901(kod_coz(veri))


class Alici:
    def __init__(self, klasor: pathlib.Path, db: sqlite3.Connection):
        self.klasor, self.db = klasor, db
        self.db.executescript(SEMA)

    def tara(self) -> list[str]:
        """Klasörü bir kez tarar; işlenen dosya adlarını döndürür."""
        islenen = []
        for yol in sorted(self.klasor.rglob("*")):
            if not yol.is_file() or yol.name.endswith(".yha-tmp"):
                continue  # yazılmakta olan dosya
            st = yol.stat()
            once = self.db.execute("select boyut, degisme from okunan where dosya = ?", (str(yol),)).fetchone()
            if once == (st.st_size, st.st_mtime):
                continue
            try:
                sonuc = oku(yol)
            except (ValueError, ET.ParseError, UnicodeDecodeError) as h:
                print(f"OKUNAMADI {yol.name}: {h}")
                continue
            if sonuc is None:
                self.db.execute("insert or replace into medya values (?, ?)", (yol.name, time.time()))
            else:
                bicim, k = sonuc
                self.kaydet(bicim, k, yol.name, iptal=k["iptal"] or "_IPTAL" in yol.name)
            self.db.execute("insert or replace into okunan values (?, ?, ?)", (str(yol), st.st_size, st.st_mtime))
            islenen.append(yol.name)
        self.db.commit()
        return islenen

    def kaydet(self, bicim: str, k: dict, dosya: str, iptal: bool) -> None:
        once = self.db.execute("select surum, durum from haber where no = ?", (k["no"],)).fetchone()
        if iptal:
            self.db.execute("insert or replace into haber values (?, ?, 'iptal', ?, ?, ?, ?)",
                            (k["no"], max(k["surum"], once[0] if once else 0), k["baslik"], bicim, dosya, time.time()))
            return
        if once and (once[1] == "iptal" or once[0] >= k["surum"]):
            return  # iptal edilmiş haber geri gelmez; eski sürüm yok sayılır
        self.db.execute("insert or replace into haber values (?, ?, 'serviste', ?, ?, ?, ?)",
                        (k["no"], k["surum"], k["baslik"], bicim, dosya, time.time()))

    def onizleme(self) -> str:
        satirlar = self.db.execute("select no, surum, durum, baslik, bicim, dosya from haber order by zaman desc").fetchall()
        govde = "".join(
            f"<tr class='{html.escape(d)}'><td>{html.escape(n)}</td><td>{s}</td><td>{html.escape(d)}</td>"
            f"<td>{html.escape(b)}</td><td>{html.escape(bi)}</td><td>{html.escape(f)}</td></tr>"
            for n, s, d, b, bi, f in satirlar)
        return ("<!doctype html><meta charset='utf-8'><title>Klasör alıcısı</title>"
                "<style>body{font-family:sans-serif}td,th{border:1px solid #ccc;padding:4px}"
                ".iptal{text-decoration:line-through;color:#a00}</style>"
                "<h1>Klasör alıcısı</h1><table><tr><th>No</th><th>Sürüm</th><th>Durum</th><th>Başlık</th><th>Biçim</th>"
                f"<th>Dosya</th></tr>{govde}</table>")


def main() -> None:
    p = argparse.ArgumentParser(description="Klasör izleyen haber yazılımı benzeticisi")
    p.add_argument("klasor")
    p.add_argument("--veri", default="alici.db")
    p.add_argument("--onizleme", default="onizleme.html")
    p.add_argument("--tek-sefer", action="store_true")
    a = p.parse_args()
    alici = Alici(pathlib.Path(a.klasor), sqlite3.connect(a.veri))
    while True:
        for ad in alici.tara():
            print("okundu:", ad)
        pathlib.Path(a.onizleme).write_text(alici.onizleme(), encoding="utf-8")
        if a.tek_sefer:
            break
        time.sleep(5)


if __name__ == "__main__":
    main()
