<?php
// api/automations-engine.php — Motor de automatizaciones SuperCerebro
// Ejecución: 0 * * * * (cada hora)
//
// Condiciones soportadas (v3):
//   meta_spend_zero           → sin inversión Meta últimos N días
//   meta_campaigns_paused     → todas las campañas Meta pausadas
//   meta_frequency_high       → frecuencia promedio 7d > umbral
//   meta_cpa_above            → CPA Meta 7d > umbral $
//   meta_conversions_drop     → conversiones Meta cayeron X% vs semana anterior
//   google_is_below           → IS% < umbral (últimos 7 días)
//   google_spend_zero         → sin inversión Google últimos N días
//   google_cpa_above          → CPA Google 7d > umbral $
//   google_conversions_zero   → 0 conversiones en últimos N días
//   google_conversions_drop   → conversiones Google cayeron X% vs semana anterior
//   seo_clicks_drop           → caída clics orgánicos ≥ umbral %
//   email_open_rate_low       → tasa apertura Mailchimp < umbral %
//   semaforo_red              → cualquier canal en rojo (composite)
//
// Acciones soportadas (v3):
//   send_email                → email de alerta al admin
//   log_only                  → solo registro en dm_automation_log
//   pause_campaign_meta       → pausa todas las campañas activas Meta
//   pause_campaign_google     → pausa todas las campañas activas Google
//   activate_campaigns_meta   → activa todas las campañas pausadas Meta
//   activate_campaigns_google → activa todas las campañas pausadas Google
//   change_budget_meta        → reduce presupuesto diario Meta X%
//   change_budget_google      → reduce presupuestos Google Ads X%
//   increase_budget_meta      → aumenta presupuesto diario Meta X%
//   increase_budget_google    → aumenta presupuestos Google Ads X%

require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/../db.php';
require_once __DIR__ . '/../credentials.php';
require_once __DIR__ . '/google-auth.php';

if (!$pdo) { error_log('[automations-engine] Sin conexión a BD'); exit(1); }

// ── FASE 4: kill switch consultable en cualquier momento del flujo ──────────
// Antes el check solo se hacía al inicio. Ahora se reconsulta antes de cada
// acción REAL (pause_campaign_*, increase_budget_*) para que si el usuario
// activa el switch durante un run en vuelo, no se sigan ejecutando mutaciones.
function dm_auto_kill_switch_active(): bool {
    global $pdo;
    try {
        $st = $pdo->prepare("SELECT `value` FROM dm_settings WHERE `key`='automation_paused' LIMIT 1");
        $st->execute();
        $v = $st->fetchColumn();
        return $v === '1';
    } catch (\Throwable $e) { return false; }
}

if (dm_auto_kill_switch_active()) {
    echo "[automations-engine] Kill switch activo — ninguna automatización ejecutada.\n";
    exit(0);
}

