#!/usr/bin/env php
<?php
/**
 * NIS2 Agile — Feedback Worker
 *
 * Worker cron per la risoluzione autonoma dei ticket di segnalazione.
 * Adattato da alltax.it/docs/sistema-segnalazioni-standard.html
 *
 * Ciclo (ogni 30 min):
 *  1. Acquisisce lock — previene run paralleli
 *  2. Recupera ticket status='in_lavorazione'
 *  3. Per ciascuno: invoca Claude Code CLI nel container devenv
 *  4. Se exit_code=0: marca ticket come risolto + broadcast email
 *  5. Logga risultati in FEEDBACK_WORKER_LOG
 *
 * Crontab (root):
 *  * /30 * * * * root /usr/bin/php8.4 /var/www/nis2-agile/scripts/feedback-worker.php
 *  (rimuovi lo spazio dopo * per avere: asterisco-slash-30)
 *
 * Variabili .env necessarie:
 *  FEEDBACK_RESOLVE_PASSWORD=... (password per POST /api/feedback/{id}/resolve)
 *  FEEDBACK_WORKER_ADMIN_EMAIL=...
 *  FEEDBACK_WORKER_ADMIN_PASS=...
 */

declare(strict_types=1);

// Carica ambiente
define('BASE_PATH', dirname(__DIR__));
define('APP_PATH',  BASE_PATH . '/application');
define('PUBLIC_PATH', BASE_PATH . '/public');
define('UPLOAD_PATH', PUBLIC_PATH . '/uploads');
define('DATA_PATH', APP_PATH . '/data');

require_once APP_PATH . '/config/env.php';
require_once APP_PATH . '/config/config.php';
require_once APP_PATH . '/config/database.php';

// ── Config worker ─────────────────────────────────────────────────────────

const LOCK_FILE       = '/tmp/nis2-feedback-worker.lock';
const CLAUDE_TIMEOUT  = 1500; // secondi (25 min, < cron interval 30 min)
const DEVENV_CONTAINER = 'nis2-agile-devenv';
const APP_URL_INTERNAL = 'http://localhost:8080'; // URL interno per chiamate PHP curl

$logFile = defined('FEEDBACK_WORKER_LOG') ? FEEDBACK_WORKER_LOG : '/tmp/nis2-feedback-worker.log';

// ── Lock ──────────────────────────────────────────────────────────────────

if (file_exists(LOCK_FILE)) {
    $lockAge = time() - filemtime(LOCK_FILE);
    if ($lockAge < 1800) {
        _log($logFile, 'Worker già in esecuzione (lock attivo, età: ' . $lockAge . 's). Uscita.');
        exit(0);
    }
    _log($logFile, 'Lock obsoleto trovato (' . $lockAge . 's), rimuovo e procedo.');
    unlink(LOCK_FILE);
}

file_put_contents(LOCK_FILE, posix_getpid());

register_shutdown_function(function () {
    if (file_exists(LOCK_FILE)) {
        unlink(LOCK_FILE);
    }
});

// ── Main ──────────────────────────────────────────────────────────────────

_log($logFile, '=== Feedback Worker avviato ===');

try {
    $tickets = Database::fetchAll(
        "SELECT id, tipo, descrizione, ai_suggerimento, ai_risposta, organization_id
         FROM feedback_reports
         WHERE status = 'in_lavorazione'
         ORDER BY created_at ASC
         LIMIT 10"
    );

    if (empty($tickets)) {
        _log($logFile, 'Nessun ticket in lavorazione. Worker terminato.');
        exit(0);
    }

    _log($logFile, count($tickets) . ' ticket da processare.');

    $resolvePassword = defined('FEEDBACK_RESOLVE_PASSWORD') ? FEEDBACK_RESOLVE_PASSWORD : '';
    if (empty($resolvePassword)) {
        _log($logFile, 'FEEDBACK_RESOLVE_PASSWORD non configurata. Worker non può risolvere ticket.');
        exit(1);
    }

    foreach ($tickets as $ticket) {
        _processTicket($ticket, $resolvePassword, $logFile);
    }

} catch (\Throwable $e) {
    _log($logFile, 'ERRORE FATALE: ' . $e->getMessage());
    exit(1);
}

_log($logFile, '=== Worker completato ===');
exit(0);

// ── Funzioni ──────────────────────────────────────────────────────────────

