#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""sync_forms_brand.py

Sincroniza el catalogo de formularios y todos los submissions de la cuenta de
Marca Principal (GbKkBpCmKu2QmloKFHy3) hacia la base de datos local
(mp_manager.sqlite). El audit_brand_sucursal_vs_form.py consume estos datos.

Por que solo Marca:
    En Monte Providencia los formularios publicos viven en la cuenta de Marca
    y todos los envios pasan por ahi, sin importar la sucursal destino. Por eso
    este script ignora sucursales por defecto. Usa --location para forzar otra.

Paginacion:
    GET /forms/submissions acepta page (default 1), limit (max 100) y
    opcionalmente startAt / endAt (formato 'YYYY-MM-DD' o ISO 8601). El cliente
    itera hasta agotar meta.nextPage, deduplicando por id.

Backfill historico:
    SIN startAt/endAt, GHL solo devuelve ~1 mes hacia atras desde la fecha
    actual. Para histórico completo hay que pedir ventanas mensuales. Este
    script automatiza eso con --backfill-months N: parte la peticion en
    ventanas mensuales (mes 1, mes 2, ...) y acumula todo en SQLite.

Uso:
    python scripts/sync_forms_brand.py                          # sync incremental (mes corriente)
    python scripts/sync_forms_brand.py --backfill-months 12     # ultimo año por ventanas mensuales
    python scripts/sync_forms_brand.py --start-at 2025-09-01    # rango explicito desde fecha
    python scripts/sync_forms_brand.py --start-at 2025-09-01 --end-at 2025-12-31
    python scripts/sync_forms_brand.py --max 200 --quiet
