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

Corrige los campos 'Sucursal' y 'TIENDA' del contacto en la cuenta de Marca
cuando el formulario original (form_submissions) apunta claramente a otra
sucursal y existe un duplicado fisico del contacto en esa sucursal correcta.

Es el paso 1 del flujo de remediacion para los casos de DISCREPANCIA detectados
por audit_brand_sucursal_vs_form.py. NO mueve oportunidades, NO elimina
contactos: solo realinea la etiqueta en Marca para que coincida con lo que el
cliente dijo en el formulario y donde fisicamente existe.

Condiciones que tiene que cumplir un contacto para entrar al plan:
    1. Tiene submission de formulario con sucursal_value extraida.
    2. La similitud entre form_sucursal y la Sucursal actual de Marca es
       menor al umbral de verify (default 0.30 = DISCREPANCIA dura).
    3. Existe duplicado del mismo contacto (por telefono normalizado) en la
       sucursal cuyo nombre matchea el form_sucursal (similitud >= 0.60).
    4. El verificador de sucursales tiene TIENDA y SUCURSAL para esa location.

Dry-run por default. Usa --apply para escribir cambios en GHL. Registra cada
cambio en script_audit cuando se pasa --run-id (auditoria reversible desde el
dashboard).

Uso:
    # Plan (dry-run) para todos los casos de DISCREPANCIA:
    python scripts/fix_brand_sucursal_from_form.py

    # Solo casos donde el form pidio Queretaro:
    python scripts/fix_brand_sucursal_from_form.py --filter-form-sucursal queretaro

    # Solo un contacto especifico:
    python scripts/fix_brand_sucursal_from_form.py --only-cid ksIuffpYapCRa1v5lj4t

    # Aplicar:
    python scripts/fix_brand_sucursal_from_form.py --apply --yes
