#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Precarga los delegados del XVI Encuentro Regional en la base de EvenRed.

Cada delegado queda en /delegados/{documento} SIN contrasena, en estado
'pendiente'. La contrasena y la palabra clave las define la persona en su
primer ingreso: nada secreto viaja por correo ni queda escrito aqui.

Decisiones aplicadas:
  - Alcance por NODO: cada delegado ve y avala solo los trabajos de su nodo.
  - Los 3 suplentes entran con los mismos permisos que el titular; se les
    quita el prefijo "Suplente:" del nombre y quedan marcados rol='suplente'
    para que se sepa quien es quien.
  - Luis David Rodriguez Castillo no tiene cedula: se precarga igual, pero
    con estado 'inactivo', porque sin documento no puede ingresar. En
    cuanto llegue su cedula se vuelve a correr el script.

REQUISITO PREVIO: publicar firebase_rules_CON_DELEGADOS.json. Sin eso la
regla $otro rechaza cualquier escritura en /delegados.

Uso:  python -X utf8 _precargar_delegados.py            (simulacion)
      python -X utf8 _precargar_delegados.py --aplicar  (escribe)
"""
import csv
import io
import json
import os
import re
import sys
import unicodedata
import urllib.request
import uuid

REG = "https://rredsi-regional26-default-rtdb.europe-west1.firebasedatabase.app"
AQUI = os.path.dirname(os.path.abspath(__file__))
FUENTE = os.path.join(AQUI, "delegados.tsv")
APLICAR = "--aplicar" in sys.argv

# El nodo se guarda con el mismo texto que usan las inscripciones.
NODOS = {
    "caldas": "Caldas",
    "valle del cauca": "Valle del Cauca",
    "risaralda": "Risaralda",
}

# Sin cedula no hay llave de ingreso: se precarga inactivo.
SIN_DOCUMENTO = "SIN-DOC-"

# Coordinacion general: ve y avala los trabajos de LOS TRES nodos, no solo
# el suyo. Se listan por documento para que quede explicito quien tiene
# alcance total y no dependa de como este escrito el nombre.
COORDINACION = {
    "1088246574",   # Jorge Luis Quintero Lopez
    "9994087",      # Frey Ricardo Jaramillo Hernandez
}


def limpiar(s):
    return re.sub(r"\s+", " ", (s or "").strip()).strip(" ,;")


def norm_nodo(s):
    base = unicodedata.normalize("NFKD", limpiar(s).lower())
    base = base.encode("ascii", "ignore").decode()
    return NODOS.get(base, limpiar(s))


def norm_doc(s):
    return re.sub(r"\D", "", s or "")


def leer_fuente():
    if not os.path.exists(FUENTE):
        print("Falta %s (nodo TAB institucion TAB nombre TAB documento TAB correo)"
              % FUENTE)
        sys.exit(1)
    with io.open(FUENTE, encoding="utf-8") as f:
        return [r for r in csv.reader(f, delimiter="\t") if r and r[0].strip()]


def construir(filas):
    regs, avisos = {}, []
    for i, r in enumerate(filas, 1):
        nodo, inst, nombre, doc, mail = (list(r) + [""] * 5)[:5]
        nombre = limpiar(nombre)

        es_suplente = nombre.lower().startswith("suplente")
        if es_suplente:
            nombre = limpiar(re.sub(r"^suplente\s*:?\s*", "", nombre,
                                    flags=re.I))

        doc = norm_doc(doc)
        estado = "pendiente"
        if not doc:
            doc = SIN_DOCUMENTO + str(i)
            estado = "inactivo"
            avisos.append("%s (%s): sin documento, queda INACTIVO"
                          % (nombre, limpiar(inst)))

        if doc in regs:
            avisos.append("documento repetido %s: %s y %s"
                          % (doc, regs[doc]["nombre"], nombre))
            continue

        if doc in COORDINACION:
            rol = "coordinacion"
        elif es_suplente:
            rol = "suplente"
        else:
            rol = "delegado"

        regs[doc] = {
            "nombre": nombre,
            "documento": doc if not doc.startswith(SIN_DOCUMENTO) else "",
            "nodo": norm_nodo(nodo),
            "institucion": limpiar(inst),
            "email": limpiar(mail).lower(),
            "rol": rol,
            "estado": estado,
        }
    return regs, avisos


def leer(path):
    """Lo que ya esta guardado en ese nodo, o None si no existe."""
    try:
        with urllib.request.urlopen("%s/%s.json" % (REG, path), timeout=60) as r:
            return json.load(r)
    except Exception:
        return None


def patch(path, payload):
    if not APLICAR:
        return "SIMULADO"
    req = urllib.request.Request(
        "%s/%s.json" % (REG, path),
        data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
        headers={"Content-Type": "application/json"},
        method="PATCH",
    )
    with urllib.request.urlopen(req, timeout=60) as r:
        return r.status


def main():
    regs, avisos = construir(leer_fuente())
    print("Delegados a precargar: %d" % len(regs))
    por_nodo = {}
    for d in regs.values():
        por_nodo[d["nodo"]] = por_nodo.get(d["nodo"], 0) + 1
    for n, c in sorted(por_nodo.items()):
        print("   %-18s %d" % (n, c))
    print("   suplentes: %d" % sum(1 for d in regs.values()
                                   if d["rol"] == "suplente"))
    coord = [d["nombre"] for d in regs.values() if d["rol"] == "coordinacion"]
    print("   coordinacion (ve los 3 nodos): %s" % (", ".join(coord) or "nadie"))
    print("Modo: %s\n" % ("APLICAR (escribe)" if APLICAR else "SIMULACION"))

    if avisos:
        print("AVISOS:")
        for a in avisos:
            print("   - %s" % a)
        print("")

    # Respaldo de lo que ya hubiera en /delegados, por si se re-ejecuta.
    ruta = os.path.join(AQUI, "_respaldo_delegados.json")
    with io.open(ruta, "w", encoding="utf-8") as f:
        f.write(json.dumps(regs, ensure_ascii=False, indent=1))
    print("Registros generados en %s\n" % ruta)

    ok, respetados, nuevos = 0, 0, 0
    indice = {}
    regs_por_id = {}
    for doc, d in sorted(regs.items()):
        envio = dict(d)
        ya = leer("delegados/%s" % doc)

        # Identificador publico del delegado. Es aleatorio, NO derivado del
        # documento: el indice que lee el panel es de lectura publica, y un
        # id derivado se podria revertir por fuerza bruta hasta descubrir la
        # cedula. Una vez asignado no cambia, para no romper asignaciones ya
        # guardadas en los trabajos.
        idpub = (ya or {}).get("idPublico") or uuid.uuid4().hex[:12]
        envio["idPublico"] = idpub
        # El dominio SI va en el indice: es institucional y ya es publico
        # (aparece en la web de cada universidad). Permite que el panel
        # muestre a quien le corresponde cada trabajo sin exponer correos.
        indice[idpub] = {
            "nombre": d["nombre"],
            "nodo": d["nodo"],
            "institucion": d["institucion"],
            "rol": d["rol"],
            "dominio": (d["email"].split("@")[-1] if "@" in d["email"] else ""),
        }
        regs_por_id[idpub] = d

        if ya and ya.get("pass"):
            # Cuenta YA ACTIVADA: no se toca su estado ni sus credenciales.
            # Mandar 'pendiente' aqui la marcaria como sin activar y le
            # reenviaria el correo de acceso. Solo se refrescan los datos
            # base: nombre, nodo, institucion, correo y rol.
            envio.pop("estado", None)
            respetados += 1
            marca = "activa, datos actualizados"
        else:
            nuevos += 1
            marca = "nuevo" if not ya else "sin activar"

        # PATCH y no PUT: nunca borra campos que no vengan en el envio.
        estado = patch("delegados/%s" % doc, envio)
        ok += 1 if estado in (200, "SIMULADO") else 0
        print("   %-12s %-30s %-16s %-9s %s"
              % (doc[:12], d["nombre"][:30], d["nodo"], estado, marca))

    print("\n%d/%d procesados  |  %d nuevos o sin activar  |  "
          "%d ya activados (cuenta respetada)"
          % (ok, len(regs), nuevos, respetados))

    # Mapa id -> correo para Apps Script. NO va a Firebase ni al HTML:
    # se pega dentro del proyecto de Apps Script, que se ejecuta en el
    # servidor. Asi el aviso de asignacion puede enviarse sin que ningun
    # correo quede expuesto en el indice publico ni en el panel.
    ruta_c = os.path.join(AQUI, "_AppsScript_correos.gs.txt")
    cab = [
        "/* Generado por _precargar_delegados.py. Pegar en Code.gs.",
        "   Mapa del id publico de cada delegado a su correo, para",
        "   avisarle cuando la coordinacion le asigne un trabajo.",
        "   Vive solo en Apps Script (servidor), nunca en el panel. */",
        "var CORREOS_DELEGADOS = {",
    ]
    filas = [
        '  "%s": {"nombre": %s, "email": %s, "nodo": %s}'
        % (i, json.dumps(v["nombre"], ensure_ascii=False),
           json.dumps(regs_por_id[i]["email"], ensure_ascii=False),
           json.dumps(v["nodo"], ensure_ascii=False))
        for i, v in sorted(indice.items(), key=lambda kv: kv[1]["nombre"])
    ]
    with io.open(ruta_c, "w", encoding="utf-8") as f:
        f.write("\n".join(cab) + "\n")
        f.write(",\n".join(filas))
        f.write("\n};\n")
    print("Mapa de correos para Apps Script: %s" % ruta_c)

    # Indice publico: lo lee el panel para armar el desplegable de
    # delegados. No lleva cedulas ni correos, solo nombre, nodo,
    # institucion y rol, identificados por el id aleatorio.
    try:
        estado = patch("delegados_indice", indice)
        print("\nIndice publico (delegados_indice): %d entradas  ->  %s"
              % (len(indice), estado))
    except Exception as err:
        print("\nIndice publico: NO se pudo escribir (%s)." % err)
        print("   Los delegados SI quedaron cargados; lo que falta es el")
        print("   indice que alimenta el desplegable de reasignacion.")
        print("   Publica de nuevo firebase_rules_CON_DELEGADOS.json: ahora")
        print("   incluye la coleccion 'delegados_indice'. Luego vuelve a")
        print("   ejecutar este script.")

    if not APLICAR:
        print("\nNada se escribio. Para aplicar:")
        print("   python -X utf8 _precargar_delegados.py --aplicar")
        print("\nRECUERDA: primero hay que publicar")
        print("   firebase_rules_CON_DELEGADOS.json")
        print("   o la regla $otro rechazara todas las escrituras.")


if __name__ == "__main__":
    main()