function _processTicket(array $ticket, string $password, string $logFile): void
{
    $id = $ticket['id'];
    _log($logFile, "Processing ticket #{$id} [{$ticket['tipo']}]: " . substr($ticket['descrizione'], 0, 80) . '…');

    // Costruisci prompt per Claude Code
    $prompt = _buildPrompt($ticket);

    // Scrivi prompt su file temporaneo
    $promptFile = "/tmp/nis2-feedback-prompt-{$id}.txt";
    file_put_contents($promptFile, $prompt);

    // Esegui Claude Code nel container devenv
    $dockerCmd = sprintf(
        'docker exec -u developer %s bash -c %s',
        escapeshellarg(DEVENV_CONTAINER),
        escapeshellarg(
            'cd /projects/nis2-agile && ' .
            'timeout ' . CLAUDE_TIMEOUT . ' ' .
            'claude --dangerously-skip-permissions --output-format stream-json ' .
            '-p "$(cat ' . $promptFile . ')" 2>&1'
        )
    );

    $output   = [];
    $exitCode = 0;
    exec($dockerCmd, $output, $exitCode);

    @unlink($promptFile);

    $outputText = implode("\n", $output);
    _log($logFile, "Ticket #{$id} — Claude exit_code={$exitCode}, output=" . substr($outputText, 0, 200));

    if ($exitCode !== 0) {
        _log($logFile, "Ticket #{$id} — risoluzione fallita (exit {$exitCode}).");
        // Rimane in in_lavorazione per il prossimo ciclo
        return;
    }

    // Chiama API interna per marcare come risolto
    $resolved = _resolveViaApi($id, $password, $logFile);

    if ($resolved) {
        _log($logFile, "Ticket #{$id} — risolto e broadcast inviato.");
    }
}

function _buildPrompt(array $ticket): string
{
    $suggerimento = $ticket['ai_suggerimento'] ?? 'Nessun suggerimento AI disponibile.';

    return <<<PROMPT
Sei un developer senior che lavora su NIS2 Agile, una piattaforma PHP 8.4 per compliance NIS2.

## Ticket da risolvere
ID: {$ticket['id']}
Tipo: {$ticket['tipo']}
Descrizione: {$ticket['descrizione']}

## Suggerimento AI (classificazione automatica)
{$suggerimento}

## Istruzioni
1. Analizza il problema descritto
2. Identifica i file rilevanti nel progetto (/projects/nis2-agile/)
3. Applica la correzione minimale necessaria
4. Non modificare test o documentazione
5. Assicurati che il codice PHP sia valido (sintassi corretta)
6. Se il problema non è risolvibile in modo sicuro, esci con exit code 1

Risolvi il problema e poi esci. Non avviare server, non fare git commit.
PROMPT;
}

function _resolveViaApi(int $reportId, string $password, string $logFile): bool
{
    $url     = APP_URL_INTERNAL . "/api/feedback/{$reportId}/resolve";
    $payload = json_encode(['password' => $password]);

    // Ottieni un token JWT admin per la chiamata interna
    $token = _getAdminToken($logFile);
    if (!$token) {
        _log($logFile, "Ticket #{$reportId} — impossibile ottenere token admin per risoluzione.");
        return false;
    }

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $payload,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $token,
        ],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 15,
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curlError = curl_error($ch);
    curl_close($ch);

    if ($curlError || $httpCode < 200 || $httpCode >= 300) {
        _log($logFile, "Ticket #{$reportId} — API resolve fallita [{$httpCode}]: {$curlError}");
        return false;
    }

    $data = json_decode($response, true);
    return $data['success'] ?? false;
}

function _getAdminToken(string $logFile): ?string
{
    static $cachedToken = null;

    if ($cachedToken !== null) return $cachedToken;

    $adminEmail = Env::get('FEEDBACK_WORKER_ADMIN_EMAIL', '');
    $adminPass  = Env::get('FEEDBACK_WORKER_ADMIN_PASS',  '');

    if (!$adminEmail || !$adminPass) {
        _log($logFile, 'FEEDBACK_WORKER_ADMIN_EMAIL / FEEDBACK_WORKER_ADMIN_PASS non configurate.');
        return null;
    }

    $ch = curl_init(APP_URL_INTERNAL . '/api/auth/login');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode(['email' => $adminEmail, 'password' => $adminPass]),
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 10,
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode !== 200) {
        _log($logFile, "Login admin fallito [{$httpCode}].");
        return null;
    }

    $data = json_decode($response, true);
    $cachedToken = $data['data']['access_token'] ?? null;

    return $cachedToken;
}

function _log(string $logFile, string $message): void
{
    $line = '[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL;
    $dir  = dirname($logFile);

    if (!is_dir($dir)) {
        @mkdir($dir, 0755, true);
    }

    @file_put_contents($logFile, $line, FILE_APPEND | LOCK_EX);
    echo $line;
}
