#!/usr/bin/env python3
"""Lleva a D1 y a content/en un cambio hecho en UNA ocurrencia concreta.

`sincronizar_masivo.py` sustituye con limite de palabra en todo el campo. Vale
cuando el cambio era de fichero entero, pero NO para los posicionales: si el
repo cambio 2 de las 5 apariciones de 'worshipers', una sustitucion global en D1
cambiaria las cinco y las dos copias dejarian de decir lo mismo.

Aqui se ancla al texto de alrededor: se toma el contexto del repo (unas palabras
a cada lado), se busca en el campo de D1 con la palabra VIEJA y se sustituye solo
ese fragmento. Si el ancla no aparece exactamente una vez, no se toca nada y se
informa: es preferible una divergencia detectada a una sustitucion a ciegas.

Ademas evita el limite de tamano: `kb/86` tiene un significado de 190 KB y un
UPDATE con el campo entero da SQLITE_TOOBIG. Un REPLACE anclado ocupa unos
cientos de bytes.

Uso: python3 sincronizar_posicional.py posicionales.json [--aplicar]
"""
import json, re, sys, os, subprocess, pathlib, collections, unicodedata

HERE = pathlib.Path(__file__).resolve().parent
ASTRO = pathlib.Path('/Users/jaganat/git_projects/astro_vedabase')
REPO = pathlib.Path('/Users/jaganat/git_projects/vedabase-original')
sys.path.insert(0, str(HERE))
from verificar_paridad import ref_de
q = lambda s: "'" + s.replace("'", "''") + "'"
dec = json.JSONDecoder()


def d1(sql, js=False):
    args = ['npx', 'wrangler', 'd1', 'execute', 'vedabase-search-db', '--remote']
    if js: args.append('--json')
    args += ['--command', sql]
    for _ in range(3):
        p = subprocess.run(args, capture_output=True, text=True, cwd=str(ASTRO),
                           env={**os.environ, 'CF_API_TOKEN': '', 'CLOUDFLARE_API_TOKEN': ''})
        if js:
            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']
        elif 'rows_written' in p.stdout:
            return True
    return [] if js else False


def anclas(txt, palabra, nueva, lados=9):
    """Devuelve, por cada ocurrencia ya cambiada en el repo, un patron que
    localiza el fragmento equivalente en D1 con la palabra VIEJA todavia puesta.

    El patron tolera cualquier espaciado entre palabras: el repo parte las lineas
    y D1 no siempre igual, y un ancla con espacios simples no aparecia nunca.
    Fallaban 10 de 23 por esto."""
    salidas = []
    for m in re.finditer(rf'\b{re.escape(nueva)}\b', txt):
        # Se toma la SUBCADENA real del repo, no una lista de palabras: unir
        # palabras con \s+ metia un espacio donde el texto lleva guion
        # ('bhaiva-avatars' quedaba como 'bhaiva -avatars') y el ancla no
        # aparecia nunca. Solo se flexibilizan los espacios ya existentes.
        ini = m.start(); fin = m.end()
        for _ in range(lados):
            j = txt.rfind(' ', 0, ini)
            if j < 0: break
            ini = j
        for _ in range(lados):
            j = txt.find(' ', fin + 1)
            if j < 0: break
            fin = j
        trozo = txt[ini:fin]
        vieja = palabra if m.group(0)[:1].islower() else palabra.capitalize()
        antes = trozo[:m.start() - ini] + vieja + trozo[m.end() - ini:]
        pat = re.sub(r'(\\\s)+', r'\\s+', re.escape(antes))
        salidas.append((re.compile(pat), vieja, m.group(0)))
    return salidas


def main():
    datos = json.load(open(sys.argv[1], encoding='utf-8'))
    aplicar = '--aplicar' in sys.argv
    porfich = collections.defaultdict(list)
    for x in datos:
        porfich[x['file']].append((x['antes'], x['despues']))

    ok = fallo = ce = 0
    for fich, cambios in sorted(porfich.items()):
        ref = ref_de(fich)
        if not ref: print('  sin ref:', fich); fallo += 1; continue
        filas = d1(f"SELECT COALESCE(purport,'') p, COALESCE(translation,'') t "
                   f"FROM verses WHERE ref={q(ref)} AND lang='en'", js=True)
        if not filas: print('  sin fila en D1:', ref); fallo += 1; continue
        r = filas[0]
        txt = (REPO / fich).read_text(encoding='utf-8')
        stmts = []
        for viejo_p, nuevo_p in cambios:
            hecho = False
            # Se prueban varias anchuras: un ancla larga puede cruzar la
            # frontera entre translation y purport (en bg/6/47 arrastraba el
            # final de la traduccion, que en D1 esta en OTRA columna) y una
            # corta puede no ser unica. Se coge la primera que sea unica.
            for campo, val, lados in [(c, v, n) for n in (9, 6, 4, 3)
                                      for c, v in (('purport', r['p']), ('translation', r['t']))]:
                for pat, vieja, nueva in anclas(txt, viejo_p, nuevo_p, lados=lados):
                    ms = list(pat.finditer(val))
                    if len(ms) != 1: continue
                    v = ms[0].group(0)                    # literal exacto de D1
                    n = re.sub(rf'\b{re.escape(vieja)}\b', nueva, v, count=1)
                    if n == v: continue
                    stmts.append(f"UPDATE verses SET {campo}=REPLACE({campo},{q(v)},{q(n)}) "
                                 f"WHERE ref={q(ref)} AND lang='en';")
                    hecho = True
                    break
                if hecho: break
            if not hecho:
                print(f'  {ref}: sin ancla unica para {viejo_p!r} -> {nuevo_p!r}'); fallo += 1
        if not stmts: continue
        print(f'  {ref}: {len(stmts)} sustitucion(es) ancladas')
        if aplicar:
            p = pathlib.Path(HERE / '_posicional.sql')
            p.write_text('\n'.join(stmts) + '\n', encoding='utf-8')
            r2 = subprocess.run(['npx', 'wrangler', 'd1', 'execute', 'vedabase-search-db',
                                 '--remote', '--file', str(p)], capture_output=True, text=True,
                                cwd=str(ASTRO),
                                env={**os.environ, 'CF_API_TOKEN': '', 'CLOUDFLARE_API_TOKEN': ''})
            if 'rows_written' in r2.stdout: ok += 1
            else: print('   FALLO en D1'); fallo += 1
            p.unlink(missing_ok=True)
            # content/en: el fichero es el mismo texto, se copia tal cual
            f = ASTRO / 'content/en/books' / ref.split('/')[0] / (ref.split('/', 1)[1].replace('/', '-') + '.md')
            if f.exists():
                t2 = f.read_text(encoding='utf-8'); orig = t2
                for viejo_p, nuevo_p in cambios:
                    for pat, vieja, nueva in anclas(txt, viejo_p, nuevo_p):
                        ms = list(pat.finditer(t2))
                        if len(ms) != 1: continue
                        v = ms[0].group(0)
                        t2 = t2.replace(v, re.sub(rf'\b{re.escape(vieja)}\b', nueva, v, count=1), 1)
                        break
                if t2 != orig: f.write_text(t2, encoding='utf-8'); ce += 1
    print(f'\nD1: {ok} refs | fallos: {fallo} | content/en: {ce}')
    return 0


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