#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Garantiza que la opp sincronizada a Marca traiga Sucursal / TIENDA / Canal de
Origen, derivándolos del CONTACTO de sucursal cuando la opp de origen no los
trae (workflow `Cfgwp0bOtDW8zuKW` "Sincronizar Oportunidad - Nodos Nuevos").

Contexto: los nodos `Armar Body - CREATE/UPDATE` ya copian TODOS los custom
fields de la opp de sucursal → Marca por `fieldKey`. Pero si la opp de origen
llega sin esos CF (carrera de tiempo / creación fuera de flujo), la opp de Marca
queda vacía (caso `8HIT…` de Miguel Temixco). El CONTACTO siempre los tiene
(poblados por [1604]/[2004]) y usa el MISMO `fieldKey` canónico que la opp:
  contact.sucursal                 -> opportunity.sucursal
  contact.tienda                   -> opportunity.tienda
  contact.fuente_de_posible_cliente-> opportunity.fuente_de_posible_cliente  (CANAL DE ORIGEN)

Cambios (solo AÑADE / enriquece, preserva el flujo actual):
  1. Nodo nuevo `Obtener Contacto - SUCURSAL` (GET /contacts/{id}) insertado
     entre `Obtener info de Oportunidad - SUCURSAL` y `Obtener Pipelines - SUCURSAL`.
  2. Extiende el Code node `Mapeo completo oportunidad origen - SUCURSAL` para,
     tras enriquecer los CF de la opp, hacer upsert de Sucursal/TIENDA/Canal con
     prioridad (a) valor de la opp → (b) valor del contacto → (c) webhook.
     El loop genérico de `Armar Body` los propaga a Marca por `fieldKey`.
     No depende de leer-después-de-escribir (sin race).

Uso:
    python n8n/_add_contact_to_opp_mapping.py            # dry-run (dumpea payload)
    python n8n/_add_contact_to_opp_mapping.py --apply     # aplica + reactiva
"""

import argparse
import os
import sys

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)

from scripts.n8n_workflow_lib import N8NClient, load_credentials  # noqa: E402

WID = "Cfgwp0bOtDW8zuKW"

# Nodos existentes (referencias).
MAPEO_NODE = "Mapeo completo oportunidad origen - SUCURSAL"
OBTENER_INFO_NODE = "Obtener info de Oportunidad - SUCURSAL"
OBTENER_PIPELINES_NODE = "Obtener Pipelines - SUCURSAL"
DATOS_API_SUCURSAL = "DATOS API - SUCURSAL"
CF_DEFS_NODE = "Conseguir Custom Fields - Opportunity - SUCURSAL"

# Nodo nuevo.
GET_CONTACT_NODE = "Obtener Contacto - SUCURSAL"

# Marca de idempotencia dentro del jsCode extendido.
ENRICH_MARKER = "CONTACT->OPP ENRICH"

CONTACT_ID_EXPR = "{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}"
TOKEN_EXPR = "{{ $('" + DATOS_API_SUCURSAL + "').item.json['Token/API'] }}"

# Bloque que se inyecta en el Code node, justo antes de `const stageInfo`.
ENRICH_BLOCK = r"""
// ── CONTACT->OPP ENRICH: garantizar Sucursal / TIENDA / Canal de Origen ──
// Respaldo cuando la opp de sucursal no trae el CF: tomar el valor del CONTACTO
// (siempre poblado por [1604]/[2004]) y, en ultimo caso, del webhook. El fieldKey
// canonico (sufijo) es identico en contact y opportunity.
const contactResp = $('Obtener Contacto - SUCURSAL').first().json;
const contact = (contactResp && contactResp.contact) ? contactResp.contact : (contactResp || {});
const webhookBody = ($('Webhook').first().json && $('Webhook').first().json.body) || {};
const norm2 = (s) => (s == null ? '' : String(s)).trim();

