<?php
/**
 * run_institutional_alerts.php
 * ------------------------------------------------------------
 * Self-healing live alert worker with live-quote prefetch and staged institutional flow.
 *
 * Production order:
 * 1. Lock worker to avoid overlapping cron executions.
 * 2. Run alert-aware candle sync for active tracked setups only.
 * 3. Run AlertService::monitorActiveAlerts().
 *
 * The sync preflight keeps tracked setup candles fresh without broad API usage.
 */

ini_set('display_errors', PHP_SAPI === 'cli' ? '1' : '0');
error_reporting(E_ALL);
date_default_timezone_set('UTC');

$startedAt = microtime(true);
$lockFile = sys_get_temp_dir() . '/institutional_alert_worker_self_healing.lock';
$lockHandle = fopen($lockFile, 'c');

if ($lockHandle && !flock($lockHandle, LOCK_EX | LOCK_NB)) {
    $payload = [
        'ok' => true,
        'skipped' => true,
        'message' => 'Alert worker already running; skipped overlapping execution.',
        'time' => date('Y-m-d H:i:s'),
    ];
    if (PHP_SAPI !== 'cli' && !headers_sent()) header('Content-Type: application/json');
    echo json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . (PHP_SAPI === 'cli' ? PHP_EOL : '');
    exit;
}

/*
 * Tell services that alert worker itself must not perform broad market-data sync.
 * Candle freshness is handled by the alert-aware sync preflight below.
 */
define('ALERT_WORKER_RUNNING', true);
define('ALERT_WORKER_NO_BROAD_MARKET_API', true);

require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../services/AlertService.php';
if (file_exists(__DIR__ . '/../services/LiveQuoteService.php')) {
    require_once __DIR__ . '/../services/LiveQuoteService.php';
}

function alert_worker_setting($key, $default = '') {
    try {
        $stmt = db()->prepare("SELECT setting_value FROM system_settings WHERE setting_key = ? LIMIT 1");
        $stmt->execute([$key]);
        $value = $stmt->fetchColumn();
        return $value !== false ? (string)$value : $default;
    } catch (Throwable $e) {
        return $default;
    }
}

function alert_worker_setting_enabled($key, $default = true) {
    $value = strtolower(trim(alert_worker_setting($key, $default ? '1' : '0')));
    return in_array($value, ['1','true','yes','on','enabled'], true);
}

function alert_worker_throttle_file(string $name): string {
    $safe = preg_replace('/[^a-z0-9_\-]/i', '_', $name);
    return sys_get_temp_dir() . '/trading_tool_' . $safe . '.last_run';
}

function alert_worker_throttle_allowed(string $name, int $minSeconds): array {
    $minSeconds = max(0, $minSeconds);
    if ($minSeconds <= 0) {
        return ['allowed' => true, 'reason' => 'No throttle configured.'];
    }

    $file = alert_worker_throttle_file($name);
    $last = is_file($file) ? (int)trim((string)@file_get_contents($file)) : 0;
    $age = $last > 0 ? time() - $last : PHP_INT_MAX;

    if ($last > 0 && $age < $minSeconds) {
        return [
            'allowed' => false,
            'reason' => 'Credit-safe throttle active. Last run was ' . $age . 's ago; minimum is ' . $minSeconds . 's.',
            'age_seconds' => $age,
            'min_seconds' => $minSeconds,
        ];
    }

    @file_put_contents($file, (string)time());
    return ['allowed' => true, 'age_seconds' => $age, 'min_seconds' => $minSeconds];
}

function alert_worker_run_sync_preflight(): array {
    /*
     * Credit-safe default: this worker runs every minute, while the platform
     * already has a separate 5-minute alert-aware candle-sync cron. Running
     * candle sync inside this 1-minute worker can quickly burn FMP/TwelveData
     * requests, especially when several alerts are active. Keep this as an
     * emergency/self-healing fallback only when explicitly enabled.
     */
    if (!alert_worker_setting_enabled('alert_worker_pre_sync_enabled', false)) {
        return ['enabled' => false, 'message' => 'Credit-safe mode: alert-worker pre-sync is disabled. Use the 5-minute sync_candles cron for candle freshness.'];
    }

    $minSeconds = (int)alert_worker_setting('alert_worker_pre_sync_min_seconds', '300');
    $throttle = alert_worker_throttle_allowed('alert_worker_pre_sync', $minSeconds);
    if (empty($throttle['allowed'])) {
        return ['enabled' => true, 'skipped' => true, 'message' => $throttle['reason'] ?? 'Pre-sync throttled.', 'throttle' => $throttle];
    }

    $syncFile = __DIR__ . '/sync_candles.php';
    if (!is_file($syncFile)) {
        return ['enabled' => true, 'ok' => false, 'message' => 'sync_candles.php not found.'];
    }

    $php = PHP_BINARY ?: '/usr/local/bin/ea-php83';
    $limit = (int)alert_worker_setting('alert_worker_pre_sync_limit', '10');
    $limit = max(5, min(50, $limit));
    $maxPairs = (int)alert_worker_setting('alert_worker_pre_sync_max_pairs', '8');
    $maxPairs = max(1, min(30, $maxPairs));

    $cmd = escapeshellarg($php)
        . ' ' . escapeshellarg($syncFile)
        . ' live --alert-aware=1 --limit=' . (int)$limit
        . ' --max-pairs=' . (int)$maxPairs
        . ' 2>&1';

    $output = '';
    $exitCode = 0;

    if (function_exists('exec')) {
        $lines = [];
        @exec($cmd, $lines, $exitCode);
        $output = implode("\n", $lines);
    } else {
        return ['enabled' => true, 'ok' => false, 'message' => 'PHP exec() is disabled, so pre-sync could not run.'];
    }

    return [
        'enabled' => true,
        'ok' => $exitCode === 0,
        'exit_code' => $exitCode,
        'command' => $cmd,
        'output_preview' => substr($output, 0, 4000),
    ];
}


