#!/usr/bin/env python3
"""Comprueba que los ebooks PUBLICADOS llevan los cambios del dia, uno a uno.

Que responde. No «se subio el fichero» —eso lo dice un 200— sino «el fichero que
sirve vedabase.cc dice lo que el repo dice hoy», en EPUB, PDF y MOBI por separado.

Como se hacen las sondas. Del diff de git, no de `corrections.jsonl`: el ledger
no siempre registra la fecha real (las de Easy Journey de este dia quedaron
fechadas el 3-ago) y ademas no recoge lo que se corrigio a mano. Del diff se
toman lineas que el repo dice AHORA y no decia en la base, con dos condiciones:

  DISCRIMINANTE  si el cambio fuese solo un diacritico, una comparacion que
                 ignora diacriticos no probaria nada. Se descarta.
  UNICA          la linea aparece una sola vez en su fichero, para que
                 encontrarla no pueda ser casualidad.

Tres trampas de la EXTRACCION, que no del libro. Las tres hacian parecer que
faltaba texto que si estaba:

  1. `pdftotext` sin `-layout` pierde palabras en las lineas muy justificadas:
     '...attachment for Krsna consciousness' salia '...attachment for
     consciousness'. Con `-layout` se arregla. 15 de 21 libros daban falso
     negativo por esto.
  2. El numero de pagina se cuela EN MEDIO del parrafo ('...stood before 351
     Krsna...'). Se quitan antes las lineas de solo numero y las que se repiten
     mucho, que es lo que es un titulillo.
  3. El devanagari del PDF no se recupera: las conjuntas no llevan mapeo
     inverso y salen descolocadas. Para esos casos NO hay comprobacion textual
     posible; hay que renderizar la pagina y mirarla (`pdftoppm`), que es lo que
     se hizo con bg 2.9, 18.44 y 18.66.

Uso:
    python3 verificar_ebooks.py --sondas          # del diff de hoy
    python3 verificar_ebooks.py --bajar           # trae lo que sirve el sitio
    python3 verificar_ebooks.py --comprobar epub pdf mobi
"""
import json, re, sys, subprocess, unicodedata, pathlib, collections, difflib, random, zipfile, html

HERE = pathlib.Path(__file__).resolve().parent
REPO = pathlib.Path('/Users/jaganat/git_projects/vedabase-original')
TMP = pathlib.Path('/tmp/vbcheck'); VIVO = TMP / 'vivo'; TXT = TMP / 'txt'
BASE_URL = 'https://vedabase.cc/downloads'

SLUGS = ('bbd bg ej ekc kb nod pqpa poy tlc tqk cc-adi cc-madhya cc-antya '
         'sb-canto-3 sb-canto-4 sb-canto-5 sb-canto-6 sb-canto-7 sb-canto-8 '
         'sb-canto-9 sb-canto-10').split()


def compacto(s):
    """Sin diacriticos, SOLO letras, sin espacios. Sin digitos a proposito: lo
    que se cuela entre paginas son numeros."""
    s = unicodedata.normalize('NFD', s or '')
    s = ''.join(c for c in s if unicodedata.category(c) != 'Mn')
    return re.sub(r'[^a-z]', '', s.lower())


def exacto(s):
    """Conserva diacriticos y escritura india. Con `\\W` las matras y el virama
    se borraban —en Python no cuentan como caracter de palabra— y 'गो' y 'गौ'
    quedaban iguales: justo el cambio que habia que probar."""
    s = unicodedata.normalize('NFC', s or '')
    return ''.join(c for c in s if unicodedata.category(c)[0] in 'LM').lower()


def limpiar_pagina(t):
    ls = t.splitlines()
    fr = collections.Counter(l.strip() for l in ls if l.strip())
    out = []
    for l in ls:
        s = l.strip()
        if not s: continue
        if re.fullmatch(r'[\divxlcIVXLC]{1,6}', s): continue
        if len(s) < 60 and fr[s] >= 5: continue
        out.append(l)
    return '\n'.join(out)


def slug_de(p):
    d = p.split('/')[0]
    if d == 'srimad-bhagavatam':
        m = re.search(r'canto-(\d+)', p)
        return f'sb-canto-{int(m.group(1))}' if m else None
    if d == 'sri-caitanya-caritamrta':
        m = re.search(r'/(adi|madhya|antya)-lila/', p)
        return f'cc-{m.group(1)}' if m else None
    return {'teachings-of-lord-caitanya': 'tlc', 'teachings-of-queen-kunti': 'tqk',
            'krsna-the-supreme-personality-of-godhead': 'kb',
            'bhagavad-gita-as-it-is': 'bg', 'elevation-to-krsna-consciousness': 'ekc',
            'beyond-birth-and-death': 'bbd', 'perfection-of-yoga': 'poy',
            'perfect-questions-perfect-answers': 'pqpa', 'nectar-of-devotion': 'nod',
            'easy-journey-to-other-planets': 'ej'}.get(d)


def g(*a):
    return subprocess.run(['git', *a], capture_output=True, text=True, cwd=str(REPO)).stdout


