#!/usr/bin/env python3
"""Comprueba que el repo y D1 dicen lo mismo, palabra por palabra.

El repo es la fuente de verdad (corregido contra el impreso); D1 es lo que sirve
vedabase.cc. Cualquier divergencia es un fallo de sincronizacion, y la unica
forma de detectarla es esta: comparar el texto, no fiarse de que el UPDATE
devolviera 'rows_written'.

Lecciones que justifican cada detalle:
  - Comparar por PALABRAS, no por cadena: el repo lleva marcado de cursiva y D1
    no siempre, y una diferencia de espacios no es una divergencia real.
  - Informar la palabra concreta que difiere. Un '0.9995 de similitud' oculta
    405 sustituciones en un significado de 190 KB (paso con kb/86).
  - Leer la lista de ficheros de `git status`: solo interesan los tocados.

Uso:
  python3 verificar_paridad.py                 # los ficheros modificados en el repo
  python3 verificar_paridad.py --refs a,b,c    # referencias concretas
  python3 verificar_paridad.py --todos <libro> # todas las de un libro
"""
import json, re, sys, os, subprocess, pathlib, difflib, collections

ASTRO = pathlib.Path('/Users/jaganat/git_projects/astro_vedabase')
REPO = pathlib.Path('/Users/jaganat/git_projects/vedabase-original')
dec = json.JSONDecoder()

PROSA = {'teachings-of-lord-caitanya': 'tlc', 'nectar-of-devotion': 'nod',
         'krsna-the-supreme-personality-of-godhead': 'kb',
         'science-of-self-realization': 'ssr', 'teachings-of-queen-kunti': 'tqk',
         'path-of-perfection': 'pop', 'light-of-the-bhagavata': 'lob',
         'nectar-of-instruction': 'noi', 'elevation-to-krsna-consciousness': 'ekc',
         'beyond-birth-and-death': 'bbd', 'perfection-of-yoga': 'poy',
         # 15-ago: tres codigos estaban MAL y el comprobador saltaba esos libros
         # en silencio, porque `WHERE book='tys'` no devuelve nada y eso no es un
         # error, es cero filas. Los de D1 son ttp, ej y krp.
         'topmost-yoga-system': 'ttp', 'easy-journey-to-other-planets': 'ej',
         'on-the-way-to-krsna': 'owk', 'perfect-questions-perfect-answers': 'pqpa',
         'raja-vidya': 'rv', 'reservoir-of-pleasure': 'krp',
         'krsna-consciousness-the-topmost-yoga-system': 'ttp',
         # Y este faltaba del todo. Se sirve en vedabase.cc con paginas, pack y
         # descarga, y no lo cotejaba nadie: tampoco tiene escaneo.
         'life-comes-from-life': 'lcfl',
         # El Bhagavatam tiene ademas paginas preliminares que NO siguen el
         # patron sb-CANTO.CAP.VERSO y por eso `ref_de` devolvia None: el
         # sincronizador las daba por 'sin ref' y no las tocaba.
         'srimad-bhagavatam': 'sb'}
ESPECIALES = {'tlc': {'preface.md': '0a', 'prologue.md': '0b',
                      'lord-chaitanya-s-mission.md': '0c', 'introduction.md': '0d'},
              'nod': {'preface.md': '0a', 'introduction.md': '0b',
                      'concluding-words.md': 'concluding_words'},
              'kb': {'dedication.md': '0a', 'words-from-apple.md': '0b', 'preface.md': '0c',
                     'introduction-by-a-c-bhaktivedanta-swami-prabhupada.md': '0d'},
              # `sb/0b` es UNA sola fila de D1 que lleva la introduccion Y la
              # semblanza de Caitanya seguidas (100 KB). Los dos ficheros del
              # repo apuntan ahi; como la sincronizacion va anclada al contexto,
              # cada uno encuentra su tramo sin pisar al otro.
              'sb': {'preface.md': '0a', 'introduction.md': '0b',
                     'a-short-sketch-of-the-life-and-teachings.md': '0b'}}


def ref_de(fich):
    n = fich.split('/')[-1]
    for pat, fmt in ((r'sb-(\d+)\.(\d+)\.(.+)\.md', 'sb/{}/{}/{}'),
                     (r'cc-(adi|madhya|antya)-(\d+)\.(.+)\.md', 'cc/{}/{}/{}'),
                     (r'bg-(\d+)\.(.+)\.md', 'bg/{}/{}'),
                     (r'iso-(.+)\.md', 'iso/{}')):
        m = re.fullmatch(pat, n)
        if m: return fmt.format(*m.groups())
    pref = PROSA.get(fich.split('/')[0])
    if not pref: return None
    m = re.fullmatch(r'chapter-(\d+)\.md', n)
    if m: return f'{pref}/{m.group(1)}'
    esp = ESPECIALES.get(pref, {}).get(n)
    return f'{pref}/{esp}' if esp else None