function alert_worker_prefetch_live_quotes(): array {
    /*
     * Credit-safe default: do not prefetch quotes every minute. AlertService
     * can request a quote only when it evaluates a setup, and LiveQuoteService
     * has its own cache. Prefetch remains available when explicitly enabled.
     */
    if (!alert_worker_setting_enabled('alert_worker_prefetch_live_quotes', false)) {
        return ['enabled' => false, 'message' => 'Credit-safe mode: live quote prefetch is disabled. Quotes are fetched on demand and cached.'];
    }

    $minSeconds = (int)alert_worker_setting('alert_worker_live_quote_prefetch_min_seconds', '180');
    $throttle = alert_worker_throttle_allowed('alert_worker_live_quote_prefetch', $minSeconds);
    if (empty($throttle['allowed'])) {
        return ['enabled' => true, 'skipped' => true, 'message' => $throttle['reason'] ?? 'Live quote prefetch throttled.', 'throttle' => $throttle];
    }

    if (!class_exists('LiveQuoteService') || !method_exists('LiveQuoteService', 'prefetchActiveAlertQuotes')) {
        return ['enabled' => true, 'ok' => false, 'message' => 'LiveQuoteService is not available.'];
    }

    $maxAlerts = (int)alert_worker_setting('alert_worker_live_quote_max_alerts', '10');
    $maxAlerts = max(1, min(50, $maxAlerts));

    try {
        $result = LiveQuoteService::prefetchActiveAlertQuotes($maxAlerts);
        $result['enabled'] = true;
        return $result;
    } catch (Throwable $e) {
        return ['enabled' => true, 'ok' => false, 'message' => 'Live quote prefetch failed: ' . $e->getMessage()];
    }
}


try {
    if (method_exists('AlertService', 'ensureTables')) {
        AlertService::ensureTables();
    }

    $preSync = alert_worker_run_sync_preflight();
    $liveQuotePrefetch = alert_worker_prefetch_live_quotes();

    $cleanup = method_exists('AlertService', 'cleanupExpiredAlerts')
        ? AlertService::cleanupExpiredAlerts()
        : ['checked' => 0, 'expired' => 0];

    $results = AlertService::monitorActiveAlerts();

    $summary = [
        'total' => count($results),
        'ready' => 0,
        'sent' => 0,
        'tracking' => 0,
        'zone_approached' => 0,
        'liquidity_swept' => 0,
        'structure_confirmed' => 0,
        'expired' => 0,
    ];

    foreach ($results as $row) {
        $stage = strtoupper((string)($row['stage'] ?? ''));
        if (!empty($row['ready'])) $summary['ready']++;
        if (!empty($row['telegram']) || !empty($row['email']) || !empty($row['sent'])) $summary['sent']++;
        if ($stage === 'TRACKING') $summary['tracking']++;
        elseif ($stage === 'ZONE_APPROACHED') $summary['zone_approached']++;
        elseif ($stage === 'LIQUIDITY_SWEPT') $summary['liquidity_swept']++;
        elseif ($stage === 'STRUCTURE_CONFIRMED') $summary['structure_confirmed']++;
        elseif ($stage === 'EXPIRED') $summary['expired']++;
    }

    $payload = [
        'ok' => true,
        'message' => 'Self-healing alert worker executed.',
        'pre_sync' => $preSync,
        'live_quote_prefetch' => $liveQuotePrefetch,
        'cleanup' => $cleanup,
        'checked' => count($results),
        'summary' => $summary,
        'results' => $results,
        'duration_seconds' => round(microtime(true) - $startedAt, 4),
        'time' => date('Y-m-d H:i:s')
    ];

    if (PHP_SAPI !== 'cli' && !headers_sent()) header('Content-Type: application/json');
    echo json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . (PHP_SAPI === 'cli' ? PHP_EOL : '');
} catch (Throwable $e) {
    error_log('[alert_worker_self_healing] ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());

    $payload = [
        'ok' => false,
        'error' => $e->getMessage(),
        'file' => $e->getFile(),
        'line' => $e->getLine(),
        'duration_seconds' => round(microtime(true) - $startedAt, 4),
        'time' => date('Y-m-d H:i:s')
    ];

    if (PHP_SAPI !== 'cli' && !headers_sent()) header('Content-Type: application/json');
    echo json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . (PHP_SAPI === 'cli' ? PHP_EOL : '');
}

if ($lockHandle) {
    flock($lockHandle, LOCK_UN);
    fclose($lockHandle);
}