"""

import argparse
import csv
import json
import os
import re
import sys
import unicodedata

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)
SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
if SCRIPTS_DIR not in sys.path:
    sys.path.insert(0, SCRIPTS_DIR)

import db  # noqa: E402
import script_audit  # noqa: E402
import sync_engine  # noqa: E402
from audit_brand_sucursal_vs_form import (  # noqa: E402
    similarity, get_contact_sucursal, resolve_sucursal_field_id_from_db,
    latest_submission_per_contact, strip_accents,
)

BRAND_LOCATION_ID = "GbKkBpCmKu2QmloKFHy3"
VERIFIER_CSV = os.path.join(ROOT_DIR, "Monte Providencia - Verificador de sucursales y correos - Sucursales.csv")
SCRIPT_NAME = os.path.basename(__file__)

# Umbral para considerar que el form_sucursal "matchea" un nombre de sucursal.
TARGET_LOCATION_MATCH_THRESHOLD = 0.60


def norm_phone(p):
    digits = re.sub(r"\D+", "", str(p or ""))
    return digits[-10:] if len(digits) >= 10 else digits


def norm_email(e):
    return (e or "").strip().lower()


def load_verifier():
    """{location_id: {tienda, sucursal_label}}"""
    mapping = {}
    if not os.path.exists(VERIFIER_CSV):
        return mapping
    with open(VERIFIER_CSV, encoding="utf-8-sig", newline="") as fh:
        for row in csv.DictReader(fh):
            loc = (row.get("ID LOCATION BUCEFALO") or "").strip()
            if not loc:
                continue
            tienda = (row.get("TIENDA") or "").strip()
            sucursal = (row.get("SUCURSAL") or "").strip()
            mapping[loc] = {
                "tienda": tienda.upper() if tienda else None,
                "sucursal": sucursal if sucursal and sucursal != "-" else None,
            }
    return mapping


def resolve_tienda_field_id(brand_location_id):
    """Lee object_schemas para encontrar el field id de 'TIENDA' en Marca."""
    conn = db.get_db_connection()
    try:
        rows = conn.execute(
            "SELECT field_id, field_name FROM object_schemas WHERE location_id=? AND object_key='contact'",
            (brand_location_id,)
        ).fetchall()
        for r in rows:
            if strip_accents(r["field_name"]).lower().strip() == "tienda":
                return r["field_id"]
        return None
    finally:
        conn.close()


def resolve_target_location(form_value, accounts, verifier):
    """Devuelve (location_id, info_verifier, similitud) para la sucursal que mejor
    matchea el form_value. Si la similitud es menor al umbral, devuelve None."""
    best_score = 0.0
    best_lid = None
    best_info = None
    for acc in accounts:
        lid = acc["location_id"]
        if lid == BRAND_LOCATION_ID:
            continue
        info = verifier.get(lid)
        if not info:
            # fallback al nombre de la cuenta (ej. '85975 - MP - Queretaro')
            name = acc.get("nombre") or ""
            parts = name.split(" - ")
            tag = parts[2].strip() if len(parts) >= 3 else name
            info = {"tienda": tag.upper(), "sucursal": tag}
        # comparar contra tanto tienda como sucursal y quedarnos con el max
        for candidate in (info.get("tienda"), info.get("sucursal")):
            if not candidate:
                continue
            s = similarity(form_value, candidate)
            if s > best_score:
                best_score = s
                best_lid = lid
                best_info = info
    if best_score >= TARGET_LOCATION_MATCH_THRESHOLD:
        return best_lid, best_info, best_score
    return None, None, best_score


def build_branch_lookups(conn):
    """Devuelve (phone_idx, email_idx) sobre todas las sucursales (no Marca).
    Cada idx es {valor_normalizado: [(contact_id, location_id), ...]}. Permite
    validar duplicado fisico cuando el cliente uso phone diferente pero mismo
    email en distintas sucursales (caso comun de clientes que reintentan)."""
    phone_idx, email_idx = {}, {}
    for r in conn.execute(
        "SELECT id, location_id, phone, email FROM contacts WHERE location_id != ?",
        (BRAND_LOCATION_ID,)
    ):
        p = norm_phone(r["phone"])
        e = norm_email(r["email"])
        if p:
            phone_idx.setdefault(p, []).append((r["id"], r["location_id"]))
        if e:
            email_idx.setdefault(e, []).append((r["id"], r["location_id"]))
    return phone_idx, email_idx


def parse_args():
    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
    parser.add_argument("--apply", action="store_true",
                        help="Aplica los PATCH reales en GHL. Sin esto solo simula (default).")
    parser.add_argument("--verify-threshold", type=float, default=0.30,
                        help="Limite superior de similitud para considerar DISCREPANCIA. Default 0.30.")
    parser.add_argument("--filter-form-sucursal",
                        help="Procesa solo casos donde el form_sucursal contenga este texto (sin acentos, case-insensitive).")
    parser.add_argument("--only-cid", action="append", default=[],
                        help="Procesa solo estos contact_ids (puedes repetir el flag).")
    parser.add_argument("--require-duplicate", action="store_true", default=True,
                        help="Solo planifica si existe duplicado en la sucursal correcta (default ON).")
    parser.add_argument("--no-require-duplicate", dest="require_duplicate", action="store_false",
                        help="Permite re-etiquetar aunque no haya duplicado fisico (peligroso).")
    parser.add_argument("--run-id", help="Audit run ID. Permite revertir desde el dashboard.")
    parser.add_argument("--yes", action="store_true",
                        help="Salta la confirmacion interactiva en --apply.")
    return parser.parse_args()


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

    # 1. Cargar datos
    accounts = sync_engine.parse_accounts_csv()
    brand_account = next((a for a in accounts if a["location_id"] == BRAND_LOCATION_ID), None)
    if not brand_account:
        raise SystemExit("No se encontro la cuenta de Marca en el CSV de tokens.")
    brand_token = brand_account["token"]

    verifier = load_verifier()
    if not verifier:
        print("WARN: verificador de sucursales no disponible; usare fallback al nombre de la cuenta.")

    suc_fid = resolve_sucursal_field_id_from_db(BRAND_LOCATION_ID)
    if not suc_fid:
        raise SystemExit("No encontre el field 'Sucursal' en object_schemas para Marca. Corre el sync global primero.")
    tienda_fid = resolve_tienda_field_id(BRAND_LOCATION_ID)
    if not tienda_fid:
        print("WARN: no encontre el field 'TIENDA' en Marca; solo voy a actualizar 'Sucursal'.")

    submissions = db.get_form_submissions(location_id=BRAND_LOCATION_ID, with_sucursal_only=True)
    if not submissions:
        raise SystemExit("No hay form_submissions con sucursal. Corre primero scripts/sync_forms_brand.py")
    latest_by_contact = latest_submission_per_contact(submissions)

    conn = db.get_db_connection()
    contacts = {r["id"]: dict(r) for r in conn.execute(
        "SELECT * FROM contacts WHERE location_id=?", (BRAND_LOCATION_ID,)
    ).fetchall()}
    phone_to_branches, email_to_branches = build_branch_lookups(conn)
    accounts_by_lid = {a["location_id"]: a for a in accounts}
    conn.close()

    filter_norm = strip_accents(args.filter_form_sucursal).lower() if args.filter_form_sucursal else None
    only_cids = set(args.only_cid) if args.only_cid else None

    # 2. Construir plan
    plans = []
    skipped_no_target = []
    skipped_no_duplicate = []

    for cid, sub in latest_by_contact.items():
        if only_cids and cid not in only_cids:
            continue
        contact = contacts.get(cid)
        if not contact:
            continue
        marca_val = get_contact_sucursal(contact, suc_fid)
        if not marca_val:
            continue
        form_val = sub.get("sucursal_value") or ""
        if not form_val:
            continue
        sim = similarity(form_val, marca_val)
        if sim >= args.verify_threshold:
            continue  # no es DISCREPANCIA, no tocamos
        if filter_norm and filter_norm not in strip_accents(form_val).lower():
            continue

        target_lid, target_info, target_sim = resolve_target_location(form_val, accounts, verifier)
        target_name = accounts_by_lid.get(target_lid, {}).get("nombre") if target_lid else None

        base_row = {
            "cid": cid,
            "name": ((contact.get("first_name") or "") + " " + (contact.get("last_name") or "")).strip(),
            "phone": contact.get("phone"),
            "form_sucursal": form_val,
            "marca_sucursal": marca_val,
            "similarity_marca_form": round(sim, 3),
            "target_location_id": target_lid,
            "target_location_name": target_name,
            "target_sim": round(target_sim, 3),
            "target_tienda": (target_info or {}).get("tienda"),
            "target_sucursal_label": (target_info or {}).get("sucursal"),
        }

        if not target_lid:
            skipped_no_target.append(base_row)
            continue

        # Validar duplicado fisico en la sucursal correcta. Cruzamos phone Y
        # email porque algunos clientes reintentan con otro numero pero el
        # mismo correo (caso tipico de jorge arroyo: phone distinto en Qro,
        # email identico).
        phone = norm_phone(contact.get("phone"))
        email = norm_email(contact.get("email"))
        dup_by_phone = any(loc == target_lid for (_, loc) in phone_to_branches.get(phone, []))
        dup_by_email = any(loc == target_lid for (_, loc) in email_to_branches.get(email, []))
        has_duplicate = dup_by_phone or dup_by_email
        match_method = (
            "phone+email" if dup_by_phone and dup_by_email
            else "phone" if dup_by_phone
            else "email" if dup_by_email
            else None
        )
        if args.require_duplicate and not has_duplicate:
            base_row["reason"] = f"no existe contacto duplicado en {target_name}"
            skipped_no_duplicate.append(base_row)
            continue

        base_row["has_duplicate_in_target"] = has_duplicate
        base_row["match_method"] = match_method
        plans.append(base_row)

    # 3. Reporte
    print("=" * 78)
    print("FIX BRAND SUCURSAL FROM FORM")
    print("=" * 78)
    print(f"Modo: {'APPLY (PATCH real en GHL)' if args.apply else 'DRY-RUN (sin cambios)'}")
    print(f"Verify threshold: {args.verify_threshold}  (similitud < threshold = DISCREPANCIA)")
    if filter_norm:
        print(f"Filtro form_sucursal: {args.filter_form_sucursal!r}")
    if only_cids:
        print(f"Solo contact_ids: {sorted(only_cids)}")
    print(f"\nA corregir:                       {len(plans)}")
    print(f"Saltados (sin target claro):      {len(skipped_no_target)}")
    print(f"Saltados (sin duplicado fisico):  {len(skipped_no_duplicate)}")

    if plans:
        print("\n" + "-" * 78)
        print("PLAN DE CORRECCIONES")
        print("-" * 78)
        for p in plans:
            new_tienda = p["target_tienda"] or "(sin TIENDA en verificador)"
            new_suc = p["target_sucursal_label"] or "(sin SUCURSAL en verificador)"
            print(f"  {p['name']}  (cid={p['cid']})")
            print(f"    Form dijo:    {p['form_sucursal']!r}")
            print(f"    Marca actual: Sucursal={p['marca_sucursal']!r}")
            print(f"    -> Cambiar a: Sucursal={new_suc!r}  TIENDA={new_tienda!r}")
            print(f"    Target sucursal: {p['target_location_name']}  (sim={p['target_sim']:.2f}, duplicado={p['has_duplicate_in_target']} via {p.get('match_method')})")

    if skipped_no_duplicate:
        print("\n" + "-" * 78)
        print("SALTADOS POR FALTA DE DUPLICADO FISICO (revisar manualmente)")
        print("-" * 78)
        for p in skipped_no_duplicate:
            print(f"  {p['name']}: form pedia {p['form_sucursal']!r} -> target {p['target_location_name']}, "
                  f"pero el contacto no existe ahi. {p.get('reason','')}")

    if skipped_no_target:
        print("\n" + "-" * 78)
        print("SALTADOS POR FALTA DE TARGET CLARO (form value no matchea ninguna sucursal)")
        print("-" * 78)
        for p in skipped_no_target:
            print(f"  {p['name']}: form_sucursal={p['form_sucursal']!r}  best_sim={p['target_sim']:.2f}")

    if not plans:
        print("\nNada que aplicar. Termino.")
        return

    # 4. Aplicar si --apply
    if not args.apply:
        print("\n" + "=" * 78)
        print("DRY-RUN: no se escribio nada en GHL. Usa --apply --yes para aplicar.")
        return

    if not args.yes:
        if sys.stdin.isatty():
            print(f"\nVas a actualizar Sucursal/TIENDA en {len(plans)} contacto(s) de la cuenta de Marca.")
            response = input("Escribe CONFIRMO para continuar: ").strip()
            if response != "CONFIRMO":
                raise SystemExit("Cancelado por el usuario.")
        else:
            raise SystemExit("--apply requiere --yes en entornos no TTY (dashboard). Abortando por seguridad.")

    if args.run_id:
        script_audit.create_run(
            args.run_id,
            SCRIPT_NAME,
            arguments=" ".join(sys.argv[1:]),
            locations=[BRAND_LOCATION_ID],
            execution_mode="sequential",
        )

    applied = 0
    failed = 0
    for p in plans:
        if args.run_id and not script_audit.wait_if_paused_or_stopped(args.run_id):
            break
        cid = p["cid"]
        custom_fields = []
        change_records = []  # (change_id, field_id, field_name, old_value, new_value)

        if p["target_sucursal_label"]:
            custom_fields.append({"id": suc_fid, "value": p["target_sucursal_label"]})
            change_records.append((suc_fid, "Sucursal", p["marca_sucursal"], p["target_sucursal_label"]))
        if tienda_fid and p["target_tienda"]:
            # leer valor actual de TIENDA del contacto en Marca
            try:
                cfs = json.loads(contacts[cid].get("custom_fields_json") or "[]")
            except Exception:
                cfs = []
            current_tienda = None
            for f in cfs:
                if isinstance(f, dict) and (f.get("id") or f.get("fieldId")) == tienda_fid:
                    current_tienda = f.get("value")
                    break
            custom_fields.append({"id": tienda_fid, "value": p["target_tienda"]})
            change_records.append((tienda_fid, "TIENDA", current_tienda, p["target_tienda"]))

        if not custom_fields:
            continue

        change_ids = []
        if args.run_id:
            for (fid, fname, old, new) in change_records:
                change_id = script_audit.record_change(
                    args.run_id, BRAND_LOCATION_ID, "contact", cid, fid, fname, old, new
                )
                if change_id is not None:
                    change_ids.append(change_id)

        try:
            sync_engine.ghl_client.update_contact(brand_token, cid, {"customFields": custom_fields})
            applied += 1
            print(f"  OK  {p['name']}  -> Sucursal={p['target_sucursal_label']!r}  TIENDA={p['target_tienda']!r}")
            for change_id in change_ids:
                script_audit.mark_change(change_id, "applied")
        except Exception as exc:
            failed += 1
            print(f"  ERR {p['name']}: {exc}")
            for change_id in change_ids:
                script_audit.mark_change(change_id, "failed", str(exc))

    print("\n" + "=" * 78)
    print(f"APPLIED: {applied}   FAILED: {failed}")
    if args.run_id:
        script_audit.update_run_status(args.run_id, "failed" if failed else "success")
    if failed:
        raise SystemExit(1)


if __name__ == "__main__":
    main()
