#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Audita el VERDADERO origen de una lista de contactos de Marca (read-only).

Contexto: contactos de Marca con tags `facebook-ads` + `sucursal` (CANAL=FACEBOOK,
Fuente=LEAD DIGITAL) cuyo origen real es dudoso: ¿lead real de Facebook Ads o
contacto creado a mano por staff de sucursal que puso la apariencia digital?

La señal autoritativa del creador real es ``createdBy.source`` del contacto en su
SUCURSAL (no en Marca: en Marca siempre es INTEGRATION por la réplica n8n).
``createdBy`` SOLO viene en el GET individual ``GET /contacts/{id}`` — nunca en el
listado paginado ni en la caché SQLite (que solo guarda el `source` nativo).

Para cada contact id de Marca:
  1. Lee de caché SQLite los CF de Marca (id_contacto_sucursal, Sucursal, TIENDA,
     CANAL DE ORIGEN, Fuente de Prospecto), tags y source nativo.
  2. Resuelve el contacto de la sucursal:
       a) por el CF `id_contacto_sucursal` -> busca su location en SQLite,
       b) fallback: match por phone+email+name (common.match_contacts) contra la
          sucursal indicada por TIENDA (vía Verificador).
  3. GET en vivo del contacto de sucursal -> createdBy.source.
  4. Veredicto:
       REAL_DIGITAL    -> createdBy.source == INTEGRATION (lead real digital).
       MANUAL_SUCURSAL -> createdBy.source in {WEB_USER, MOBILE_USER} (creado a mano:
                          el tag facebook-ads/formulario es falsa atribución).
       UNKNOWN         -> no se pudo resolver la sucursal o el GET falló.

