<?php
/**
 * Diagnóstico de salud por sensor (job batch, sin LLM).
 *
 * - Lee últimas N lecturas por sensor activo.
 * - Aplica reglas: gaps, flatline, drift, noise.
 * - Genera filas en `ai_insights` con type='health'.
 *
 * Programar en cron cada 30 min:
 *   *\/30 * * * * php /ruta/bin/ai_health_sensors >> logs/ai.log 2>&1
 */

if (php_sapi_name() !== 'cli') {
    die("Este script se debe ejecutar por consola.\n");
}

define('BASE_PATH', dirname(__DIR__) . DIRECTORY_SEPARATOR);

require BASE_PATH . 'core/helpers/Env.php';
Env::load(BASE_PATH . '.env');

require BASE_PATH . 'app/config/Config.php';
require BASE_PATH . 'app/config/ConfigEnv.php';

if (file_exists(BASE_PATH . 'vendor/autoload.php')) {
    require BASE_PATH . 'vendor/autoload.php';
}

if (class_exists('ConfigEnv') && method_exists('ConfigEnv', 'applyEnvironmentOverrides')) {
    ConfigEnv::applyEnvironmentOverrides();
}

use Klee\Core\Ai\HealthAnalyzer;

// ---- Conexión PDO ----------------------------------------------------------
try {
    $connection = ConfigEnv::getConnectionConfig('klee');
    $dsn = "mysql:host={$connection['host']};dbname={$connection['dbname']};charset=utf8mb4";
    $pdo = new PDO($dsn, $connection['user'], $connection['password'], array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    ));
} catch (Exception $e) {
    fwrite(STDERR, "[ai_health_sensors] Error conexión BD: " . $e->getMessage() . "\n");
    exit(1);
}

// ---- Parámetros ------------------------------------------------------------
$WINDOW_POINTS = (int)(getenv('AI_HEALTH_WINDOW') ?: 288);   // 24h @ 5min
$EXPECTED      = (int)(getenv('AI_HEALTH_EXPECTED') ?: 288); // esperadas en ventana
$GAP_PCT       = (float)(getenv('AI_HEALTH_GAP_PCT') ?: 0.20);
$DRIFT_REL     = (float)(getenv('AI_HEALTH_DRIFT_REL') ?: 0.30);
$NOISE_RATIO   = (float)(getenv('AI_HEALTH_NOISE_RATIO') ?: 2.5);
$tenantId      = (int)(getenv('TENANT_ID') ?: 1);

$analyzer = new HealthAnalyzer($GAP_PCT, 1e-6, $DRIFT_REL, $NOISE_RATIO, 30);

// ---- Sensores activos ------------------------------------------------------
$sensores = $pdo->query(
    "SELECT Id, IdEstacion, Codigo, Nombre, Variable, Unidad
     FROM sensores WHERE Estado = 1"
)->fetchAll(PDO::FETCH_ASSOC);

$lecturasStmt = $pdo->prepare(
    "SELECT Valor, Fecha, Hora FROM lecturas
     WHERE IdEstacion = :est AND IdSensor = :sen
     ORDER BY Fecha DESC, Hora DESC
     LIMIT :lim"
);

$totalSensores = count($sensores);
$totalFindings = 0;
$totalInsertados = 0;
$saltadosPocaData = 0;