// ── Asegurar que las tablas existen ─────────────────────────────────────────
$pdo->exec("
    CREATE TABLE IF NOT EXISTS dm_automations (
        id                INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        client_id         INT UNSIGNED NOT NULL,
        name              VARCHAR(100) NOT NULL,
        condition_type    VARCHAR(40)  NOT NULL,
        condition_params  JSON         NOT NULL,
        action_type       VARCHAR(40)  NOT NULL,
        action_params     JSON         NOT NULL,
        frequency         ENUM('hourly','daily','weekly') NOT NULL DEFAULT 'daily',
        status            ENUM('active','paused') NOT NULL DEFAULT 'active',
        last_triggered_at DATETIME     NULL,
        trigger_count     INT UNSIGNED NOT NULL DEFAULT 0,
        created_at        DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_client (client_id),
        INDEX idx_status  (status)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
$pdo->exec("
    CREATE TABLE IF NOT EXISTS dm_automation_log (
        id            INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        automation_id INT UNSIGNED NOT NULL,
        client_id     INT UNSIGNED NOT NULL,
        result        ENUM('triggered','skipped','error') NOT NULL DEFAULT 'triggered',
        detail        TEXT,
        triggered_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        INDEX idx_auto   (automation_id),
        INDEX idx_client (client_id),
        INDEX idx_ts     (triggered_at)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");

$now     = new DateTime();
$hourNow = (int)$now->format('G');  // 0-23
$dayNow  = (int)$now->format('N');  // 1=Lun … 7=Dom

// ── Leer configuración de horario desde dm_settings ─────────────────────────
$notif_cfg = [
    'notif_window_start' => 8,
    'notif_window_end'   => 20,
    'notif_daily_hour'   => 9,
    'notif_weekly_day'   => 1,
    'notif_weekly_hour'  => 9,
    'notif_digest_mode'  => 0,
];
try {
    $cfg_keys  = "'".implode("','", array_keys($notif_cfg))."'";
    $cfg_rows  = $pdo->query(
        "SELECT `key`,`value` FROM dm_settings WHERE `key` IN ({$cfg_keys})"
    )->fetchAll(PDO::FETCH_KEY_PAIR);
    foreach ($notif_cfg as $k => $def) {
        $notif_cfg[$k] = isset($cfg_rows[$k]) ? (int)$cfg_rows[$k] : $def;
    }
} catch (Exception $e) { /* tabla no existe aún, usa defaults */ }

$digest_mode   = (bool)$notif_cfg['notif_digest_mode'];
$digest_alerts = []; // acumula alertas cuando digest_mode=true

// ── Obtener automatizaciones activas ────────────────────────────────────────
$autos = $pdo->query("
    SELECT a.*, c.name AS client_name, c.services
      FROM dm_automations a
      JOIN dm_clients c ON c.id = a.client_id
     WHERE a.status = 'active' AND c.active = 1
     ORDER BY a.client_id, a.id
")->fetchAll(PDO::FETCH_ASSOC);

if (empty($autos)) {
    echo "[automations-engine] Sin automatizaciones activas.\n";
    exit(0);
}

// ── Cargar historial de métricas de los clientes implicados ─────────────────
$client_ids   = array_unique(array_column($autos, 'client_id'));
$placeholders = implode(',', array_fill(0, count($client_ids), '?'));
$d14 = date('Y-m-d', strtotime('-14 days'));

$stmt = $pdo->prepare("
    SELECT client_id, service, metric_date, data
      FROM dm_metrics_history
     WHERE client_id IN ({$placeholders}) AND metric_date >= ?
     ORDER BY client_id, service, metric_date ASC
");
$stmt->execute(array_merge($client_ids, [$d14]));

$history = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
    $history[$row['client_id']][$row['service']][$row['metric_date']]
        = json_decode($row['data'], true);
}

$d7 = date('Y-m-d', strtotime('-7 days'));
$d3 = date('Y-m-d', strtotime('-3 days'));

$triggered_total = 0;

foreach ($autos as $auto) {
    $cid    = (int)$auto['client_id'];
    $h      = $history[$cid] ?? [];
    $cparams = json_decode($auto['condition_params'], true) ?? [];
    $aparams = json_decode($auto['action_params'],   true) ?? [];

    // ── Verificar si las alertas de este cliente están silenciadas ───────
    try {
        $ps = $pdo->prepare("SELECT `value` FROM dm_settings WHERE `key`=? LIMIT 1");
        $ps->execute(["alerts_paused_until_{$cid}"]);
        $pv = $ps->fetchColumn();
        if ($pv && intval($pv) > time()) {
            echo "[engine] Cliente {$cid} con alertas silenciadas hasta ".date('Y-m-d H:i', intval($pv))."\n";
            continue;
        }
    } catch (Exception $e) { /* dm_settings no existe aún */ }

    // ── FASE 4 FIX: lock advisory MySQL para evitar race con crons paralelos ──
    // Antes: SELECT cooldown → eval → UPDATE. Dos crons que arrancaran al mismo
    // tiempo podían pasar el check simultáneamente y disparar la misma regla 2x
    // (pausando campañas duplicado, mandando 2 emails, etc).
    // Ahora: GET_LOCK('auto_eval_<id>', 0) — el segundo cron salta esta regla
    // en este tick. El lock se libera automáticamente al cerrar la conexión.
    $lock_key = "dm_auto_{$auto['id']}";
    $glk = $pdo->prepare("SELECT GET_LOCK(?, 0) AS got");
    $glk->execute([$lock_key]);
    $got_lock = (int)$glk->fetchColumn();
    if (!$got_lock) {
        echo "[engine] Skip {$auto['id']}: otro cron tiene el lock.\n";
        continue;
    }

    // ── Verificar cooldown según frecuencia (con la fila bloqueada lógicamente) ──
    if ($auto['last_triggered_at']) {
        $last    = new DateTime($auto['last_triggered_at']);
        $diff    = $now->getTimestamp() - $last->getTimestamp();
        $min_gap = match($auto['frequency']) {
            'hourly'  => 3600,
            'daily'   => 86400,
            'weekly'  => 604800,
            default   => 86400,
        };
        if ($diff < $min_gap) {
            $pdo->prepare("SELECT RELEASE_LOCK(?)")->execute([$lock_key]);
            continue;
        }
    }

    // ── Verificar ventana horaria de notificación ────────────────────────
    // hourly : solo dentro de la ventana de negocio (p.ej. 8-20h)
    // daily  : solo en la hora exacta configurada (p.ej. las 9)
    // weekly : solo el día y hora exactos configurados
    $in_window = false;
    switch ($auto['frequency']) {
        case 'hourly':
            $in_window = ($hourNow >= $notif_cfg['notif_window_start']
                       && $hourNow <= $notif_cfg['notif_window_end']);
            break;
        case 'daily':
            $in_window = ($hourNow === $notif_cfg['notif_daily_hour']);
            break;
        case 'weekly':
            $in_window = ($dayNow  === $notif_cfg['notif_weekly_day']
                       && $hourNow === $notif_cfg['notif_weekly_hour']);
            break;
    }
    if (!$in_window) {
        $pdo->prepare("SELECT RELEASE_LOCK(?)")->execute([$lock_key]);
        continue;
    }

    // ── Evaluar condición ────────────────────────────────────────────────
    $condition_met = false;
    $detail_msg    = '';

    switch ($auto['condition_type']) {

        case 'meta_spend_zero':
            $days  = max(1, (int)($cparams['days'] ?? 3));
            $since = date('Y-m-d', strtotime("-{$days} days"));
            $mh    = array_filter($h['meta'] ?? [], fn($d) => $d >= $since, ARRAY_FILTER_USE_KEY);
            if (array_sum(array_column($mh, 'spend')) <= 0 && count($mh) > 0) {
                $condition_met = true;
                $detail_msg    = "Meta Ads sin inversión en los últimos {$days} días";
            }
            break;

        case 'meta_campaigns_paused':
            $mh7    = array_filter($h['meta'] ?? [], fn($d) => $d >= $d7, ARRAY_FILTER_USE_KEY);
            $active = array_sum(array_column($mh7, 'campaigns_active'));
            if (count($mh7) > 0 && $active === 0) {
                $condition_met = true;
                $detail_msg    = "Meta Ads: todas las campañas están pausadas";
            }
            break;

        case 'google_is_below':
            $threshold = (float)($cparams['threshold'] ?? 30);
            $gh7       = array_filter($h['google'] ?? [], fn($d) => $d >= $d7, ARRAY_FILTER_USE_KEY);
            $is_vals   = array_filter(
                array_column($gh7, 'search_impression_share'),
                fn($v) => $v !== null && is_numeric($v)
            );
            if (count($is_vals) > 0) {
                $is_avg = array_sum($is_vals) / count($is_vals);
                if ($is_avg < $threshold) {
                    $condition_met = true;
                    $detail_msg    = "Google Ads IS promedio ".round($is_avg,1)."% < umbral {$threshold}%";
                }
            }
            break;

        case 'google_spend_zero':
            $days  = max(1, (int)($cparams['days'] ?? 3));
            $since = date('Y-m-d', strtotime("-{$days} days"));
            $gh    = array_filter($h['google'] ?? [], fn($d) => $d >= $since, ARRAY_FILTER_USE_KEY);
            if (array_sum(array_column($gh, 'cost')) <= 0 && count($gh) > 0) {
                $condition_met = true;
                $detail_msg    = "Google Ads sin inversión en los últimos {$days} días";
            }
            break;

        case 'seo_clicks_drop':
            $threshold = (float)($cparams['threshold'] ?? 20);
            $sh        = $h['gsc'] ?? [];
            ksort($sh);
            $all_dates = array_keys($sh);
            $n         = count($all_dates);
            if ($n >= 4) {
                $mid  = (int)floor($n / 2);
                $prev = array_sum(array_map(fn($d) => (int)($sh[$d]['clicks'] ?? 0), array_slice($all_dates,0,$mid)));
                $curr = array_sum(array_map(fn($d) => (int)($sh[$d]['clicks'] ?? 0), array_slice($all_dates,$mid)));
                if ($prev > 0) {
                    $pct = ($curr - $prev) / $prev * 100;
                    if ($pct <= -$threshold) {
                        $condition_met = true;
                        $detail_msg    = "SEO: caída ".round($pct,1)."% en clics orgánicos (umbral -{$threshold}%)";
                    }
                }
            }
            break;

        case 'semaforo_red':
            $red = [];
            // Meta
            $mh7 = array_filter($h['meta'] ?? [], fn($d) => $d >= $d7, ARRAY_FILTER_USE_KEY);
            if (!empty($mh7)) {
                $sp7 = array_sum(array_column($mh7,'spend'));
                $ac7 = array_sum(array_column($mh7,'campaigns_active'));
                if ($sp7 <= 0 || ($ac7 === 0)) $red[] = 'Meta';
            }
            // Google
            $gh7 = array_filter($h['google'] ?? [], fn($d) => $d >= $d7, ARRAY_FILTER_USE_KEY);
            if (!empty($gh7)) {
                $is_vals = array_filter(array_column($gh7,'search_impression_share'), fn($v) => is_numeric($v));
                $is_avg  = count($is_vals) > 0 ? array_sum($is_vals)/count($is_vals) : null;
                $cost7   = array_sum(array_column($gh7,'cost'));
                if (($is_avg !== null && $is_avg < 30) || ($is_avg === null && $cost7 <= 0)) $red[] = 'Google';
            }
            // SEO
            $sh = $h['gsc'] ?? [];
            ksort($sh);
            $all_d = array_keys($sh);
            $n     = count($all_d);
            if ($n >= 4) {
                $mid  = (int)floor($n/2);
                $prev = array_sum(array_map(fn($d) => (int)($sh[$d]['clicks']??0), array_slice($all_d,0,$mid)));
                $curr = array_sum(array_map(fn($d) => (int)($sh[$d]['clicks']??0), array_slice($all_d,$mid)));
                if ($prev > 0 && ($curr-$prev)/$prev*100 <= -20) $red[] = 'SEO';
            }
            if (!empty($red)) {
                $condition_met = true;
                $detail_msg    = "Semáforo en rojo: ".implode(', ',$red);
            }
            break;

        // ── v2 condiciones ───────────────────────────────────────────────────

        case 'meta_frequency_high':
            $threshold = (float)($cparams['threshold'] ?? 3.0);
            $mh7       = array_filter($h['meta'] ?? [], fn($d) => $d >= $d7, ARRAY_FILTER_USE_KEY);
            $freq_vals = array_filter(array_column($mh7, 'frequency'), fn($v) => $v !== null && $v > 0);
            if (count($freq_vals) > 0) {
                $freq_avg = array_sum($freq_vals) / count($freq_vals);
                if ($freq_avg > $threshold) {
                    $condition_met = true;
                    $detail_msg    = "Meta Ads frecuencia promedio ".round($freq_avg,2)." > umbral {$threshold}";
                }
            }
            break;

        case 'meta_cpa_above':
            $threshold   = (float)($cparams['threshold'] ?? 50);
            $mh7         = array_filter($h['meta'] ?? [], fn($d) => $d >= $d7, ARRAY_FILTER_USE_KEY);
            $total_spend = array_sum(array_column($mh7, 'spend'));
            $total_conv  = array_sum(array_column($mh7, 'conversions'));
            if ($total_spend > 0 && $total_conv > 0) {
                $cpa = $total_spend / $total_conv;
                if ($cpa > $threshold) {
                    $condition_met = true;
                    $detail_msg    = "Meta Ads CPA \$".number_format($cpa,2)." > umbral \${$threshold}";
                }
            }
            break;

        case 'google_cpa_above':
            $threshold  = (float)($cparams['threshold'] ?? 50);
            $gh7        = array_filter($h['google'] ?? [], fn($d) => $d >= $d7, ARRAY_FILTER_USE_KEY);
            $total_cost = array_sum(array_column($gh7, 'cost'));
            $total_conv = array_sum(array_column($gh7, 'conversions'));
            if ($total_cost > 0 && $total_conv > 0) {
                $cpa = $total_cost / $total_conv;
                if ($cpa > $threshold) {
                    $condition_met = true;
                    $detail_msg    = "Google Ads CPA \$".number_format($cpa,2)." > umbral \${$threshold}";
                }
            }
            break;

        case 'google_conversions_zero':
            $days  = max(1, (int)($cparams['days'] ?? 7));
            $since = date('Y-m-d', strtotime("-{$days} days"));
            $gh    = array_filter($h['google'] ?? [], fn($d) => $d >= $since, ARRAY_FILTER_USE_KEY);
            if (count($gh) > 0 && array_sum(array_column($gh, 'conversions')) == 0) {
                $condition_met = true;
                $detail_msg    = "Google Ads sin conversiones en los últimos {$days} días";
            }
            break;

        // ── v3 condiciones ───────────────────────────────────────────────────

        case 'meta_conversions_drop':
            $threshold = (float)($cparams['threshold'] ?? 20);
            $mh7p  = array_filter($h['meta'] ?? [], fn($d) => $d >= $d7,  ARRAY_FILTER_USE_KEY);
            $mhprev= array_filter($h['meta'] ?? [], fn($d) => $d >= $d14 && $d < $d7, ARRAY_FILTER_USE_KEY);
            $curr_mc = array_sum(array_column($mh7p,  'conversions'));
            $prev_mc = array_sum(array_column($mhprev, 'conversions'));
            if ($prev_mc > 0) {
                $pct_mc = ($curr_mc - $prev_mc) / $prev_mc * 100;
                if ($pct_mc <= -$threshold) {
                    $condition_met = true;
                    $detail_msg    = "Meta Ads: conversiones cayeron ".round($pct_mc,1)."% vs semana anterior (umbral -{$threshold}%)";
                }
            }
            break;

        case 'google_conversions_drop':
            $threshold = (float)($cparams['threshold'] ?? 20);
            $gh7p  = array_filter($h['google'] ?? [], fn($d) => $d >= $d7,  ARRAY_FILTER_USE_KEY);
            $ghprev= array_filter($h['google'] ?? [], fn($d) => $d >= $d14 && $d < $d7, ARRAY_FILTER_USE_KEY);
            $curr_gc = array_sum(array_column($gh7p,  'conversions'));
            $prev_gc = array_sum(array_column($ghprev, 'conversions'));
            if ($prev_gc > 0) {
                $pct_gc = ($curr_gc - $prev_gc) / $prev_gc * 100;
                if ($pct_gc <= -$threshold) {
                    $condition_met = true;
                    $detail_msg    = "Google Ads: conversiones cayeron ".round($pct_gc,1)."% vs semana anterior (umbral -{$threshold}%)";
                }
            }
            break;

        case 'email_open_rate_low':
            $threshold = (float)($cparams['threshold'] ?? 15);
            try {
                $cache_stmt = $pdo->prepare(
                    "SELECT data FROM dm_cache WHERE cache_key=? AND expires_at > NOW() LIMIT 1"
                );
                $cache_stmt->execute(["mc_camps_{$cid}"]);
                $cached_em = $cache_stmt->fetchColumn();
                if ($cached_em) {
                    $em_camps = json_decode($cached_em, true) ?? [];
                    $em_rates = array_filter(
                        array_column($em_camps, 'open_rate'),
                        fn($v) => $v !== null && $v > 0
                    );
                    if (count($em_rates) > 0) {
                        $avg_rate = array_sum($em_rates) / count($em_rates) * 100;
                        if ($avg_rate < $threshold) {
                            $condition_met = true;
                            $detail_msg    = "Email Marketing: apertura promedio ".round($avg_rate,1)."% < umbral {$threshold}%";
                        }
                    }
                }
            } catch (Exception $e) { /* dm_cache no existe o sin datos */ }
            break;
    }

    if (!$condition_met) {
        $pdo->prepare("SELECT RELEASE_LOCK(?)")->execute([$lock_key]);
        continue;
    }

    // ── Ejecutar acción ──────────────────────────────────────────────────
    $action_ok     = false;
    $action_detail = '';

    switch ($auto['action_type']) {

        case 'send_email':
            $to      = defined('ALERT_EMAIL') ? ALERT_EMAIL : 'davidcalderon107@gmail.com';
            if ($digest_mode) {
                // En modo digest: acumular, no enviar individualmente
                $digest_alerts[] = [
                    'name'   => $auto['name'],
                    'client' => $auto['client_name'],
                    'detail' => $detail_msg,
                ];
                $action_ok     = true;
                $action_detail = "Email encolado para digest diario → {$to}";
            } else {
                $subject = "[SuperCerebro] ⚠ {$auto['name']} — {$auto['client_name']}";
                $body    = "Automatización activada: {$auto['name']}\n"
                         . "Cliente: {$auto['client_name']}\n\n"
                         . "Condición detectada:\n{$detail_msg}\n\n"
                         . "Dashboard: https://dimark.co/supercerebro/\n"
                         . "Generado: ".date('Y-m-d H:i')."\n";
                $hdrs    = "From: SuperCerebro <noreply@dimark.co>\r\nContent-Type: text/plain; charset=UTF-8";
                $sent    = mail($to, $subject, $body, $hdrs);
                $action_ok     = $sent;
                $action_detail = "Email ".($sent?'enviado':'FALLÓ')." → {$to}";
            }
            break;

        case 'log_only':
            $action_ok     = true;
            $action_detail = 'Solo log — sin acción adicional';
            break;

        case 'pause_campaign_meta':
            // FASE 4 FIX: re-check kill switch antes de mutación REAL
            if (dm_auto_kill_switch_active()) {
                $action_ok = false; $action_detail = 'Kill switch activado mid-flight — pausa Meta abortada'; break;
            }
            $mc = dm_get_credentials($cid, 'meta');
            $mac_id = $mc['ad_account_id'] ?? '';
            $mat    = $mc['access_token'] ?? (defined('META_SYSTEM_TOKEN') ? META_SYSTEM_TOKEN : '');
            if (!$mac_id || !$mat) {
                $action_ok = false; $action_detail = 'Sin credenciales Meta'; break;
            }
            if (strpos($mac_id,'act_')!==0) $mac_id = 'act_'.$mac_id;
            // Fetch active campaigns
            $ch = curl_init("https://graph.facebook.com/v19.0/{$mac_id}/campaigns?".http_build_query([
                'effective_status'=>json_encode(['ACTIVE']), 'fields'=>'id,name', 'limit'=>100, 'access_token'=>$mat
            ]));
            curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>20,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2]);
            $mc_camps = json_decode(curl_exec($ch),true)['data'] ?? []; curl_close($ch);
            $paused_meta = 0;
            foreach ($mc_camps as $camp) {
                $ch2 = curl_init("https://graph.facebook.com/v19.0/{$camp['id']}");
                curl_setopt_array($ch2,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_TIMEOUT=>15,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2,
                    CURLOPT_POSTFIELDS=>http_build_query(['status'=>'PAUSED','access_token'=>$mat])]);
                $r2 = json_decode(curl_exec($ch2),true); curl_close($ch2);
                if (!empty($r2['success'])) $paused_meta++;
            }
            $action_ok     = true;
            $action_detail = "Meta: {$paused_meta} campaña(s) pausada(s) de ".count($mc_camps)." activas";
            break;

        case 'activate_campaigns_meta':
            // FASE 8 FIX: kill switch in-flight
            if (dm_auto_kill_switch_active()) {
                $action_ok = false; $action_detail = 'Kill switch activado mid-flight — activación Meta abortada'; break;
            }
            $mc2    = dm_get_credentials($cid, 'meta');
            $mac_id2 = $mc2['ad_account_id'] ?? '';
            $mat2    = $mc2['access_token'] ?? (defined('META_SYSTEM_TOKEN') ? META_SYSTEM_TOKEN : '');
            if (!$mac_id2 || !$mat2) {
                $action_ok = false; $action_detail = 'Sin credenciales Meta'; break;
            }
            if (strpos($mac_id2,'act_')!==0) $mac_id2 = 'act_'.$mac_id2;
            $ch = curl_init("https://graph.facebook.com/v19.0/{$mac_id2}/campaigns?".http_build_query([
                'effective_status'=>json_encode(['PAUSED']), 'fields'=>'id,name', 'limit'=>100, 'access_token'=>$mat2
            ]));
            curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>20,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2]);
            $paused_camps = json_decode(curl_exec($ch),true)['data'] ?? []; curl_close($ch);
            $activated = 0;
            foreach ($paused_camps as $camp2) {
                $ch2 = curl_init("https://graph.facebook.com/v19.0/{$camp2['id']}");
                curl_setopt_array($ch2,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_TIMEOUT=>15,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2,
                    CURLOPT_POSTFIELDS=>http_build_query(['status'=>'ACTIVE','access_token'=>$mat2])]);
                $r2 = json_decode(curl_exec($ch2),true); curl_close($ch2);
                if (!empty($r2['success'])) $activated++;
            }
            $action_ok     = true;
            $action_detail = "Meta: {$activated} campaña(s) activada(s) de ".count($paused_camps)." pausadas";
            break;

        case 'change_budget_meta':
            // FASE 8 FIX: kill switch in-flight
            if (dm_auto_kill_switch_active()) {
                $action_ok = false; $action_detail = 'Kill switch activado mid-flight — cambio budget Meta abortado'; break;
            }
            $pct3   = max(1, min(80, (int)($aparams['reduction_pct'] ?? 20)));
            $mc3    = dm_get_credentials($cid, 'meta');
            $mac_id3 = $mc3['ad_account_id'] ?? '';
            $mat3    = $mc3['access_token'] ?? (defined('META_SYSTEM_TOKEN') ? META_SYSTEM_TOKEN : '');
            if (!$mac_id3 || !$mat3) {
                $action_ok = false; $action_detail = 'Sin credenciales Meta'; break;
            }
            if (strpos($mac_id3,'act_')!==0) $mac_id3 = 'act_'.$mac_id3;
            $ch = curl_init("https://graph.facebook.com/v19.0/{$mac_id3}/campaigns?".http_build_query([
                'effective_status'=>json_encode(['ACTIVE']), 'fields'=>'id,name,daily_budget', 'limit'=>100, 'access_token'=>$mat3
            ]));
            curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>20,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2]);
            $active_camps3 = json_decode(curl_exec($ch),true)['data'] ?? []; curl_close($ch);
            $budget_updated = 0;
            foreach ($active_camps3 as $camp3) {
                $current_budget = (int)($camp3['daily_budget'] ?? 0);
                if ($current_budget <= 0) continue;
                $new_budget = max(100, (int)floor($current_budget * (1 - $pct3/100)));
                $ch2 = curl_init("https://graph.facebook.com/v19.0/{$camp3['id']}");
                curl_setopt_array($ch2,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_TIMEOUT=>15,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2,
                    CURLOPT_POSTFIELDS=>http_build_query(['daily_budget'=>$new_budget,'access_token'=>$mat3])]);
                $r2 = json_decode(curl_exec($ch2),true); curl_close($ch2);
                if (!empty($r2['success'])) $budget_updated++;
            }
            $action_ok     = true;
            $action_detail = "Meta: presupuesto reducido {$pct3}% en {$budget_updated} campaña(s)";
            break;

        case 'pause_campaign_google':
            // FASE 4 FIX: re-check kill switch antes de mutación REAL
            if (dm_auto_kill_switch_active()) {
                $action_ok = false; $action_detail = 'Kill switch activado mid-flight — pausa Google abortada'; break;
            }
            $gc        = dm_get_credentials($cid, 'google');
            $g_cust    = preg_replace('/[^0-9]/','', $gc['customer_id'] ?? (defined('GOOGLE_CUSTOMER_ID')?GOOGLE_CUSTOMER_ID:''));
            $g_mcc     = preg_replace('/[^0-9]/','', $gc['mcc_id']      ?? (defined('GOOGLE_MCC_ID')?GOOGLE_MCC_ID:''));
            $g_dev     = defined('GOOGLE_DEV_TOKEN') ? GOOGLE_DEV_TOKEN : '';
            if (!$g_cust) {
                $action_ok = false; $action_detail = 'Sin Customer ID de Google Ads'; break;
            }
            $g_at = dm_google_access_token();
            if (!$g_at) {
                $action_ok = false; $action_detail = 'No se pudo obtener token de Google'; break;
            }
            $gh = ['Content-Type: application/json',"Authorization: Bearer {$g_at}","developer-token: {$g_dev}"];
            if ($g_mcc && $g_mcc !== $g_cust) $gh[] = "login-customer-id: {$g_mcc}";
            // Fetch ENABLED campaigns
            $ch = curl_init("https://googleads.googleapis.com/v22/customers/{$g_cust}/googleAds:search");
            curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2,
                CURLOPT_TIMEOUT=>20,CURLOPT_HTTPHEADER=>$gh,
                CURLOPT_POSTFIELDS=>json_encode(['query'=>"SELECT campaign.id,campaign.resource_name FROM campaign WHERE campaign.status='ENABLED' LIMIT 100"])]);
            $raw_g = curl_exec($ch); $http_g = curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
            if ($http_g !== 200) {
                $action_ok = false; $action_detail = "Google Ads: error al obtener campañas (HTTP {$http_g})"; break;
            }
            $g_camps = json_decode($raw_g,true)['results'] ?? [];
            if (empty($g_camps)) {
                $action_ok = true; $action_detail = 'Google Ads: no hay campañas activas para pausar'; break;
            }
            $ops = [];
            foreach ($g_camps as $gc_row)
                $ops[] = ['update'=>['resourceName'=>$gc_row['campaign']['resourceName'],'status'=>'PAUSED'],'updateMask'=>'status'];
            $ch2 = curl_init("https://googleads.googleapis.com/v22/customers/{$g_cust}/campaigns:mutate");
            curl_setopt_array($ch2,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2,
                CURLOPT_TIMEOUT=>20,CURLOPT_HTTPHEADER=>$gh,CURLOPT_POSTFIELDS=>json_encode(['operations'=>$ops])]);
            $raw_g2 = curl_exec($ch2); $http_g2 = curl_getinfo($ch2,CURLINFO_HTTP_CODE); curl_close($ch2);
            if ($http_g2 !== 200) {
                $err_g = json_decode($raw_g2,true);
                $action_ok = false; $action_detail = "Google Ads: error al pausar: ".($err_g['error']['message']??"HTTP {$http_g2}"); break;
            }
            $action_ok     = true;
            $action_detail = "Google Ads: ".count($g_camps)." campaña(s) pausada(s)";
            break;

        // ── v3 acciones Google presupuesto ──────────────────────────────────
        case 'change_budget_google':
        case 'increase_budget_google':
            // FASE 4 FIX: re-check kill switch antes de mutación REAL
            if (dm_auto_kill_switch_active()) {
                $action_ok = false; $action_detail = 'Kill switch activado mid-flight — cambio budget Google abortado'; break;
            }
            $pct_bg      = max(1, min(80, (int)($aparams['reduction_pct'] ?? 20)));
            $is_inc_bg   = ($auto['action_type'] === 'increase_budget_google');
            $gc_bg       = dm_get_credentials($cid, 'google');
            $g_cust_bg   = preg_replace('/[^0-9]/','', $gc_bg['customer_id'] ?? (defined('GOOGLE_CUSTOMER_ID')?GOOGLE_CUSTOMER_ID:''));
            $g_mcc_bg    = preg_replace('/[^0-9]/','', $gc_bg['mcc_id']      ?? (defined('GOOGLE_MCC_ID')?GOOGLE_MCC_ID:''));
            $g_dev_bg    = defined('GOOGLE_DEV_TOKEN') ? GOOGLE_DEV_TOKEN : '';
            if (!$g_cust_bg) { $action_ok=false; $action_detail='Sin Customer ID Google Ads'; break; }
            $g_at_bg = dm_google_access_token();
            if (!$g_at_bg) { $action_ok=false; $action_detail='No se pudo obtener token Google'; break; }
            $ghdrs_bg = ['Content-Type: application/json',"Authorization: Bearer {$g_at_bg}","developer-token: {$g_dev_bg}"];
            if ($g_mcc_bg && $g_mcc_bg !== $g_cust_bg) $ghdrs_bg[] = "login-customer-id: {$g_mcc_bg}";
            // Fetch active campaigns con budget resource y amount_micros
            $q_bg = "SELECT campaign.id, campaign_budget.resource_name, campaign_budget.amount_micros FROM campaign WHERE campaign.status='ENABLED' LIMIT 100";
            $ch = curl_init("https://googleads.googleapis.com/v22/customers/{$g_cust_bg}/googleAds:search");
            curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2,
                CURLOPT_TIMEOUT=>20,CURLOPT_HTTPHEADER=>$ghdrs_bg,CURLOPT_POSTFIELDS=>json_encode(['query'=>$q_bg])]);
            $raw_bg = curl_exec($ch); $http_bg = curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
            if ($http_bg !== 200) { $action_ok=false; $action_detail="Google: error al obtener campañas (HTTP {$http_bg})"; break; }
            $g_camps_bg = json_decode($raw_bg,true)['results'] ?? [];
            // Deduplicar por resource_name (varias campañas pueden compartir presupuesto)
            $budgets_seen = [];
            $ops_bg       = [];
            foreach ($g_camps_bg as $gc_row_bg) {
                $brn = $gc_row_bg['campaignBudget']['resourceName'] ?? '';
                $bam = (int)($gc_row_bg['campaignBudget']['amountMicros'] ?? 0);
                if (!$brn || isset($budgets_seen[$brn]) || $bam <= 0) continue;
                $budgets_seen[$brn] = true;
                $factor_bg  = $is_inc_bg ? (1 + $pct_bg/100) : (1 - $pct_bg/100);
                $new_micros = max(1000000, (int)round($bam * $factor_bg)); // mínimo $1
                $ops_bg[]   = ['update'=>['resourceName'=>$brn,'amountMicros'=>$new_micros],'updateMask'=>'amount_micros'];
            }
            if (empty($ops_bg)) { $action_ok=true; $action_detail='Google: sin campañas activas con presupuesto ajustable'; break; }
            $ch2 = curl_init("https://googleads.googleapis.com/v22/customers/{$g_cust_bg}/campaignBudgets:mutate");
            curl_setopt_array($ch2,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2,
                CURLOPT_TIMEOUT=>20,CURLOPT_HTTPHEADER=>$ghdrs_bg,CURLOPT_POSTFIELDS=>json_encode(['operations'=>$ops_bg])]);
            $raw_bg2 = curl_exec($ch2); $http_bg2 = curl_getinfo($ch2,CURLINFO_HTTP_CODE); curl_close($ch2);
            if ($http_bg2 !== 200) {
                $err_bg = json_decode($raw_bg2,true);
                $action_ok=false; $action_detail="Google: error ".($err_bg['error']['message']??"HTTP {$http_bg2}"); break;
            }
            $action_ok     = true;
            $verb_bg       = $is_inc_bg ? 'aumentado' : 'reducido';
            $action_detail = "Google Ads: presupuesto {$verb_bg} {$pct_bg}% en ".count($ops_bg)." presupuesto(s)";
            break;

        // ── v3 acción Meta aumento presupuesto ──────────────────────────────
        case 'increase_budget_meta':
            // FASE 4 FIX: re-check kill switch antes de mutación REAL
            if (dm_auto_kill_switch_active()) {
                $action_ok = false; $action_detail = 'Kill switch activado mid-flight — cambio budget Meta abortado'; break;
            }
            $pct_im  = max(1, min(80, (int)($aparams['reduction_pct'] ?? 20)));
            $mc_im   = dm_get_credentials($cid, 'meta');
            $mac_im  = $mc_im['ad_account_id'] ?? '';
            $mat_im  = $mc_im['access_token'] ?? (defined('META_SYSTEM_TOKEN') ? META_SYSTEM_TOKEN : '');
            if (!$mac_im || !$mat_im) { $action_ok=false; $action_detail='Sin credenciales Meta'; break; }
            if (strpos($mac_im,'act_')!==0) $mac_im = 'act_'.$mac_im;
            $ch = curl_init("https://graph.facebook.com/v19.0/{$mac_im}/campaigns?".http_build_query([
                'effective_status'=>json_encode(['ACTIVE']),'fields'=>'id,name,daily_budget','limit'=>100,'access_token'=>$mat_im
            ]));
            curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>20,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2]);
            $camps_im = json_decode(curl_exec($ch),true)['data'] ?? []; curl_close($ch);
            $increased = 0;
            foreach ($camps_im as $camp_im) {
                $cur_b = (int)($camp_im['daily_budget'] ?? 0);
                if ($cur_b <= 0) continue;
                $new_b = (int)floor($cur_b * (1 + $pct_im/100));
                $ch2 = curl_init("https://graph.facebook.com/v19.0/{$camp_im['id']}");
                curl_setopt_array($ch2,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_TIMEOUT=>15,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2,
                    CURLOPT_POSTFIELDS=>http_build_query(['daily_budget'=>$new_b,'access_token'=>$mat_im])]);
                $r2 = json_decode(curl_exec($ch2),true); curl_close($ch2);
                if (!empty($r2['success'])) $increased++;
            }
            $action_ok     = true;
            $action_detail = "Meta: presupuesto aumentado {$pct_im}% en {$increased} campaña(s)";
            break;

        // ── v3 acción Google activar campañas ───────────────────────────────
        case 'activate_campaigns_google':
            // FASE 8 FIX: kill switch in-flight
            if (dm_auto_kill_switch_active()) {
                $action_ok = false; $action_detail = 'Kill switch activado mid-flight — activación Google abortada'; break;
            }
            $gc_ag    = dm_get_credentials($cid, 'google');
            $g_cust_ag= preg_replace('/[^0-9]/','', $gc_ag['customer_id'] ?? (defined('GOOGLE_CUSTOMER_ID')?GOOGLE_CUSTOMER_ID:''));
            $g_mcc_ag = preg_replace('/[^0-9]/','', $gc_ag['mcc_id']      ?? (defined('GOOGLE_MCC_ID')?GOOGLE_MCC_ID:''));
            $g_dev_ag = defined('GOOGLE_DEV_TOKEN') ? GOOGLE_DEV_TOKEN : '';
            if (!$g_cust_ag) { $action_ok=false; $action_detail='Sin Customer ID Google Ads'; break; }
            $g_at_ag = dm_google_access_token();
            if (!$g_at_ag) { $action_ok=false; $action_detail='No se pudo obtener token Google'; break; }
            $ghdrs_ag = ['Content-Type: application/json',"Authorization: Bearer {$g_at_ag}","developer-token: {$g_dev_ag}"];
            if ($g_mcc_ag && $g_mcc_ag !== $g_cust_ag) $ghdrs_ag[] = "login-customer-id: {$g_mcc_ag}";
            $ch = curl_init("https://googleads.googleapis.com/v22/customers/{$g_cust_ag}/googleAds:search");
            curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2,
                CURLOPT_TIMEOUT=>20,CURLOPT_HTTPHEADER=>$ghdrs_ag,
                CURLOPT_POSTFIELDS=>json_encode(['query'=>"SELECT campaign.id,campaign.resource_name FROM campaign WHERE campaign.status='PAUSED' LIMIT 100"])]);
            $raw_ag = curl_exec($ch); $http_ag = curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch);
            if ($http_ag !== 200) { $action_ok=false; $action_detail="Google: error al obtener campañas (HTTP {$http_ag})"; break; }
            $paused_ag = json_decode($raw_ag,true)['results'] ?? [];
            if (empty($paused_ag)) { $action_ok=true; $action_detail='Google: no hay campañas pausadas para activar'; break; }
            $ops_ag = [];
            foreach ($paused_ag as $gr_ag)
                $ops_ag[] = ['update'=>['resourceName'=>$gr_ag['campaign']['resourceName'],'status'=>'ENABLED'],'updateMask'=>'status'];
            $ch2 = curl_init("https://googleads.googleapis.com/v22/customers/{$g_cust_ag}/campaigns:mutate");
            curl_setopt_array($ch2,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2,
                CURLOPT_TIMEOUT=>20,CURLOPT_HTTPHEADER=>$ghdrs_ag,CURLOPT_POSTFIELDS=>json_encode(['operations'=>$ops_ag])]);
            $raw_ag2 = curl_exec($ch2); $http_ag2 = curl_getinfo($ch2,CURLINFO_HTTP_CODE); curl_close($ch2);
            if ($http_ag2 !== 200) {
                $err_ag = json_decode($raw_ag2,true);
                $action_ok=false; $action_detail="Google: error ".($err_ag['error']['message']??"HTTP {$http_ag2}"); break;
            }
            $action_ok     = true;
            $action_detail = "Google Ads: ".count($paused_ag)." campaña(s) activada(s)";
            break;

        default:
            $action_ok     = false;
            $action_detail = "Acción desconocida: {$auto['action_type']}";
    }

    // ── Registrar en dm_automation_log ───────────────────────────────────
    $log_detail = $detail_msg.' | Acción: '.$action_detail;
    $pdo->prepare("
        INSERT INTO dm_automation_log (automation_id, client_id, result, detail)
        VALUES (?, ?, ?, ?)
    ")->execute([$auto['id'], $cid, $action_ok ? 'triggered' : 'error', $log_detail]);

    $pdo->prepare("
        UPDATE dm_automations
           SET last_triggered_at = NOW(), trigger_count = trigger_count + 1
         WHERE id = ?
    ")->execute([$auto['id']]);

    echo "[automations-engine] [{$auto['client_name']}] {$auto['name']}: {$log_detail}\n";
    $triggered_total++;

    // FASE 4 FIX: liberar advisory lock al cerrar el ciclo
    $pdo->prepare("SELECT RELEASE_LOCK(?)")->execute([$lock_key]);
}

// ── Enviar email digest si aplica ────────────────────────────────────────────
if ($digest_mode && !empty($digest_alerts)) {
    $to      = defined('ALERT_EMAIL') ? ALERT_EMAIL : 'davidcalderon107@gmail.com';
    $subject = "[SuperCerebro] 📋 Resumen de alertas — ".date('d/m/Y');
    $body    = "Resumen de automatizaciones activadas hoy:\n\n";
    foreach ($digest_alerts as $i => $al) {
        $body .= ($i+1).". [{$al['client']}] {$al['name']}\n"
               . "   → {$al['detail']}\n\n";
    }
    $body .= "Dashboard: https://dimark.co/supercerebro/\n"
           . "Generado: ".date('Y-m-d H:i')."\n";
    $hdrs = "From: SuperCerebro <noreply@dimark.co>\r\nContent-Type: text/plain; charset=UTF-8";
    $sent = mail($to, $subject, $body, $hdrs);
    echo "[automations-engine] Digest ".count($digest_alerts)." alerta(s) ".($sent?'enviado':'FALLÓ')." → {$to}\n";
}

echo "[automations-engine] Ciclo completado — {$triggered_total} acción(es) ejecutada(s).\n";
