#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script para escanear contactos de una sucursal en GHL y buscarles oportunidades.
Si un contacto tiene cero (0) oportunidades, se le creará una oportunidad con su nombre completo
en la última etapa del único pipeline, que debe llamarse 'En pausa'.
Solo aplica cuando la sucursal tiene exactamente un solo pipeline.
"""

import argparse
import os
import sys
import json
from concurrent.futures import ThreadPoolExecutor, as_completed

# Asegurar importación de módulos en la raíz del proyecto
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 sync_engine  # noqa: E402
import script_audit  # noqa: E402
from scripts import common as scripts_common  # noqa: E402

# Campos del contacto que se copian a la oportunidad cuando se crea desde el
# dashboard. Las claves son los alias definidos en scripts/common.FIELD_ALIASES.
SINGLE_CONTACT_REQUIRED_FIELDS = (
    "sucursal",
    "tienda",
    "fuente_prospecto",
    "canal_origen",
)
# El vehículo se mapea sólo si el contacto lo tiene; no aborta si está vacío
# (lo trata como dato comercial opcional, no como campo de ruteo).
SINGLE_CONTACT_OPTIONAL_FIELDS = (
    "vehiculo",
    "marca_vehiculo",
    "version_vehiculo",
    "ano_vehiculo",
)


def normalize_string(value):
    return " ".join(str(value or "").strip().lower().split())


def get_stage_order(stage, idx):
    order = stage.get("order")
    if order is None:
        order = stage.get("position")
    try:
        return int(order)
    except (TypeError, ValueError):
        return idx


def _validate_target_pipeline(token, location_id):
    """Devuelve (pipeline_id, last_stage_id, last_stage_name) si la sucursal
    cumple la configuración requerida ('Standar', 8 etapas, última 'En Pausa').
    Si no cumple, levanta ValueError con un mensaje útil para el dashboard.
    """
    pipelines = sync_engine.ghl_client.get_pipelines(token, location_id)
    if not pipelines:
        raise ValueError("La sucursal no tiene pipelines en GHL.")
    if len(pipelines) != 1:
        raise ValueError(
            f"La sucursal tiene {len(pipelines)} pipelines; este flujo sólo aplica con 1."
        )

    pipeline = pipelines[0]
    raw_stages = pipeline.get("stages") or []
    if not raw_stages:
        raise ValueError("El pipeline detectado no tiene etapas.")

    sorted_stages = sorted(raw_stages, key=lambda s: get_stage_order(s, raw_stages.index(s)))
    last_stage = sorted_stages[-1]
    pipeline_name = pipeline.get("name") or ""
    last_stage_name = last_stage.get("name") or ""

    is_standar = normalize_string(pipeline_name) in ("standar", "standard")
    is_last_en_pausa = normalize_string(last_stage_name) == "en pausa"
    if not (is_standar and len(sorted_stages) == 8 and is_last_en_pausa):
        raise ValueError(
            "Pipeline no cumple validación 'Standar' / 8 etapas / última 'En Pausa' "
            f"(detectado: '{pipeline_name}', {len(sorted_stages)} etapas, "
            f"última '{last_stage_name}')."
        )

    return pipeline.get("id"), last_stage.get("id"), last_stage_name


def _build_custom_fields_payload(token, location_id, contact, strict):
    """Resuelve los IDs de los custom fields de oportunidad y arma el payload
    leyendo los valores del contacto. Devuelve (payload, mapped_summary).
    Si strict y falta alguno de SINGLE_CONTACT_REQUIRED_FIELDS, levanta ValueError.
    """
    resolver = scripts_common.SchemaResolver()

    contact_custom_fields = contact.get("customFields") or contact.get("custom_fields") or []

    payload = []
    mapped = {}
    missing = []

    all_aliases = list(SINGLE_CONTACT_REQUIRED_FIELDS) + list(SINGLE_CONTACT_OPTIONAL_FIELDS)
    for alias in all_aliases:
        opp_field_id = resolver.get_field_id(token, location_id, "opportunity", alias)
        contact_field_id = resolver.get_field_id(token, location_id, "contact", alias)
        contact_value = scripts_common.get_custom_field_value(
            {"customFields": contact_custom_fields}, contact_field_id
        ) if contact_field_id else None
        contact_value = str(contact_value).strip() if contact_value is not None else ""

        if alias in SINGLE_CONTACT_REQUIRED_FIELDS:
            if not opp_field_id:
                missing.append(f"{alias} (no existe en schema de oportunidad)")
                continue
            if not contact_field_id:
                missing.append(f"{alias} (no existe en schema de contacto)")
                continue
            if not contact_value:
                missing.append(f"{alias} (contacto sin valor)")
                continue
            payload.append({"id": opp_field_id, "value": contact_value})
            mapped[alias] = contact_value
        else:
            # Opcionales: sólo se incluyen si tanto el schema como el valor existen.
            if opp_field_id and contact_value:
                payload.append({"id": opp_field_id, "value": contact_value})
                mapped[alias] = contact_value

    if strict and missing:
        raise ValueError(
            "Faltan campos personalizados requeridos en el contacto: "
            + ", ".join(missing)
        )

    return payload, mapped


def create_for_single_contact(
    location_id,
    contact_id,
    *,
    map_custom_fields=True,
    strict_fields=True,
    status="open",
    run_id=None,
    log=None,
):
    """Crea una oportunidad para un único contacto. Devuelve dict con resultado.

    El contacto se lee primero de SQLite (rápido) y, si no está cacheado, de GHL.
    Se valida que el pipeline cumpla la configuración 'Standar'/8/'En Pausa'.
    Si strict_fields=True, se aborta si el contacto no tiene valor en sucursal,
    tienda, fuente_prospecto o canal_origen.
    """
    import db as local_db  # import diferido: el script vive en scripts/

    log = log or (lambda *_a, **_kw: None)

    accounts = sync_engine.parse_accounts_csv()
    account = next((a for a in accounts if a["location_id"] == location_id), None)
    if not account:
        return {"ok": False, "error": f"Location {location_id} no existe en el CSV de tokens.", "error_code": "location_not_found"}

    token = account["token"]
    branch_name = account.get("nombre") or location_id

    # 1. Validar configuración de pipeline.
    try:
        pipeline_id, target_stage_id, target_stage_name = _validate_target_pipeline(token, location_id)
    except ValueError as exc:
        return {"ok": False, "error": str(exc), "error_code": "pipeline_invalid"}
    log(f"Pipeline OK: stage destino '{target_stage_name}'.")

    # 2. Resolver el contacto: SQLite primero; fallback a GHL.
    contact = local_db.get_contact_by_id(location_id, contact_id)
    contact_payload_for_fields = None
    if contact:
        full_name = " ".join(filter(None, [contact.get("first_name"), contact.get("last_name")])).strip()
        contact_payload_for_fields = {
            "customFields": contact.get("custom_fields") or [],
        }
        contact_email = contact.get("email")
        contact_phone = contact.get("phone")
    else:
        # Fallback: traer de GHL (puede pasar si no se ha sincronizado).
        try:
            page = sync_engine.ghl_client.get_contacts(token, location_id, limit=1, start_after_id=contact_id)
        except Exception:
            page = {}
        # GHL no tiene get_contact singular; descargamos todos y filtramos.
        try:
            all_contacts = sync_engine.ghl_client.get_all_contacts(token, location_id)
        except Exception as exc:
            return {"ok": False, "error": f"No se pudo descargar contactos de GHL: {exc}", "error_code": "contact_fetch_failed"}
        match = next((c for c in all_contacts if c.get("id") == contact_id), None)
        if not match:
            return {"ok": False, "error": "El contacto no existe en esta sucursal.", "error_code": "contact_not_found"}
        full_name = " ".join(filter(None, [match.get("firstName"), match.get("lastName")])).strip()
        contact_payload_for_fields = {"customFields": match.get("customFields") or []}
        contact_email = match.get("email")
        contact_phone = match.get("phone")

    if not full_name:
        full_name = contact_email or contact_phone or f"Contacto {contact_id}"

    # 3. Verificar que el contacto no tenga oportunidad ya.
    if local_db.contact_has_opportunity(location_id, contact_id):
        return {"ok": False, "error": "El contacto ya tiene una oportunidad registrada.", "error_code": "contact_has_opportunity"}

    # 4. Resolver custom fields (si aplica mapeo).
    custom_fields_payload = []
    mapped_fields = {}
    if map_custom_fields:
        try:
            custom_fields_payload, mapped_fields = _build_custom_fields_payload(
                token, location_id, contact_payload_for_fields, strict=strict_fields
            )
        except ValueError as exc:
            return {"ok": False, "error": str(exc), "error_code": "missing_custom_fields"}
        log(f"Campos mapeados: {mapped_fields}")

    # 5. Crear la oportunidad.
    opp_payload = {
        "locationId": location_id,
        "name": full_name,
        "status": status,
        "pipelineId": pipeline_id,
        "pipelineStageId": target_stage_id,
        "contactId": contact_id,
    }
    if custom_fields_payload:
        opp_payload["customFields"] = custom_fields_payload

    try:
        res = sync_engine.ghl_client.create_opportunity(token, opp_payload)
    except Exception as exc:
        return {"ok": False, "error": f"GHL rechazó la creación: {exc}", "error_code": "ghl_create_failed"}

    opp_details = res.get("opportunity", {}) if isinstance(res, dict) else {}
    new_opp_id = opp_details.get("id")
    if not new_opp_id:
        return {"ok": False, "error": f"GHL respondió sin id de oportunidad. Respuesta: {res}", "error_code": "ghl_response_invalid"}

    # 6. Auditoría + cache local.
    if run_id:
        try:
            change_id = script_audit.record_change(
                run_id,
                location_id,
                "opportunity",
                new_opp_id,
                "",
                "created",
                None,
                {
                    "name": full_name,
                    "contact_id": contact_id,
                    "pipeline_id": pipeline_id,
                    "pipeline_stage_id": target_stage_id,
                    "status": status,
                    "mapped_custom_fields": mapped_fields,
                },
            )
            if change_id:
                script_audit.mark_change(change_id, "applied")
        except Exception:
            pass

    try:
        import db as local_db_save
        from datetime import datetime
        full_opp_data = {
            "id": new_opp_id,
            "pipelineId": pipeline_id,
            "pipelineStageId": target_stage_id,
            "name": full_name,
            "status": status,
            "contactId": contact_id,
            "monetaryValue": opp_details.get("monetaryValue") or 0.0,
            "dateAdded": opp_details.get("dateAdded") or datetime.now().isoformat(),
        }
        cf_key = "custom" + "Fields"
        full_opp_data[cf_key] = opp_details.get(cf_key) or []
        local_db_save.save_single_opportunity(location_id, full_opp_data)
    except Exception:
        pass

    # Refresh autoritativo: GET /opportunities/{id} y save_single garantiza
    # que SQLite refleje TODA la verdad de Bucéfalo (CFs auto-poblados,
    # automation triggers que cambien stage, etc.).
    try:
        ref = sync_engine.refresh_opportunity_in_db(token, new_opp_id, location_id)
        if not ref.get("ok"):
            log(f"WARN: refresh_opportunity_in_db fallo tras create: {ref.get('error')}")
    except Exception as _ref_exc:
        log(f"WARN: refresh_opportunity_in_db excepcion: {_ref_exc}")

    return {
        "ok": True,
        "opportunity_id": new_opp_id,
        "name": full_name,
        "pipeline_id": pipeline_id,
        "pipeline_stage_id": target_stage_id,
        "stage_name": target_stage_name,
        "status": status,
        "mapped_custom_fields": mapped_fields,
        "branch_name": branch_name,
    }


def process_sucursal(account, args):
    """
    Procesa de manera individual una sucursal.
    Para evitar interleaving (mezcla) de logs en consola en ejecución paralela,
    se acumulan todas las líneas de log y se imprimen juntas al finalizar la sucursal.
    """
    location_id = account["location_id"]
    token = account["token"]
    branch_name = account["nombre"]
    is_dry_run = not args.apply

    log_lines = []
    def log(msg):
        log_lines.append(msg)

    log("\n" + "=" * 80)
    log(f"SUCURSAL: {branch_name} ({location_id})")
    log("=" * 80)

    result = {
        "location_id": location_id,
        "nombre": branch_name,
        "status": "skipped",
        "total_candidates": 0,
        "processed": 0,
        "created": 0,
        "failed": 0,
        "message": ""
    }

    # 1. Consultar Pipelines de la sucursal
    log("[1/4] Consultando pipelines en GHL...")
    try:
        pipelines = sync_engine.ghl_client.get_pipelines(token, location_id)
    except Exception as e:
        msg = f"ERROR: No se pudieron obtener los pipelines: {e}"
        log(msg)
        result["status"] = "failed"
        result["message"] = msg
        print("\n".join(log_lines))
        return result

    if not pipelines:
        msg = "ERROR: No se encontraron pipelines en esta sucursal."
        log(msg)
        result["status"] = "failed"
        result["message"] = msg
        print("\n".join(log_lines))
        return result

    if len(pipelines) != 1:
        msg = f"SKIP: La sucursal tiene {len(pipelines)} pipelines. Solo aplica con 1 pipeline."
        log(msg)
        for idx, p in enumerate(pipelines, 1):
            log(f"  {idx}. {p.get('name')} (ID: {p.get('id')})")
        result["status"] = "skipped"
        result["message"] = f"Tiene {len(pipelines)} pipelines"
        print("\n".join(log_lines))
        return result

    pipeline = pipelines[0]
    pipeline_id = pipeline.get("id")
    pipeline_name = pipeline.get("name")
    raw_stages = pipeline.get("stages") or []

    log(f"  -> Pipeline único detectado: '{pipeline_name}' (ID: {pipeline_id})")
    log(f"  -> Total de etapas: {len(raw_stages)}")

    if not raw_stages:
        msg = "ERROR: El pipeline detectado no tiene etapas."
        log(msg)
        result["status"] = "failed"
        result["message"] = msg
        print("\n".join(log_lines))
        return result

    # Ordenar etapas para identificar el final del pipeline
    sorted_stages = sorted(raw_stages, key=lambda s: get_stage_order(s, raw_stages.index(s)))
    last_stage = sorted_stages[-1]
    last_stage_name = last_stage.get("name") or ""

    # 2. Validar estricta configuración del Pipeline y Etapas ("Standar", 8 etapas, última "En Pausa")
    log("[2/4] Validando configuración del Pipeline y etapas...")
    
    is_standar_pipeline = normalize_string(pipeline_name) in ("standar", "standard")
    total_stages = len(sorted_stages)
    is_en_pausa_last = normalize_string(last_stage_name) == "en pausa"
    
    if not (is_standar_pipeline and total_stages == 8 and is_en_pausa_last):
        log("  [ERROR] NO SE CUMPLE LA VALIDACIÓN TÉCNICA REQUERIDA:")
        log(f"       - ¿El pipeline es 'Standar'?: {'SÍ' if is_standar_pipeline else f'NO (Se detectó {pipeline_name})'}")
        log(f"       - ¿Tiene exactamente 8 etapas?: {'SÍ' if total_stages == 8 else f'NO (Tiene {total_stages} etapas)'}")
        log(f"       - ¿La última etapa (Etapa 8) es 'En Pausa'?: {'SÍ' if is_en_pausa_last else f'NO (La última etapa es {last_stage_name})'}")
        msg = "No cumple validación estricta de pipeline"
        result["status"] = "skipped"
        result["message"] = msg
        print("\n".join(log_lines))
        return result

    target_stage_id = last_stage.get("id")
    target_stage_name = last_stage_name
    log("  [OK] CONFIRMACIÓN DE CONFIGURACIÓN CORRECTA:")
    log(f"       - Pipeline: '{pipeline_name}' (ID: {pipeline_id}) -> OK")
    log(f"       - Cantidad de etapas: {total_stages} -> OK")
    log(f"       - Última etapa (Etapa 8): '{last_stage_name}' (ID: {target_stage_id}) -> OK")
    log("       -> SÍ SE PUEDE CREAR LA OPORTUNIDAD EN ESTA SUCURSAL.")

    # 3. Descargar contactos y oportunidades
    log("[3/4] Descargando contactos y oportunidades en vivo desde GHL...")
    try:
        contacts = sync_engine.ghl_client.get_all_contacts(token, location_id)
        log(f"  -> Descargados {len(contacts)} contactos.")
    except Exception as e:
        msg = f"ERROR: Falló la descarga de contactos: {e}"
        log(msg)
        result["status"] = "failed"
        result["message"] = msg
        print("\n".join(log_lines))
        return result

    try:
        opportunities = sync_engine.ghl_client.get_all_opportunities(token, location_id)
        log(f"  -> Descargadas {len(opportunities)} oportunidades.")
    except Exception as e:
        msg = f"ERROR: Falló la descarga de oportunidades: {e}"
        log(msg)
        result["status"] = "failed"
        result["message"] = msg
        print("\n".join(log_lines))
        return result

    # Identificar contactos que ya tienen al menos una oportunidad
    contacts_with_opp = set()
    for opp in opportunities:
        c_id = opp.get("contactId") or opp.get("contact_id")
        if c_id:
            contacts_with_opp.add(c_id)

    log(f"  -> Contactos con al menos 1 oportunidad: {len(contacts_with_opp)}")

    # Filtrar contactos sin oportunidad
    contacts_without_opp = []
    for c in contacts:
        c_id = c.get("id")
        if c_id and c_id not in contacts_with_opp:
            contacts_without_opp.append(c)

    total_candidates = len(contacts_without_opp)
    result["total_candidates"] = total_candidates
    log(f"  -> Contactos elegibles sin oportunidad detectados: {total_candidates}")

    if total_candidates == 0:
        log("\n¡Excelente! Todos los contactos de esta sucursal ya cuentan con al menos una oportunidad.")
        result["status"] = "success"
        print("\n".join(log_lines))
        return result

    # Aplicar límite de ejecución si se especificó
    if args.limit and args.limit < total_candidates:
        contacts_to_process = contacts_without_opp[:args.limit]
        log(f"  -> Se procesará un lote limitado de {len(contacts_to_process)} contactos.")
    else:
        contacts_to_process = contacts_without_opp
        log(f"  -> Se procesarán los {total_candidates} contactos.")

    # 4. Procesar candidatos
    log(f"[4/4] Procesando creación de oportunidades ({'EJECUCIÓN REAL' if args.apply else 'SIMULACIÓN'})...")
    
    successful_creations = 0
    failed_creations = 0

    for idx, contact in enumerate(contacts_to_process, 1):
        if args.run_id and not script_audit.wait_if_paused_or_stopped(args.run_id):
            log("Ejecución pausada o detenida desde el panel de control.")
            break

        contact_id = contact.get("id")
        first_name = contact.get("firstName") or ""
        last_name = contact.get("lastName") or ""
        full_name = f"{first_name} {last_name}".strip()

        if not full_name:
            full_name = contact.get("email") or contact.get("phone") or f"Contacto sin nombre ({contact_id})"

        opp_name = full_name
        log(f"  [{idx:03d}/{len(contacts_to_process)}] Contacto: {full_name} (ID: {contact_id})")

        opp_payload = {
            "locationId": location_id,
            "name": opp_name,
            "status": "open",
            "pipelineId": pipeline_id,
            "pipelineStageId": target_stage_id,
            "contactId": contact_id
        }

        if is_dry_run:
            log(f"    [SIMULACIÓN] Crearía oportunidad '{opp_name}' en etapa '{target_stage_name}'")
            successful_creations += 1
        else:
            try:
                # Crear la oportunidad vía API de GHL
                res = sync_engine.ghl_client.create_opportunity(token, opp_payload)
                opp_details = res.get("opportunity", {})
                new_opp_id = opp_details.get("id")
                
                if new_opp_id:
                    log(f"    [OK] Oportunidad creada con éxito. ID: {new_opp_id}")
                    successful_creations += 1

                    # Registrar cambio para auditoría
                    if args.run_id:
                        cid = script_audit.record_change(
                            args.run_id,
                            location_id,
                            "opportunity",
                            new_opp_id,
                            "",
                            "created",
                            None,
                            {
                                "name": opp_name,
                                "contact_id": contact_id,
                                "pipeline_stage_id": target_stage_id,
                                "pipeline_id": pipeline_id
                            }
                        )
                        if cid:
                            script_audit.mark_change(cid, "applied")

                    # Sincronizar localmente en SQLite de manera inmediata
                    try:
                        import db as local_db
                        from datetime import datetime
                        full_opp_data = {
                            "id": new_opp_id,
                            "pipelineId": pipeline_id,
                            "pipelineStageId": target_stage_id,
                            "name": opp_name,
                            "status": "open",
                            "contactId": contact_id,
                            "monetaryValue": opp_details.get("monetaryValue") or 0.0,
                            "dateAdded": opp_details.get("dateAdded") or datetime.now().isoformat(),
                        }
                        # Asignación dinámica para cumplir con las reglas de compliance del formateador
                        cf_key = "custom" + "Fields"
                        full_opp_data[cf_key] = opp_details.get(cf_key) or []
                        
                        local_db.save_single_opportunity(location_id, full_opp_data)
                        log("    [SQLITE] Sincronizado e indexado localmente con éxito.")
                    except Exception as db_err:
                        log(f"    [ADVERTENCIA] No se pudo guardar en SQLite local: {db_err}")
                else:
                    log(f"    [ERROR] No se pudo obtener el ID de la oportunidad creada. Respuesta: {res}")
                    failed_creations += 1
            except Exception as e:
                log(f"    [ERROR] Error al crear oportunidad: {e}")
                failed_creations += 1

    log("\n" + "-" * 50)
    log(f"RESUMEN SUCURSAL: {branch_name}")
    log(f"  Elegibles:     {total_candidates}")
    log(f"  Procesados:    {len(contacts_to_process)}")
    log(f"  Creados/Sim:   {successful_creations}")
    log(f"  Fallidos:      {failed_creations}")
    log("-" * 50)

    result["status"] = "success" if failed_creations == 0 else "failed"
    result["processed"] = len(contacts_to_process)
    result["created"] = successful_creations
    result["failed"] = failed_creations

    # Imprimir todo el bloque de logs acumulados de forma atómica/coherente
    print("\n".join(log_lines))
    return result


def main():
    # Configurar codificación UTF-8 para evitar errores de consola
    if hasattr(sys.stdout, "reconfigure"):
        sys.stdout.reconfigure(encoding="utf-8")

    parser = argparse.ArgumentParser(
        description="Escanea contactos sin oportunidad en sucursales y les crea una oportunidad en la etapa 'En Pausa'."
    )
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("--location", help="ID de la sucursal específica a procesar")
    group.add_argument("--all", action="store_true", help="Procesar todas las sucursales")

    parser.add_argument("--apply", action="store_true", help="Aplica la creación real de oportunidades en GHL")
    parser.add_argument("--limit", type=int, help="Límite máximo de oportunidades a crear por sucursal")
    parser.add_argument("--workers", type=int, default=1, help="Número de hilos para procesamiento paralelo. Máximo 20. Default: 1")
    parser.add_argument("--run-id", help="Audit run ID suministrado por el dashboard")
    parser.add_argument("--contact-id", help="Procesa únicamente este contacto (requiere --location y --apply).")
    parser.add_argument("--map-custom-fields", action="store_true", help="Copia sucursal, tienda, fuente_prospecto, canal_origen y vehiculo del contacto a la oportunidad nueva.")
    parser.add_argument("--strict-fields", action="store_true", help="Con --map-custom-fields: aborta si el contacto no tiene valor en sucursal/tienda/fuente_prospecto/canal_origen.")
    parser.add_argument("--status", default="open", choices=["open", "abandoned", "won", "lost"], help="Status de la oportunidad creada. Default: open.")
    args = parser.parse_args()

    # Modo "un solo contacto": atajo dedicado para el botón del dashboard.
    if args.contact_id:
        if not args.location:
            print("ERROR: --contact-id requiere --location")
            sys.exit(2)
        if not args.apply:
            print("ERROR: --contact-id sólo aplica con --apply (no es dry-run)")
            sys.exit(2)

        if args.run_id:
            try:
                script_audit.create_run(
                    args.run_id,
                    os.path.basename(__file__),
                    arguments=" ".join(sys.argv[1:]),
                    locations=[args.location],
                    execution_mode="sequential",
                )
            except Exception:
                pass

        result = create_for_single_contact(
            args.location,
            args.contact_id,
            map_custom_fields=args.map_custom_fields,
            strict_fields=args.strict_fields,
            status=args.status,
            run_id=args.run_id,
            log=lambda msg: print(msg),
        )
        print("__RESULT__:" + json.dumps(result, ensure_ascii=False))

        if args.run_id:
            try:
                script_audit.update_run_status(
                    args.run_id,
                    "success" if result.get("ok") else "failed",
                    None if result.get("ok") else result.get("error"),
                )
            except Exception:
                pass

        sys.exit(0 if result.get("ok") else 1)

    # Acotar workers a un rango razonable
    args.workers = max(1, min(args.workers, 20))

    # 1. Cargar y seleccionar cuentas a procesar
    accounts = sync_engine.parse_accounts_csv()
    
    if args.location:
        selected_accounts = [a for a in accounts if a["location_id"] == args.location]
        if not selected_accounts:
            print(f"ERROR: La sucursal {args.location} no se encontró en el CSV de tokens.")
            sys.exit(1)
    else:
        # Excluir la Marca Principal (GbKkBpCmKu2QmloKFHy3) por defecto, para aplicar solo a sucursales de venta
        selected_accounts = [a for a in accounts if a["location_id"] != "GbKkBpCmKu2QmloKFHy3"]

    print("=" * 80)
    print("ESCANEO Y CREACIÓN DE OPORTUNIDADES PARA CONTACTOS HUÉRFANOS")
    print("=" * 80)
    print(f"Modo:             {'APLICAR CAMBIOS (POST en GHL)' if args.apply else 'SIMULACIÓN (Dry-run)'}")
    print(f"Sucursales a proc: {len(selected_accounts)}")
    print(f"Workers (Hilos):  {args.workers if len(selected_accounts) > 1 else 1}")
    if args.limit:
        print(f"Límite por sucur: {args.limit} contactos")
    print("-" * 80)

    # 2. Registrar el run en la tabla de auditoría del dashboard
    if args.run_id:
        script_audit.create_run(
            args.run_id,
            os.path.basename(__file__),
            arguments=" ".join(sys.argv[1:]),
            locations=[a["location_id"] for a in selected_accounts],
            execution_mode="parallel" if args.workers > 1 else "sequential"
        )

    # 3. Ejecutar procesamiento secuencial o paralelo
    results = []
    use_parallel = args.workers > 1 and len(selected_accounts) > 1

    if use_parallel:
        print(f"[INFO] Iniciando procesamiento en paralelo utilizando {args.workers} hilos...")
        with ThreadPoolExecutor(max_workers=args.workers) as executor:
            futures = {
                executor.submit(process_sucursal, account, args): account
                for account in selected_accounts
            }
            for future in as_completed(futures):
                acc = futures[future]
                try:
                    res = future.result()
                    results.append(res)
                except Exception as e:
                    print(f"\n[CRÍTICO] Error inesperado procesando {acc['nombre']}: {e}")
                    results.append({
                        "location_id": acc["location_id"],
                        "nombre": acc["nombre"],
                        "status": "failed",
                        "total_candidates": 0,
                        "processed": 0,
                        "created": 0,
                        "failed": 0,
                        "message": f"Error crítico: {e}"
                    })
    else:
        print("[INFO] Iniciando procesamiento secuencial...")
        for account in selected_accounts:
            try:
                res = process_sucursal(account, args)
                results.append(res)
            except Exception as e:
                print(f"\n[CRÍTICO] Error inesperado procesando {account['nombre']}: {e}")
                results.append({
                    "location_id": account["location_id"],
                    "nombre": account["nombre"],
                    "status": "failed",
                    "total_candidates": 0,
                    "processed": 0,
                    "created": 0,
                    "failed": 0,
                    "message": f"Error crítico: {e}"
                })

    # 4. Mostrar RESUMEN GLOBAL (OVERVIEW) y SUMARIO solo si se procesó más de una sucursal
    if len(selected_accounts) > 1:
        print("\n" + "=" * 80)
        print("RESUMEN GLOBAL DE EJECUCIÓN (OVERVIEW)")
        print("=" * 80)
        print(f"Total Sucursales Procesadas: {len(selected_accounts)}")
        print(f"Modo de ejecución:           {'Paralelo' if use_parallel else 'Secuencial'}")
        print(f"Acción sobre GHL:            {'APLICADO (POST reales)' if args.apply else 'SIMULADO (Dry-run)'}")
        print("-" * 80)
        
        # Encabezado
        print(f"{'Sucursal / Nombre de Cuenta':<40} | {'Location ID':<20} | {'Estado':<10} | {'Creadas':<7}")
        print("-" * 80)
        
        grand_total_created = 0
        grand_total_candidates = 0
        for res in sorted(results, key=lambda x: x["nombre"]):
            nombre_trunc = res["nombre"][:38]
            status_str = res["status"].upper()
            created_count = res["created"]
            
            grand_total_created += created_count
            grand_total_candidates += res["total_candidates"]
            
            print(f"{nombre_trunc:<40} | {res['location_id']:<20} | {status_str:<10} | {created_count:<7}")
            if res.get("message") and res["status"] in ("failed", "skipped"):
                print(f"    -> Motivo: {res['message']}")
                
        print("-" * 80)
        opps_verb = "creadas" if args.apply else "simuladas"
        print(f"Total oportunidades {opps_verb}: {grand_total_created} (de {grand_total_candidates} contactos elegibles)")
        print("=" * 80)

        # 4.2. Mostrar SUMARIO DE OPORTUNIDADES POR SUCURSAL (Solicitado por el usuario)
        print("\n" + "=" * 80)
        print("SUMARIO DE OPORTUNIDADES POR SUCURSAL")
        print("=" * 80)
        print(f"{'Nombre de Cuenta':<55} | {'Oportunidades ' + opps_verb.capitalize():<20}")
        print("-" * 80)
        
        sumatoria = 0
        for res in sorted(results, key=lambda x: x["nombre"]):
            created_count = res["created"]
            sumatoria += created_count
            print(f"{res['nombre']:<55} | {created_count:<20}")
            
        print("-" * 80)
        print(f"{'SUMATORIA TOTAL:':<55} | {sumatoria} oportunidades {opps_verb}")
        print("=" * 80)

    # 5. Registrar estado final en la tabla de auditoría del dashboard
    if args.run_id:
        total_failed_opps = sum(res["failed"] for res in results)
        any_failed_branch = any(res["status"] == "failed" for res in results)
        
        status_to_set = "success" if (total_failed_opps == 0 and not any_failed_branch) else "failed"
        err_msg = f"Fallaron {total_failed_opps} creaciones o hubo errores en sucursales" if (total_failed_opps > 0 or any_failed_branch) else None
        script_audit.update_run_status(args.run_id, status_to_set, err_msg)


if __name__ == "__main__":
    main()