foreach ($sensores as $sensor) {
    $lecturasStmt->bindValue(':est', (int)$sensor['IdEstacion'], PDO::PARAM_INT);
    $lecturasStmt->bindValue(':sen', (int)$sensor['Id'], PDO::PARAM_INT);
    $lecturasStmt->bindValue(':lim', $WINDOW_POINTS, PDO::PARAM_INT);
    $lecturasStmt->execute();
    $rows = $lecturasStmt->fetchAll(PDO::FETCH_ASSOC);

    if (empty($rows)) {
        // Sin datos: registrar como gap crítico.
        $title = sprintf('Sensor sin lecturas en la ventana actual (%s)', $sensor['Nombre'] ?: $sensor['Codigo']);
        $msg = sprintf('No se recibieron lecturas en las últimas %d ranuras esperadas.', $EXPECTED);
        try {
            $id = AiInsightsModel::createIfNotExists(array(
                'TenantId' => $tenantId,
                'Type' => 'health',
                'EntityType' => 'sensor',
                'EntityId' => (int)$sensor['Id'],
                'Score' => 1.0,
                'Severity' => 'critical',
                'Title' => $title,
                'Message' => $msg,
                'Payload' => array(
                    'finding' => 'gaps',
                    'sensor' => $sensor,
                    'estacion_id' => (int)$sensor['IdEstacion'],
                    'expected' => $EXPECTED,
                    'received' => 0,
                ),
                'Model' => HealthAnalyzer::MODEL_TAG,
                'WindowFrom' => null,
                'WindowTo' => null,
            ));
            if ($id !== null) {
                $totalInsertados++;
            }
            $totalFindings++;
        } catch (Throwable $e) {
            fwrite(STDERR, "[ai_health_sensors] Sensor {$sensor['Id']}: " . $e->getMessage() . "\n");
        }
        continue;
    }

    // Orden cronológico ascendente.
    $rows = array_reverse($rows);
    $values = array();
    foreach ($rows as $r) {
        if ($r['Valor'] === null || $r['Valor'] === '') {
            continue;
        }
        $values[] = (float)$r['Valor'];
    }

    $findings = $analyzer->analyze($values, $EXPECTED);
    if (empty($findings)) {
        continue;
    }

    $windowFrom = $rows[0]['Fecha'] . ' ' . $rows[0]['Hora'];
    $windowTo = $rows[count($rows) - 1]['Fecha'] . ' ' . $rows[count($rows) - 1]['Hora'];
    $unidad = trim((string)$sensor['Unidad']);
    $nombre = $sensor['Nombre'] ?: $sensor['Codigo'] ?: ('Sensor #' . $sensor['Id']);

    foreach ($findings as $f) {
        $totalFindings++;
        $title = ai_health_title($f['type'], $nombre, $f);
        $msg = ai_health_message($f['type'], $sensor['Variable'] ?: 's/v', $unidad, $f);

        $payload = array(
            'finding' => $f['type'],
            'sensor' => array(
                'id' => (int)$sensor['Id'],
                'codigo' => $sensor['Codigo'],
                'nombre' => $sensor['Nombre'],
                'variable' => $sensor['Variable'],
                'unidad' => $sensor['Unidad'],
            ),
            'estacion_id' => (int)$sensor['IdEstacion'],
            'window_points' => $WINDOW_POINTS,
            'expected' => $EXPECTED,
            'detail' => $f['detail'],
        );

        try {
            $id = AiInsightsModel::createIfNotExists(array(
                'TenantId' => $tenantId,
                'Type' => 'health',
                'EntityType' => 'sensor',
                'EntityId' => (int)$sensor['Id'],
                'Score' => (float)$f['score'],
                'Severity' => $f['severity'],
                'Title' => $title,
                'Message' => $msg,
                'Payload' => $payload,
                'Model' => HealthAnalyzer::MODEL_TAG,
                'WindowFrom' => $windowFrom,
                'WindowTo' => $windowTo,
            ));
            if ($id !== null) {
                $totalInsertados++;
            }
        } catch (Throwable $e) {
            fwrite(STDERR, "[ai_health_sensors] Sensor {$sensor['Id']} ({$f['type']}): " . $e->getMessage() . "\n");
        }
    }
}

printf(
    "[ai_health_sensors] %s | sensores=%d, sin_data=%d, findings=%d, insertados=%d\n",
    date('Y-m-d H:i:s'),
    $totalSensores,
    $saltadosPocaData,
    $totalFindings,
    $totalInsertados
);

exit(0);

// ---- Helpers (locales) -----------------------------------------------------

function ai_health_title(string $type, string $nombre, array $f): string
{
    switch ($type) {
        case 'gaps':
            return sprintf('Pérdida de datos en %s (%.0f%% faltante)', $nombre, $f['detail']['pct'] ?? 0);
        case 'flatline':
            return sprintf('Sensor pegado (flatline) en %s', $nombre);
        case 'drift':
            return sprintf('Deriva %s en %s (%.0f%% del promedio)', $f['detail']['direction'] ?? '', $nombre, $f['detail']['rel_change_pct'] ?? 0);
        case 'noise':
            return sprintf('Ruido excesivo en %s (x%.1f vs histórico)', $nombre, $f['detail']['ratio'] ?? 0);
    }
    return sprintf('Hallazgo de salud en %s', $nombre);
}

function ai_health_message(string $type, string $variable, string $unidad, array $f): string
{
    $u = $unidad !== '' ? ' ' . $unidad : '';
    switch ($type) {
        case 'gaps':
            return sprintf(
                'Variable "%s": faltan %d de %d lecturas esperadas (%.1f%%). Revisar conectividad/alimentación.',
                $variable,
                (int)($f['detail']['missing'] ?? 0),
                (int)($f['detail']['expected'] ?? 0),
                (float)($f['detail']['pct'] ?? 0)
            );
        case 'flatline':
            return sprintf(
                'Variable "%s" reporta valor constante (%s%s) en %d puntos. Posible sensor congelado o desconectado.',
                $variable,
                (string)($f['detail']['mean'] ?? '0'),
                $u,
                (int)($f['detail']['points'] ?? 0)
            );
        case 'drift':
            return sprintf(
                'Variable "%s" muestra deriva %s significativa (~%.1f%% del promedio). Considerar recalibración.',
                $variable,
                (string)($f['detail']['direction'] ?? ''),
                (float)($f['detail']['rel_change_pct'] ?? 0)
            );
        case 'noise':
            return sprintf(
                'Variable "%s" muestra dispersión reciente x%.1f vs histórico. Validar interferencias o cableado.',
                $variable,
                (float)($f['detail']['ratio'] ?? 0)
            );
    }
    return 'Hallazgo no clasificado.';
}