"""

import argparse
import os
import re
import sys
import unicodedata
from datetime import date, datetime, timedelta

ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if ROOT_DIR not in sys.path:
    sys.path.insert(0, ROOT_DIR)

import db  # noqa: E402
import sync_engine  # noqa: E402

BRAND_LOCATION_ID = "GbKkBpCmKu2QmloKFHy3"

# Field IDs historicos para el campo "Sucursal" en el formulario de Marca.
# Cuando se rediseña el form, GHL asigna un id nuevo al mismo campo conceptual.
# El primary se resuelve dinamicamente del schema del contacto; estos fallbacks
# cubren versiones viejas del form que ya no estan en el schema actual.
HISTORICAL_SUCURSAL_FIELD_IDS = {
    BRAND_LOCATION_ID: ["ZVMoW8J0MqZNh3fEZ7Uu"],
}


def norm_field_name(name):
    if not name:
        return ""
    t = unicodedata.normalize("NFKD", str(name).strip().lower())
    t = "".join(ch for ch in t if not unicodedata.combining(ch))
    return " ".join(t.split())


def resolve_sucursal_field_id(token, location_id):
    """Busca el field id del campo 'Sucursal' en el schema de contactos de la
    location. Es el mismo id que aparece como key dentro de others en los
    submissions, asi que vale para extraer el valor sin hardcodear."""
    schema = sync_engine.ghl_client.get_object_schema(token, location_id, "contact")
    for name, fid in schema.items():
        if norm_field_name(name) == "sucursal":
            return fid, name
    return None, None


def parse_date_str(value):
    """Acepta YYYY-MM-DD o ISO 8601. Devuelve string normalizado para GHL."""
    if not value:
        return None
    v = value.strip()
    if re.match(r"^\d{4}-\d{2}-\d{2}$", v):
        return v  # GHL acepta YYYY-MM-DD directo
    if re.match(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}", v):
        return v
    raise SystemExit(f"Fecha invalida {value!r}. Usa YYYY-MM-DD o ISO 8601.")


def first_of_month(d):
    return d.replace(day=1)


def add_months(d, months):
    """Suma N meses a un date (sin dependencia externa)."""
    y = d.year + (d.month - 1 + months) // 12
    m = (d.month - 1 + months) % 12 + 1
    # day=1 para evitar errores como feb 30
    return d.replace(year=y, month=m, day=1)


def build_windows(start_at, end_at, backfill_months):
    """Devuelve lista de (start_str, end_str) en orden cronologico.

    Reglas:
        - Si hay --start-at y/o --end-at explicitos, se usa esa ventana (una sola).
        - Si hay --backfill-months N, se generan N ventanas mensuales hasta hoy.
        - Si no hay nada, devuelve [(None, None)] = comportamiento default (sin filtro).
    """
    if start_at or end_at:
        return [(start_at, end_at)]
    if backfill_months and backfill_months > 0:
        today = date.today()
        # Empezamos backfill_months atras (al dia 1 de ese mes) y avanzamos
        # mes a mes hasta cubrir hoy.
        start = add_months(first_of_month(today), -backfill_months + 1)
        windows = []
        cursor = start
        for _ in range(backfill_months + 1):
            end = add_months(cursor, 1)
            # ventana semicerrada [cursor, end)
            windows.append((cursor.strftime("%Y-%m-%d"), end.strftime("%Y-%m-%d")))
            cursor = end
            if cursor > today:
                break
        return windows
    return [(None, None)]


def main():
    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    parser.add_argument("--location", default=BRAND_LOCATION_ID,
                        help=f"Location de la que sincronizar formularios. Default: Marca ({BRAND_LOCATION_ID})")
    parser.add_argument("--max", type=int, default=None,
                        help="Tope de submissions por formulario por ventana. Default: todos.")
    parser.add_argument("--page-size", type=int, default=100,
                        help="Tamano de pagina (max 100). Default 100.")
    parser.add_argument("--start-at", help="Fecha desde (YYYY-MM-DD o ISO). Filtra createdAt.")
    parser.add_argument("--end-at", help="Fecha hasta (YYYY-MM-DD o ISO). Filtra createdAt.")
    parser.add_argument("--backfill-months", type=int, default=0,
                        help="Backfill historico por N ventanas mensuales hacia atras. "
                             "Ignora si se pasa --start-at/--end-at.")
    parser.add_argument("--reextract-only", action="store_true",
                        help="No descarga nada de GHL: solo re-procesa sucursal_value en "
                             "los submissions ya guardados usando los field_ids actual + historicos.")
    parser.add_argument("--quiet", action="store_true", help="Reduce logs.")
    args = parser.parse_args()

    if hasattr(sys.stdout, "reconfigure"):
        sys.stdout.reconfigure(encoding="utf-8")

    if args.page_size < 1 or args.page_size > 100:
        raise SystemExit("--page-size debe estar entre 1 y 100")
    if args.backfill_months and args.backfill_months < 0:
        raise SystemExit("--backfill-months debe ser >= 0")

    start_at = parse_date_str(args.start_at)
    end_at = parse_date_str(args.end_at)
    windows = build_windows(start_at, end_at, args.backfill_months)

    accounts = sync_engine.parse_accounts_csv()
    account = next((a for a in accounts if a["location_id"] == args.location), None)
    if not account:
        raise SystemExit(f"Location {args.location} no esta en el CSV de tokens.")

    token = account["token"]
    location_id = account["location_id"]
    print(f"=== SYNC FORMS: {account['nombre']} ({location_id}) ===")
    print(f"  Ventanas de fecha a consultar: {len(windows)}")
    if len(windows) <= 3:
        for s, e in windows:
            print(f"    [{s or '(sin filtro)'} -> {e or '(sin filtro)'}]")

    sucursal_fid, sucursal_name = resolve_sucursal_field_id(token, location_id)
    historical_fids = HISTORICAL_SUCURSAL_FIELD_IDS.get(location_id, [])
    sucursal_field_ids = []
    if sucursal_fid:
        sucursal_field_ids.append(sucursal_fid)
        print(f"  Campo Sucursal actual: '{sucursal_name}' -> {sucursal_fid}")
    else:
        print("  WARN: no encontre el campo Sucursal actual en el schema de contactos.")
    for hfid in historical_fids:
        if hfid not in sucursal_field_ids:
            sucursal_field_ids.append(hfid)
    if historical_fids:
        print(f"  Field_ids historicos de Sucursal a tolerar: {historical_fids}")

    # Modo re-extract: no hay sync nuevo, solo re-procesar registros existentes
    if args.reextract_only:
        if not sucursal_field_ids:
            raise SystemExit("--reextract-only sin field_ids candidatos. Abortando.")
        updated, total = db.reextract_sucursal_values(location_id, sucursal_field_ids)
        print(f"\n=== RE-EXTRACT ===")
        print(f"  Submissions procesados: {total}")
        print(f"  Con sucursal poblada:   {updated}")
        print(f"  Sin sucursal extraible: {total - updated}")
        return

    print("\n[1/2] Descargando catalogo de formularios...")
    forms = sync_engine.ghl_client.get_forms(token, location_id)
    print(f"  Formularios: {len(forms)}")
    for f in forms:
        print(f"    - {f.get('id')}  {f.get('name')!r}")
    db.save_forms(location_id, forms)

    if not forms:
        print("\nNo hay formularios. Nada que sincronizar.")
        return

    print("\n[2/2] Descargando submissions...")
    total_synced = 0
    seen_ids_global = set()  # dedup cross-window
    for form in forms:
        form_id = form.get("id")
        form_name = form.get("name") or form_id

        for win_start, win_end in windows:
            window_label = f"{win_start or 'open'}->{win_end or 'open'}"

            def progress(page, page_count, accum, _form_name=form_name, _wl=window_label):
                if args.quiet:
                    return
                print(f"    {_form_name} [{_wl}]  page {page}: +{page_count} (acumulado {accum})")

            subs = sync_engine.ghl_client.get_all_form_submissions(
                token, location_id,
                form_id=form_id,
                page_size=args.page_size,
                max_submissions=args.max,
                start_at=win_start,
                end_at=win_end,
                progress_callback=progress,
            )
            # Dedup cross-window: el mismo submission puede aparecer en dos
            # ventanas si las fechas se traslapan o si la API tiene jitter.
            unique = [s for s in subs if s.get("id") and s["id"] not in seen_ids_global]
            for s in unique:
                seen_ids_global.add(s["id"])

            if not unique:
                if not args.quiet:
                    print(f"  {form_name} [{window_label}]: 0 nuevos")
                continue

            n_saved = db.upsert_form_submissions(location_id, unique, sucursal_field_id=sucursal_field_ids)
            total_synced += n_saved
            print(f"  {form_name} [{window_label}]: {n_saved} nuevos guardados (descargados {len(subs)}, unicos {len(unique)})")

    print("\n" + "=" * 60)
    db_total = db.count_form_submissions(location_id)
    print(f"DONE. Submissions en SQLite para {location_id}: {db_total} (sync de hoy aporto {total_synced})")


if __name__ == "__main__":
    main()