def sondas(desde):
    fich = [f for f in g('diff', '--name-only', desde, 'HEAD', '--', '*.md').splitlines()
            if not f.startswith('translations/')]
    por = collections.defaultdict(list)
    for f in fich:
        s = slug_de(f)
        if not s: continue
        viejo = g('show', f'{desde}:{f}')
        try: nuevo = (REPO / f).read_text(encoding='utf-8')
        except Exception: continue
        A, B = viejo.splitlines(), nuevo.splitlines()
        for op, i1, i2, j1, j2 in difflib.SequenceMatcher(None, A, B, autojunk=False).get_opcodes():
            if op == 'equal': continue
            for l in B[j1:j2]:
                c = compacto(l)
                if len(c) < 80: continue
                if any(compacto(x) == c for x in A[i1:i2]): continue   # solo diacritico
                if nuevo.count(l) != 1: continue                        # tiene que ser unica
                por[s].append({'file': f, 'texto': re.sub(r'[*_>#\\]', '', l).strip(),
                               'compacto': c})
    random.seed(7)
    sel = {}
    for s, v in por.items():
        pf = collections.defaultdict(list)
        for x in v: pf[x['file']].append(x)
        e = [random.choice(pf[k]) for k in sorted(pf)]
        random.shuffle(e); sel[s] = e[:5]
    TMP.mkdir(exist_ok=True)
    (TMP / 'sondas.json').write_text(json.dumps(sel, ensure_ascii=False, indent=1))
    for s in SLUGS: print(f'  {s:14} {len(por.get(s, [])):5} fragmentos | {len(sel.get(s, []))} sondas')
    sin = [s for s in SLUGS if not sel.get(s)]
    if sin: print(f'\n  sin sonda automatica (mirar a mano): {sin}')
    return 0


def bajar():
    VIVO.mkdir(parents=True, exist_ok=True); TXT.mkdir(parents=True, exist_ok=True)
    for s in SLUGS:
        for ext in ('epub', 'pdf', 'mobi'):
            d = VIVO / f'{s}.{ext}'
            subprocess.run(['curl', '-s', '-A', 'Mozilla/5.0', f'{BASE_URL}/{s}.{ext}',
                            '-o', str(d)], check=False)
        # EPUB
        try:
            z = zipfile.ZipFile(VIVO / f'{s}.epub'); out = []
            for n in z.namelist():
                if n.lower().endswith(('.xhtml', '.html', '.htm')):
                    x = z.read(n).decode('utf-8', 'replace')
                    x = re.sub(r'<(script|style)[^>]*>.*?</\1>', ' ', x, flags=re.S | re.I)
                    out.append(html.unescape(re.sub(r'<[^>]+>', ' ', x)))
            (TXT / f'{s}.epub.txt').write_text('\n'.join(out), encoding='utf-8')
        except Exception as e: print(f'  {s}.epub: {e}')
        # PDF — con -layout SIEMPRE (ver cabecera)
        subprocess.run(['pdftotext', '-layout', str(VIVO / f'{s}.pdf'),
                        str(TXT / f'{s}.pdf.txt')], check=False)
        # MOBI
        subprocess.run(['ebook-convert', str(VIVO / f'{s}.mobi'),
                        str(TXT / f'{s}.mobi.txt')], capture_output=True, check=False)
        print(f'  {s} listo')
    return 0


def comprobar(formatos):
    gen = json.loads((TMP / 'sondas.json').read_text(encoding='utf-8'))
    man = TMP / 'sondas_manuales.json'
    extra = json.loads(man.read_text(encoding='utf-8')) if man.exists() else {}
    tot = collections.Counter(); fallos = []
    for s in SLUGS:
        for fmt in formatos:
            p = TXT / f'{s}.{fmt}.txt'
            if not p.exists():
                print(f'  {s:14} {fmt:6} SIN TEXTO'); tot['sin texto'] += 1; continue
            t = limpiar_pagina(p.read_text(encoding='utf-8', errors='replace'))
            tc, te = compacto(t), exacto(t)
            ok = mal = 0
            for x in (extra.get(s) or gen.get(s, [])):
                if x.get('modo') == 'exacto':
                    b = exacto(x['presente']) in te
                    if b and x.get('ausente'): b = exacto(x['ausente']) not in te
                    et = x['presente']
                else:
                    b = x['compacto'] in tc; et = x['texto'][:64]
                if b: ok += 1
                else: mal += 1; fallos.append((s, fmt, et, x.get('file', '')))
            print(f'  {s:14} {fmt:6} {ok:>3}/{ok+mal:<3} '
                  f'{"OK" if mal == 0 and ok else (str(mal)+" FALLAN" if mal else "SIN SONDA")}')
            tot['ok' if mal == 0 and ok else 'fallo'] += 1
    for k, v in tot.most_common(): print(f'  {v:4}  libro/formato: {k}')
    for s, fmt, e, f in fallos[:40]: print(f'    {s}/{fmt}  {f}\n        {e!r}')
    if fallos:
        print('\n  Antes de dar por malo un PDF: comprobar que no es la extraccion.'
              '\n  pdftoppm -f <pag> -l <pag> -r 130 -png libro.pdf /tmp/pag  y mirar la imagen.')
    return 1 if fallos else 0


def main():
    if '--sondas' in sys.argv:
        i = sys.argv.index('--sondas')
        desde = sys.argv[i + 1] if len(sys.argv) > i + 1 and not sys.argv[i + 1].startswith('--') \
                else g('log', '--since=1 day ago', '--format=%H').split()[-1] + '^'
        return sondas(desde)
    if '--bajar' in sys.argv: return bajar()
    if '--comprobar' in sys.argv:
        f = [a for a in sys.argv[sys.argv.index('--comprobar') + 1:] if not a.startswith('--')]
        return comprobar(f or ['epub', 'pdf', 'mobi'])
    print(__doc__); return 1


if __name__ == '__main__':
    sys.exit(main())
