#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Fase 1 — SUCURSAL → MARCA: match primario por id_contacto_sucursal.

Insertar entre `Datos API Cuenta objetivo - MARCA` y `Buscar Contacto Objetivo
- MARCA (phone)`:
  1) HTTP search por CF
  2) IF que decide CF match único
     - true → Capturar ID Match (reutilizado)
     - false → Buscar Contacto Objetivo - MARCA (phone) (cascada existente)

Modificar los code nodes de CREATE y UPDATE para que inyecten el CF
`contact.id_contacto_sucursal` con el id del contacto origen, garantizando
enlace incluso si el match cayó a la cascada.

Uso:
    python n8n/_apply_phase1.py             # dry-run
    python n8n/_apply_phase1.py --apply
"""
import argparse
import copy
import json
import os
import re
import sys
import uuid

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT)
sys.path.insert(0, os.path.join(ROOT, "scripts"))
from n8n_workflow_lib import load_credentials, N8NClient  # noqa: E402

WID = "x4DqZ5FtSc43tdzB"
CF_ID_BRAND = "E6lI9ykWhqpj7Pmi7Qd3"

NEW_HTTP_NAME = "Buscar Contacto Objetivo - MARCA (id_contacto_sucursal)"
NEW_IF_NAME = "¿id_contacto_sucursal match único?"


def http_node_search_by_cf():
    return {
        "parameters": {
            "method": "POST",
            "url": "https://services.leadconnectorhq.com/contacts/search",
            "sendHeaders": True,
            "headerParameters": {
                "parameters": [
                    {"name": "Version", "value": "2021-07-28"},
                    {"name": "Authorization",
                     "value": "=Bearer {{ $('Datos API Cuenta objetivo - MARCA').item.json['Token/API'] }}"},
                ]
            },
            "sendBody": True,
            "specifyBody": "json",
            "jsonBody": (
                "={\n"
                "  \"locationId\": \"{{ $('Datos API Cuenta objetivo - MARCA').item.json['Location ID'] }}\",\n"
                "  \"pageLimit\": 5,\n"
                "  \"filters\": [\n"
                "    {\n"
                "      \"group\": \"AND\",\n"
                "      \"filters\": [\n"
                "        {\n"
                "          \"field\": \"customFields." + CF_ID_BRAND + "\",\n"
                "          \"operator\": \"eq\",\n"
                "          \"value\": \"{{ $('Crear Contacto').item.json.body.contact_id }}\"\n"
                "        }\n"
                "      ]\n"
                "    }\n"
                "  ]\n"
                "}"
            ),
            "options": {"redirect": {"redirect": {}}},
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [2832, -496],
        "id": str(uuid.uuid4()),
        "name": NEW_HTTP_NAME,
        "onError": "continueRegularOutput",
        "alwaysOutputData": True,
        "notes": (
            "FASE 1 — Match primario por contact.id_contacto_sucursal. "
            "Si total==1, el siguiente IF deriva a Capturar ID Match. "
            "Si no, sigue a la cascada phone→email→nombre."
        ),
    }


def if_node_cf_match():
    return {
        "parameters": {
            "conditions": {
                "options": {
                    "caseSensitive": True,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 3,
                },
                "conditions": [
                    {
                        "id": str(uuid.uuid4()),
                        "leftValue": "={{ $json.total }}",
                        "rightValue": 1,
                        "operator": {"type": "number", "operation": "equals"},
                    }
                ],
                "combinator": "and",
            },
            "options": {},
        },
        "type": "n8n-nodes-base.if",
        "typeVersion": 2,
        "position": [3040, -496],
        "id": str(uuid.uuid4()),
        "name": NEW_IF_NAME,
        "notes": "FASE 1. true → Capturar ID Match. false → cascada phone.",
    }


# ---- Patch helpers para code nodes ----

INJECT_BLOCK_UPDATE = (
    "\n// === FASE 1: inyectar CF id_contacto_sucursal (defensive) ===\n"
    "var __linkId = '" + CF_ID_BRAND + "';\n"
    "customFields = customFields.filter(function(c){ return c.id !== __linkId; });\n"
    "if (origenData && origenData.id) {\n"
    "  customFields.push({ id: __linkId, key: 'contact.id_contacto_sucursal', field_value: origenData.id });\n"
    "}\n"
)

INJECT_BLOCK_CREATE_FK = (
    "\n// === FASE 1: garantizar CF contact.id_contacto_sucursal ===\n"
    "(function(){\n"
    "  var origenId = ($('Obtener Contacto Cuenta Origen - SUCURSAL').first().json.contact || {}).id;\n"
    "  if (!origenId) return;\n"
    "  // Encontrar el def en Marca por fieldKey.\n"
    "  var defLink = marcaCustomDefs.find(function(d){ return d.fieldKey === 'contact.id_contacto_sucursal'; });\n"
    "  if (!defLink) return;\n"
    "  customFields = customFields.filter(function(c){ return c.id !== defLink.id; });\n"
    "  customFields.push({ id: defLink.id, field_value: origenId });\n"
    "})();\n"
)


def patch_update_code(wf, client):
    """Modifica `Obtener Body para Actualizar Contacto Objetivo - MARCA`."""
    node = client.find_node(wf, "Obtener Body para Actualizar Contacto Objetivo - MARCA")
    if not node:
        raise RuntimeError("nodo update no encontrado")
    code = node["parameters"].get("jsCode") or ""
    if "FASE 1: inyectar CF id_contacto_sucursal" in code:
        print("  [skip] code update ya tiene el bloque FASE 1")
        return False
    # Insertar antes de `var body = {`
    marker = "var body = {"
    idx = code.rfind(marker)
    if idx < 0:
        raise RuntimeError("marker 'var body = {' no encontrado en code update")
    new_code = code[:idx] + INJECT_BLOCK_UPDATE + code[idx:]
    node["parameters"]["jsCode"] = new_code
    return True


def patch_create_code(wf, client):
    """Modifica `Obtener el body para crear Contacto - MARCA`."""
    node = client.find_node(wf, "Obtener el body para crear Contacto - MARCA")
    if not node:
        raise RuntimeError("nodo create no encontrado")
    code = node["parameters"].get("jsCode") or ""
    if "FASE 1: garantizar CF contact.id_contacto_sucursal" in code:
        print("  [skip] code create ya tiene el bloque FASE 1")
        return False
    marker = "var body = {"
    idx = code.rfind(marker)
    if idx < 0:
        raise RuntimeError("marker 'var body = {' no encontrado en code create")
    new_code = code[:idx] + INJECT_BLOCK_CREATE_FK + code[idx:]
    node["parameters"]["jsCode"] = new_code
    return True


# ---- Main ----

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--apply", action="store_true", help="Aplica al workflow. Sin esto: dry-run.")
    parser.add_argument("--activate", action="store_true", help="Tras apply OK, activar el workflow.")
    args = parser.parse_args()
    if hasattr(sys.stdout, "reconfigure"):
        sys.stdout.reconfigure(encoding="utf-8")

    client = N8NClient(*load_credentials())
    print(f"[fase 1] GET workflow {WID}")
    wf, backup_path = client.backup_workflow(WID, label="fase1_pre")
    prev_version = wf.get("versionId")
    print(f"  backup: {backup_path}")
    print(f"  versionId: {prev_version}  active: {wf.get('active')}")
    print(f"  nodes pre: {len(wf['nodes'])}")

    # Trabajar sobre copia
    wfm = copy.deepcopy(wf)

    # Idempotencia
    try:
        client.assert_idempotent(wfm, NEW_HTTP_NAME)
        client.assert_idempotent(wfm, NEW_IF_NAME)
    except Exception as e:
        raise SystemExit(f"Aborto: {e}")

    # Insertar nodos
    print("[fase 1] insertando nodos nuevos...")
    http_node = http_node_search_by_cf()
    if_node = if_node_cf_match()
    client.add_node(wfm, http_node)
    client.add_node(wfm, if_node)

    # Reconectar:
    #   antes: Datos API Cuenta objetivo - MARCA → Buscar Contacto Objetivo - MARCA (phone)
    #   ahora: Datos API → HTTP nuevo → IF nuevo
    #            IF true → Capturar ID Match
    #            IF false → Buscar Contacto Objetivo - MARCA (phone)
    client.set_connection(wfm, "Datos API Cuenta objetivo - MARCA", NEW_HTTP_NAME)
    client.set_connection(wfm, NEW_HTTP_NAME, NEW_IF_NAME)
    client.branch_if(wfm, NEW_IF_NAME,
                     true_target="Capturar ID Match",
                     false_target="Buscar Contacto Objetivo - MARCA (phone)")

    # Modificar code nodes
    print("[fase 1] modificando code UPDATE...")
    patch_update_code(wfm, client)
    print("[fase 1] modificando code CREATE...")
    patch_create_code(wfm, client)

    print(f"[fase 1] nodes post: {len(wfm['nodes'])}  (+{len(wfm['nodes'])-len(wf['nodes'])})")

    # Dry-run
    print("[fase 1] dry-run PUT (dump a archivo)...")
    res = client.put_workflow(WID, wfm, dry_run=True)
    print(f"  dry-run path: {res['path']}")

    if not args.apply:
        print("\nDRY-RUN. Para aplicar: --apply [--activate]")
        return

    # Apply real
    print("[fase 1] PUT real...")
    client.put_workflow(WID, wfm, dry_run=False)
    print("  PUT OK. Verificando...")
    wf_post = client.verify_post(
        WID,
        expected_node_names=[NEW_HTTP_NAME, NEW_IF_NAME],
        prev_version_id=prev_version,
    )
    print(f"  versionId nuevo: {wf_post.get('versionId')}")
    print(f"  active post-PUT: {wf_post.get('active')}")

    if args.activate or not wf_post.get("active"):
        print("[fase 1] activando workflow...")
        client.activate(WID)
        wf_post2 = client.get_workflow(WID)
        print(f"  active final: {wf_post2.get('active')}")

    print("\n[fase 1] aplicado y activado.")
    print("Siguiente paso: python scripts/n8n_e2e_test.py --scenario all-phase1")


if __name__ == "__main__":
    main()