NO escribe nada en el CRM. Emite un reporte CSV + JSON a generated/reports/origin_audit/.
"""

import argparse
import csv
import json
import os
import sqlite3
import sys
from collections import Counter
from datetime import datetime

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 common  # noqa: E402
from common import BRAND_LOCATION_ID, DB_PATH, REPORTS_DIR, SchemaResolver  # noqa: E402
from tag_canal_origen_workflow import ghl_request  # noqa: E402
from fill_sucursal_tienda_from_location import load_verifier_map  # noqa: E402

# createdBy.source: orígenes "creado por un usuario" (humano en la sucursal).
USER_SOURCES = {"WEB_USER", "MOBILE_USER"}
INTEGRATION_SOURCE = "INTEGRATION"

# Alias del CF que enlaza Marca -> contacto de sucursal.
ID_CONTACTO_SUCURSAL_NAMES = ["ID Contacto Sucursal", "Id Contacto Sucursal"]

DEFAULT_CSV = r"d:\Export_Contacts_leads_May_2026_11_20_PM (1).csv"
OUT_DIR = os.path.join(REPORTS_DIR, "origin_audit")


# --------------------------------------------------------------------------- #
# Acceso a caché SQLite (read-only)
# --------------------------------------------------------------------------- #
def _connect():
    conn = sqlite3.connect(f"file:{DB_PATH}?mode=ro", uri=True)
    conn.row_factory = sqlite3.Row
    return conn


def get_cached_contact(conn, location_id, contact_id):
    row = conn.execute(
        "SELECT id, location_id, first_name, last_name, email, phone, tags, "
        "custom_fields_json, source FROM contacts WHERE id=? AND location_id=?",
        (contact_id, location_id),
    ).fetchone()
    return dict(row) if row else None


def find_locations_for_contact_id(conn, contact_id):
    """Toda location (excluyendo Marca) donde existe un contacto con ese id."""
    rows = conn.execute(
        "SELECT DISTINCT location_id FROM contacts WHERE id=? AND location_id!=?",
        (contact_id, BRAND_LOCATION_ID),
    ).fetchall()
    return [r["location_id"] for r in rows]


def get_branch_contacts(conn, location_id):
    rows = conn.execute(
        "SELECT id, first_name, last_name, email, phone FROM contacts WHERE location_id=?",
        (location_id,),
    ).fetchall()
    return [dict(r) for r in rows]


# --------------------------------------------------------------------------- #
# Helpers de schema / custom fields
# --------------------------------------------------------------------------- #
def resolve_marca_field_ids(resolver, marca_token):
    """Resuelve los field IDs de Marca por nombre (NO hardcodear: varían por loc)."""
    def first(alias_or_name):
        ids = resolver.get_field_ids(marca_token, BRAND_LOCATION_ID, "contact", alias_or_name)
        return ids[0] if ids else None

    field_ids = {
        "canal": first("canal_origen"),
        "fuente": first("fuente_prospecto"),
        "tienda": first("tienda"),
        "sucursal": first("sucursal"),
        "id_contacto_sucursal": None,
    }
    for name in ID_CONTACTO_SUCURSAL_NAMES:
        fid = first(name)
        if fid:
            field_ids["id_contacto_sucursal"] = fid
            break
    return field_ids


def cf_value(cached_contact, field_id):
    """Lee un CF del contacto cacheado (custom_fields_json) por field_id."""
    if not field_id or not cached_contact:
        return None
    return common.get_custom_field_value(cached_contact.get("custom_fields_json"), field_id)


# --------------------------------------------------------------------------- #
# GET en vivo del contacto de sucursal -> createdBy.source
# --------------------------------------------------------------------------- #
def get_created_by_source(contact_id, token):
    """Devuelve (created_by_source, native_source, tags, error)."""
    try:
        data = ghl_request("GET", f"/contacts/{contact_id}", token)
    except Exception as exc:  # noqa: BLE001
        return None, None, None, f"{type(exc).__name__}: {exc}"
    contact = data.get("contact") if isinstance(data.get("contact"), dict) else data
    created_by = (contact.get("createdBy") or {}).get("source")
    native_source = contact.get("source")
    tags = contact.get("tags") or []
    return created_by, native_source, tags, None


# --------------------------------------------------------------------------- #
# Resolución del contacto de sucursal
# --------------------------------------------------------------------------- #
def build_tienda_to_location(verifier_map):
    """Invierte el Verificador: normalize_text(tienda|sucursal) -> location_id."""
    index = {}
    for loc_id, entry in (verifier_map or {}).items():
        for key in ("tienda", "sucursal", "sc_name"):
            val = (entry.get(key) or "").strip()
            if val:
                index.setdefault(common.normalize_text(val), loc_id)
    return index


def resolve_branch_contact(conn, marca_cached, field_ids, accounts_by_id,
                           tienda_index):
    """Devuelve (branch_location_id, branch_contact_id, how) o (None, None, reason)."""
    # (a) por CF id_contacto_sucursal
    branch_cid = cf_value(marca_cached, field_ids.get("id_contacto_sucursal"))
    branch_cid = str(branch_cid).strip() if branch_cid else ""
    if branch_cid:
        locs = find_locations_for_contact_id(conn, branch_cid)
        if len(locs) == 1:
            return locs[0], branch_cid, "cf_id+sqlite"
        if len(locs) > 1:
            # Desambiguar por TIENDA si hay varias coincidencias.
            tienda_loc = _location_from_tienda(marca_cached, field_ids, tienda_index)
            if tienda_loc in locs:
                return tienda_loc, branch_cid, "cf_id+tienda"
            return locs[0], branch_cid, "cf_id+sqlite(multi)"
        # El id existe pero no está en caché de ninguna sucursal: intentamos GET
        # directo si podemos ubicar la sucursal por TIENDA.
        tienda_loc = _location_from_tienda(marca_cached, field_ids, tienda_index)
        if tienda_loc and tienda_loc in accounts_by_id:
            return tienda_loc, branch_cid, "cf_id+tienda(uncached)"

    # (b) fallback: match por phone+email+name contra la sucursal del TIENDA
    tienda_loc = _location_from_tienda(marca_cached, field_ids, tienda_index)
    if tienda_loc:
        cand = _match_in_branch(conn, marca_cached, tienda_loc)
        if cand:
            return tienda_loc, cand, "match_contacts"
        return None, None, f"no_match_in_branch:{tienda_loc}"
    return None, None, "no_branch_resolved"


def _location_from_tienda(marca_cached, field_ids, tienda_index):
    tienda = cf_value(marca_cached, field_ids.get("tienda"))
    sucursal = cf_value(marca_cached, field_ids.get("sucursal"))
    for raw in (tienda, sucursal):
        if raw:
            loc = tienda_index.get(common.normalize_text(raw))
            if loc:
                return loc
    return None


def _match_in_branch(conn, marca_cached, branch_loc):
    best = None
    best_score = 0.0
    for cand in get_branch_contacts(conn, branch_loc):
        res = common.match_contacts(marca_cached, cand)
        if res["level"] in ("strong", "medium") and res["score"] > best_score:
            best = cand["id"]
            best_score = res["score"]
    return best


# --------------------------------------------------------------------------- #
# Lectura de la lista de entrada
# --------------------------------------------------------------------------- #
def read_contact_ids(args):
    if args.ids:
        return [i.strip() for i in args.ids.split(",") if i.strip()]
    path = args.csv or DEFAULT_CSV
    if not os.path.exists(path):
        raise SystemExit(f"No existe el CSV de entrada: {path} (usa --csv o --ids)")
    ids = []
    with open(path, "r", encoding="utf-8-sig", newline="") as fh:
        reader = csv.DictReader(fh)
        col = next((c for c in reader.fieldnames or [] if c.strip().lower() in
                    ("contact id", "contactid", "id")), None)
        if not col:
            raise SystemExit(f"El CSV no tiene columna 'Contact Id': {reader.fieldnames}")
        for row in reader:
            cid = (row.get(col) or "").strip()
            if cid:
                ids.append(cid)
    return ids


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

    parser = argparse.ArgumentParser(
        description="Audita el verdadero origen (createdBy.source en sucursal) de "
                    "una lista de contactos de Marca. READ-ONLY."
    )
    parser.add_argument("--csv", help=f"CSV de entrada (col 'Contact Id'). Default: {DEFAULT_CSV}")
    parser.add_argument("--ids", help="Lista de contact ids de Marca separados por coma (alternativa a --csv)")
    parser.add_argument("--limit", type=int, help="Limita el número de contactos (debug)")
    args = parser.parse_args()

    contact_ids = read_contact_ids(args)
    if args.limit:
        contact_ids = contact_ids[:args.limit]

    accounts = common.load_accounts(include_main=True)
    accounts_by_id = {a["location_id"]: a for a in accounts}
    marca = accounts_by_id.get(BRAND_LOCATION_ID)
    if not marca:
        raise SystemExit("No se encontró la cuenta de Marca en el CSV de cuentas.")
    marca_token = marca["token"]

    resolver = SchemaResolver()
    field_ids = resolve_marca_field_ids(resolver, marca_token)
    verifier_map = load_verifier_map()
    tienda_index = build_tienda_to_location(verifier_map)

    print("=" * 74)
    print("AUDITORÍA DE ORIGEN REAL (createdBy.source en sucursal) — READ-ONLY")
    print("=" * 74)
    print(f"Contactos a auditar: {len(contact_ids)}")
    print(f"Field IDs Marca resueltos: {field_ids}")
    print(f"Sucursales en índice TIENDA: {len(tienda_index)}\n")

    rows = []
    verdict_counter = Counter()
    branch_counter = Counter()

    with _connect() as conn:
        for n, cid in enumerate(contact_ids, 1):
            marca_cached = get_cached_contact(conn, BRAND_LOCATION_ID, cid)
            rec = {
                "marca_id": cid,
                "nombre": "",
                "tienda": "",
                "branch_location_id": "",
                "branch_contact_id": "",
                "resolved_via": "",
                "created_by_source": "",
                "native_source_sucursal": "",
                "tiene_cf_marca": bool(marca_cached),
                "tags_marca": "",
                "canal_marca": "",
                "fuente_marca": "",
                "verdict": "",
                "note": "",
            }

            if not marca_cached:
                rec["verdict"] = "UNKNOWN"
                rec["note"] = "contacto no está en caché de Marca (re-sincroniza Marca)"
                verdict_counter["UNKNOWN"] += 1
                rows.append(rec)
                print(f"[{n}/{len(contact_ids)}] {cid} -> UNKNOWN (no en caché Marca)")
                continue

            rec["nombre"] = f"{marca_cached.get('first_name') or ''} {marca_cached.get('last_name') or ''}".strip()
            rec["tienda"] = cf_value(marca_cached, field_ids.get("tienda")) or ""
            rec["canal_marca"] = cf_value(marca_cached, field_ids.get("canal")) or ""
            rec["fuente_marca"] = cf_value(marca_cached, field_ids.get("fuente")) or ""
            try:
                rec["tags_marca"] = ", ".join(json.loads(marca_cached.get("tags") or "[]"))
            except Exception:  # noqa: BLE001
                rec["tags_marca"] = marca_cached.get("tags") or ""

            branch_loc, branch_cid, how = resolve_branch_contact(
                conn, marca_cached, field_ids, accounts_by_id, tienda_index)
            rec["resolved_via"] = how

            if not branch_loc or not branch_cid:
                rec["verdict"] = "UNKNOWN"
                rec["note"] = f"no se resolvió contacto de sucursal ({how})"
                verdict_counter["UNKNOWN"] += 1
                rows.append(rec)
                print(f"[{n}/{len(contact_ids)}] {rec['nombre']} -> UNKNOWN ({how})")
                continue

            rec["branch_location_id"] = branch_loc
            rec["branch_contact_id"] = branch_cid
            branch_acc = accounts_by_id.get(branch_loc)
            branch_name = (branch_acc or {}).get("nombre", branch_loc)

            if not branch_acc:
                rec["verdict"] = "UNKNOWN"
                rec["note"] = f"sucursal {branch_loc} sin token en CSV de cuentas"
                verdict_counter["UNKNOWN"] += 1
                rows.append(rec)
                print(f"[{n}/{len(contact_ids)}] {rec['nombre']} -> UNKNOWN (sin token {branch_loc})")
                continue

            created_by, native_source, _tags, err = get_created_by_source(
                branch_cid, branch_acc["token"])
            rec["created_by_source"] = created_by or ""
            rec["native_source_sucursal"] = native_source or ""

            if err:
                rec["verdict"] = "UNKNOWN"
                rec["note"] = f"GET sucursal falló: {err}"
                verdict_counter["UNKNOWN"] += 1
            elif created_by in USER_SOURCES:
                rec["verdict"] = "MANUAL_SUCURSAL"
                verdict_counter["MANUAL_SUCURSAL"] += 1
                branch_counter[branch_name] += 1
            elif created_by == INTEGRATION_SOURCE:
                rec["verdict"] = "REAL_DIGITAL"
                verdict_counter["REAL_DIGITAL"] += 1
            else:
                rec["verdict"] = "UNKNOWN"
                rec["note"] = f"createdBy.source inesperado: {created_by!r}"
                verdict_counter["UNKNOWN"] += 1

            rows.append(rec)
            print(f"[{n}/{len(contact_ids)}] {rec['nombre']:<28} | {branch_name:<26} | "
                  f"createdBy={created_by or '-':<12} | {rec['verdict']}")

    # --- Reportes ---
    os.makedirs(OUT_DIR, exist_ok=True)
    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    csv_path = os.path.join(OUT_DIR, f"origin_audit_{stamp}.csv")
    json_path = os.path.join(OUT_DIR, f"origin_audit_{stamp}.json")

    fieldnames = list(rows[0].keys()) if rows else []
    with open(csv_path, "w", encoding="utf-8-sig", newline="") as fh:
        writer = csv.DictWriter(fh, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)

    manual_ids = [r["marca_id"] for r in rows if r["verdict"] == "MANUAL_SUCURSAL"]
    summary = {
        "generated_at": datetime.now().isoformat(),
        "total": len(rows),
        "by_verdict": dict(verdict_counter),
        "manual_by_branch": dict(branch_counter),
        "manual_sucursal_marca_ids": manual_ids,
        "manual_sucursal_branch_pairs": [
            {"marca_id": r["marca_id"], "branch_location_id": r["branch_location_id"],
             "branch_contact_id": r["branch_contact_id"], "nombre": r["nombre"]}
            for r in rows if r["verdict"] == "MANUAL_SUCURSAL"
        ],
    }
    with open(json_path, "w", encoding="utf-8") as fh:
        json.dump(summary, fh, ensure_ascii=False, indent=2)

    print("\n" + "=" * 74)
    print("RESUMEN")
    print("=" * 74)
    for verdict, cnt in verdict_counter.most_common():
        print(f"  {verdict:<16}: {cnt}")
    if branch_counter:
        print("\n  MANUAL_SUCURSAL por sucursal:")
        for branch, cnt in branch_counter.most_common():
            print(f"    {branch}: {cnt}")
    print(f"\n  Reporte CSV : {csv_path}")
    print(f"  Resumen JSON: {json_path}")
    print(f"  MANUAL_SUCURSAL (falsa atribución digital): {len(manual_ids)}")


if __name__ == "__main__":
    main()