def d1(sql):
    for _ in range(3):
        p = subprocess.run(['npx', 'wrangler', 'd1', 'execute', 'vedabase-search-db',
                            '--remote', '--json', '--command', sql],
                           capture_output=True, text=True, cwd=str(ASTRO),
                           env={**os.environ, 'CF_API_TOKEN': '', 'CLOUDFLARE_API_TOKEN': ''})
        for m in re.finditer(r'\[\s*\{', p.stdout):
            try: o, _ = dec.raw_decode(p.stdout[m.start():])
            except ValueError: continue
            if isinstance(o, list) and o and 'results' in o[0]: return o[0]['results']
    return []


INDIO = re.compile(r'[\u0900-\u097F\u0980-\u09FF]')


def pal(s):
    """Palabras comparables: sin marcado de cursiva, sin encabezados y SIN las
    lineas en devanagari/bengali. Esas viven en la columna `devanagari` de D1,
    no en translation/purport. Igual con las lineas de cita (`> ...`): son el
    verso, que vive en `verse_text`, y con la glosa palabra-por-palabra (muchos
    guiones largos), que vive en `synonyms`. Contarlas daba 300 fantasmas.
    Se quitan de LOS DOS lados, asi que la comparacion sigue siendo simetrica."""
    fuera = [l for l in (s or '').splitlines()
             if not l.startswith('#') and not l.lstrip().startswith('>')
             and not INDIO.search(l) and l.count('—') <= 3]
    return re.sub(r'[*_]', '', '\n'.join(fuera)).split()


def main():
    args = sys.argv[1:]
    if '--ficheros' in args:
        # Lista de ficheros del repo en un JSON (cada objeto con clave 'file').
        # Hace falta porque tras el commit `git status` sale limpio y el modo por
        # defecto no encuentra nada que comprobar.
        vistos = json.load(open(args[args.index('--ficheros') + 1], encoding='utf-8'))
        ficheros = {}
        for x in vistos:
            f = x['file'] if isinstance(x, dict) else x
            r = ref_de(f)
            if r: ficheros[r] = f
        refs = sorted(ficheros)
    elif '--refs' in args:
        refs = args[args.index('--refs') + 1].split(',')
        ficheros = {}
    else:
        salida = subprocess.run(['git', 'status', '--porcelain'], capture_output=True,
                                text=True, cwd=str(REPO)).stdout
        ficheros = {}
        for l in salida.splitlines():
            f = l[3:].strip()
            if not f.endswith('.md'): continue
            r = ref_de(f)
            if r: ficheros[r] = f
        refs = sorted(ficheros)
    if not refs:
        print('nada que comprobar'); return 0

    print(f'comprobando {len(refs)} referencias')
    q = lambda s: "'" + s.replace("'", "''") + "'"
    malos = []; sinfila = []
    for i in range(0, len(refs), 40):
        lote = refs[i:i + 40]
        filas = {r['ref']: r for r in d1(
            "SELECT ref, COALESCE(translation,'') t, COALESCE(purport,'') p FROM verses "
            f"WHERE lang='en' AND ref IN ({','.join(q(x) for x in lote)})")}
        for r in lote:
            if r not in filas: sinfila.append(r); continue
            f = ficheros.get(r)
            if not f: continue
            a = pal((REPO / f).read_text(encoding='utf-8'))
            b = pal(filas[r]['t'] + '\n' + filas[r]['p'])
            if a == b: continue
            sm = difflib.SequenceMatcher(None, b, a)
            dif = [(b[i1:i2], a[j1:j2]) for op, i1, i2, j1, j2 in sm.get_opcodes() if op != 'equal']
            malos.append((r, f, dif))
        print(f'  {min(i+40,len(refs))}/{len(refs)}', end='\r')

    print(f'\ndivergentes: {len(malos)} | sin fila en D1: {len(sinfila)}')
    for r, f, dif in malos[:40]:
        print(f'  {r}  ({len(dif)} bloque(s))')
        for x, y in dif[:3]:
            print(f"      D1: {' '.join(x)[:70]!r}\n    repo: {' '.join(y)[:70]!r}")
    if sinfila: print('  sin fila:', ', '.join(sinfila[:10]))
    json.dump([{'ref': r, 'file': f, 'bloques': len(d)} for r, f, d in malos],
              open(pathlib.Path(__file__).parent / 'paridad_divergentes.json', 'w'),
              ensure_ascii=False, indent=1)
    return 1 if malos else 0


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