// Valores del contacto por fieldKey (contacts traen {id, value}).
const contactValueByFieldKey = {};
for (const cf of (contact.customFields || [])) {
  const fk = fieldMap[cf.id] ? fieldMap[cf.id].fieldKey : null;
  if (fk) contactValueByFieldKey[fk] = cf.value;
}

const ENRICH_TARGETS = [
  { oppKey: 'opportunity.sucursal',                  contactKey: 'contact.sucursal',                  webhookKey: 'Sucursal' },
  { oppKey: 'opportunity.tienda',                    contactKey: 'contact.tienda',                    webhookKey: 'TIENDA' },
  { oppKey: 'opportunity.fuente_de_posible_cliente', contactKey: 'contact.fuente_de_posible_cliente', webhookKey: 'CANAL DE ORIGEN' },
];

for (const t of ENRICH_TARGETS) {
  const existing = enrichedCustomFields.find(cf => cf.fieldKey === t.oppKey);
  if (existing && norm2(existing.fieldValue) !== '') continue;        // (a) ya viene de la opp
  let value = norm2(contactValueByFieldKey[t.contactKey]);            // (b) contacto
  if (value === '') value = norm2(webhookBody[t.webhookKey]);         // (c) webhook
  if (value === '') continue;
  if (existing) {
    existing.fieldValue = value;
  } else {
    const def = customFieldsDefs.find(d => d.fieldKey === t.oppKey);
    enrichedCustomFields.push({ id: def ? def.id : null, name: def ? def.name : null, fieldKey: t.oppKey, fieldValue: value });
  }
}
// ── fin CONTACT->OPP ENRICH ──
"""


def ensure_model_all(cf_node):
    """Asegura que `Conseguir Custom Fields…` pase ?model=all (sin esto el
    endpoint /locations/{id}/customFields solo devuelve campos de CONTACTO, y el
    mapeo genérico descarta TODOS los CF nativos de la opp). Devuelve True si
    cambió algo."""
    p = cf_node.setdefault("parameters", {})
    p["sendQuery"] = True
    qp = p.setdefault("queryParameters", {})
    params = qp.setdefault("parameters", [])
    for entry in params:
        if entry.get("name") == "model":
            if entry.get("value") == "all":
                return False
            entry["value"] = "all"
            return True
    params.append({"name": "model", "value": "all"})
    return True


def build_get_contact_node():
    return {
        "parameters": {
            "url": "=https://services.leadconnectorhq.com/contacts/" + CONTACT_ID_EXPR,
            "sendHeaders": True,
            "headerParameters": {
                "parameters": [
                    {"name": "Accept", "value": "application/json"},
                    {"name": "Version", "value": "2021-07-28"},
                    {"name": "Authorization", "value": "=Bearer " + TOKEN_EXPR},
                ]
            },
            "options": {"redirect": {"redirect": {}}},
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [1750, -32],
        "name": GET_CONTACT_NODE,
        "onError": "continueRegularOutput",
        "notes": "Trae el contacto de sucursal para derivar Sucursal/TIENDA/Canal de Origen de la opp cuando la opp de origen no los trae. onError=continue para no romper el sync.",
    }


def inject_enrich(js_code):
    """Inserta ENRICH_BLOCK antes de `const stageInfo` (anchor estable)."""
    anchor_candidates = [
        "// Resolve pipeline and stage info",
        "const stageInfo =",
    ]
    for anchor in anchor_candidates:
        idx = js_code.find(anchor)
        if idx != -1:
            return js_code[:idx] + ENRICH_BLOCK + "\n" + js_code[idx:]
    raise SystemExit(
        "No se encontró un anchor para inyectar el bloque de enriquecimiento "
        "en el jsCode del nodo Mapeo. Revisar el Code node manualmente."
    )


def main():
    parser = argparse.ArgumentParser(
        description="Mapea Sucursal/TIENDA/Canal de Origen del contacto a la opp en el workflow Cfgwp0bOtDW8zuKW."
    )
    parser.add_argument("--apply", action="store_true", help="Aplica el PUT real (sin esto: dry-run).")
    args = parser.parse_args()

    client = N8NClient(*load_credentials())
    wf, backup_path = client.backup_workflow(WID, label="contact_to_opp_mapping")
    prev_version = wf.get("versionId")
    print(f"Workflow: {wf.get('name')}")
    print(f"  active={wf.get('active')} nodes={len(wf.get('nodes') or [])} versionId={prev_version}")
    print(f"  backup -> {backup_path}")

    # Validar que existen los nodos de referencia.
    for nm in (MAPEO_NODE, OBTENER_INFO_NODE, OBTENER_PIPELINES_NODE, DATOS_API_SUCURSAL, CF_DEFS_NODE):
        if client.find_node(wf, nm) is None:
            raise SystemExit(f"No se encontró el nodo esperado {nm!r}.")

    # Idempotencia.
    code_node = client.find_node(wf, MAPEO_NODE)
    cf_node = client.find_node(wf, CF_DEFS_NODE)
    already_node = client.find_node(wf, GET_CONTACT_NODE) is not None
    already_code = ENRICH_MARKER in (code_node["parameters"].get("jsCode") or "")
    cf_params = (cf_node.get("parameters") or {}).get("queryParameters", {}).get("parameters", [])
    already_query = any(e.get("name") == "model" and e.get("value") == "all" for e in cf_params)
    if already_node and already_code and already_query:
        raise SystemExit("El complemento ya fue aplicado (nodo + jsCode + model=all). Nada que hacer.")

    # 0. Asegurar ?model=all en el fetch de custom fields (trae defs de opp).
    if ensure_model_all(cf_node):
        print(f"  Nodo {CF_DEFS_NODE!r}: query 'model=all' añadido (ahora trae defs de opportunity).")
    else:
        print(f"  Nodo {CF_DEFS_NODE!r} ya tenía 'model=all'.")

    # 1. Nodo nuevo GET contacto + cableado.
    if not already_node:
        client.add_node(wf, build_get_contact_node())
        client.insert_between(wf, OBTENER_INFO_NODE, GET_CONTACT_NODE, OBTENER_PIPELINES_NODE)
        print(f"  Nodo {GET_CONTACT_NODE!r} insertado entre "
              f"{OBTENER_INFO_NODE!r} y {OBTENER_PIPELINES_NODE!r}.")
    else:
        print(f"  Nodo {GET_CONTACT_NODE!r} ya existía (no se re-inserta).")

    # 2. Enriquecer el Code node.
    if not already_code:
        code_node["parameters"]["jsCode"] = inject_enrich(code_node["parameters"]["jsCode"])
        print(f"  Code node {MAPEO_NODE!r}: bloque de enriquecimiento inyectado.")
    else:
        print(f"  Code node {MAPEO_NODE!r} ya tenía el bloque (no se re-inyecta).")

    expected = [GET_CONTACT_NODE]

    if not args.apply:
        res = client.put_workflow(WID, wf, dry_run=True)
        print(f"\nDRY-RUN. Payload -> {res['path']} ({res['node_count']} nodos).")
        print("Revisa el JSON y vuelve a correr con --apply para aplicar.")
        return

    was_active = bool(wf.get("active"))
    if was_active:
        try:
            client.deactivate(WID)
            print("  Workflow desactivado para PUT estructural.")
        except Exception as exc:
            print(f"  WARN al desactivar: {exc}")
    client.put_workflow(WID, wf, dry_run=False)
    print("  PUT aplicado.")
    if was_active:
        client.activate(WID)
        print("  Workflow reactivado.")
    client.verify_post(WID, expected_node_names=expected, prev_version_id=prev_version)
    print("\nOK: complemento aplicado y verificado. Backup en:", backup_path)


if __name__ == "__main__":
    main()
