#!/usr/bin/env php
<?php
/**
 * NIS2 Agile — Supplier Questionnaire Runner (Fase 2)
 *
 * Cron scadenze/reminder per il modulo questionari fornitori.
 * Opera sulla tabella questionnaire_campaigns (migrazione 032).
 *
 * Tre fasi, tutte IDEMPOTENTI (UPDATE condizionato → safe se il cron parte 2 volte):
 *  1. REMINDER   — campagne sent/in_progress con next_reminder_at <= NOW(): invia
 *                  email, claim atomico, ricalcola next_reminder_at dal prossimo offset.
 *  2. OVERDUE    — campagne sent/in_progress con due_at < NOW() → status='expired'.
 *  3. RICORRENZA — completed/expired con is_recurring=1 e next_recurrence_at <= NOW()
 *                  → clona nuova campagna (token sq_, due_at=now+recurrence_months).
 *
 * Invocato da scripts/supplier-questionnaire-cron.sh (06:00 Europe/Rome).
 */

declare(strict_types=1);
date_default_timezone_set('Europe/Rome');

define('BASE_PATH',   dirname(__DIR__));
define('APP_PATH',    BASE_PATH . '/application');
define('PUBLIC_PATH', BASE_PATH . '/public');

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

const PORTAL_LINK = 'https://nis2.agile.software/supplier-portal.html';
const LEGACY_BASE = 'https://nis2.agile.software/supplier-assessment.html?token=';

$LOG_FILE = Env::get('SUPPLIER_CRON_LOG', '/var/log/nis2-supplier-cron.log');
$BATCH    = max(1, Env::int('SUPPLIER_CRON_BATCH', 500));

qlog($LOG_FILE, '=== Supplier Questionnaire Runner avviato ===');
$summary = ['reminders_sent' => 0, 'reminders_failed' => 0, 'expired' => 0, 'recurrences' => 0, 'errors' => 0];

try {
    $summary['reminders_sent'] = runReminders($LOG_FILE, $BATCH, $summary);
    $summary['expired']        = runOverdue($LOG_FILE, $BATCH);
    $summary['recurrences']    = runRecurrence($LOG_FILE, $BATCH);
} catch (\Throwable $e) {
    $summary['errors']++;
    qlog($LOG_FILE, 'ERRORE FATALE: ' . $e->getMessage());
}

qlog($LOG_FILE, sprintf(
    'SUMMARY reminders_sent=%d reminders_failed=%d expired=%d recurrences=%d errors=%d',
    $summary['reminders_sent'], $summary['reminders_failed'],
    $summary['expired'], $summary['recurrences'], $summary['errors']
));
qlog($LOG_FILE, '=== Runner completato ===');
exit($summary['errors'] > 0 ? 1 : 0);

// ── FASE 1 — REMINDER ───────────────────────────────────────────────────────
function runReminders(string $log, int $batch, array &$summary): int
{
    $rows = Database::fetchAll(
        "SELECT c.id, c.organization_id, c.supplier_id, c.sent_to_email, c.due_at,
                c.reminder_offsets, c.last_reminder_at, c.language, s.name AS supplier_name
           FROM questionnaire_campaigns c
           LEFT JOIN suppliers s ON s.id = c.supplier_id
          WHERE c.status IN ('sent','in_progress')
            AND c.next_reminder_at IS NOT NULL AND c.next_reminder_at <= NOW()
          ORDER BY c.next_reminder_at ASC LIMIT {$batch}",
        []
    );
    if (!$rows) { qlog($log, 'REMINDER: nessuna campagna da sollecitare.'); return 0; }
    qlog($log, 'REMINDER: ' . count($rows) . ' candidate.');

    $sent = 0;
    foreach ($rows as $c) {
        $id = (int) $c['id'];
        $claimed = Database::query(
            "UPDATE questionnaire_campaigns SET next_reminder_at = NULL
              WHERE id = ? AND status IN ('sent','in_progress')
                AND next_reminder_at IS NOT NULL AND next_reminder_at <= NOW()",
            [$id]
        )->rowCount();
        if ($claimed === 0) { qlog($log, "REMINDER: #{$id} già preso, salto."); continue; }

        $email = trim((string) ($c['sent_to_email'] ?? ''));
        $ok = $email !== '' ? sendReminderEmail($c, $log) : false;

        $offsets = json_decode((string) ($c['reminder_offsets'] ?? '[]'), true);
        $offsets = is_array($offsets) ? $offsets : [];
        $next = computeNextReminder($c['due_at'] ?? null, $offsets, date('Y-m-d H:i:s'));

        if ($ok) {
            Database::query(
                "UPDATE questionnaire_campaigns
                    SET reminder_count = reminder_count + 1, last_reminder_at = NOW(), next_reminder_at = ?
                  WHERE id = ?",
                [$next, $id]
            );
            $sent++;
            qlog($log, "REMINDER: #{$id} inviato a " . maskEmail($email) . ($next ? " — prossimo {$next}" : ' — ultimo'));
        } else {
            $retry = $next ?? computeNextReminder($c['due_at'] ?? null, $offsets, null);
            Database::query("UPDATE questionnaire_campaigns SET next_reminder_at = ? WHERE id = ?", [$retry, $id]);
            $summary['reminders_failed']++;
            qlog($log, "REMINDER: #{$id} invio FALLITO — riprogrammato a " . ($retry ?? 'mai'));
        }
    }
    return $sent;
}

// ── FASE 2 — OVERDUE ────────────────────────────────────────────────────────
function runOverdue(string $log, int $batch): int
{
    $n = Database::query(
        "UPDATE questionnaire_campaigns SET status = 'expired', next_reminder_at = NULL
          WHERE status IN ('sent','in_progress') AND due_at IS NOT NULL AND due_at < NOW()
          LIMIT {$batch}",
        []
    )->rowCount();
    qlog($log, "OVERDUE: {$n} campagne marcate 'expired'.");
    return $n;
}

// ── FASE 3 — RICORRENZA ─────────────────────────────────────────────────────
function runRecurrence(string $log, int $batch): int
{
    $rows = Database::fetchAll(
        "SELECT id, organization_id, supplier_id, template_id, template_version,
                show_score_to_supplier, language, sent_to_email, reminder_offsets,
                recurrence_months, created_by
           FROM questionnaire_campaigns
          WHERE status IN ('completed','expired') AND is_recurring = 1
            AND next_recurrence_at IS NOT NULL AND next_recurrence_at <= NOW()
          ORDER BY next_recurrence_at ASC LIMIT {$batch}",
        []
    );
    if (!$rows) { qlog($log, 'RICORRENZA: nessuna campagna da riemettere.'); return 0; }
    qlog($log, 'RICORRENZA: ' . count($rows) . ' candidate.');

    $created = 0;
    foreach ($rows as $old) {
        $oldId = (int) $old['id'];
        $claimed = Database::query(
            "UPDATE questionnaire_campaigns SET next_recurrence_at = NULL
              WHERE id = ? AND is_recurring = 1
                AND next_recurrence_at IS NOT NULL AND next_recurrence_at <= NOW()",
            [$oldId]
        )->rowCount();
        if ($claimed === 0) { qlog($log, "RICORRENZA: #{$oldId} già riemessa, salto."); continue; }

        $months = (int) $old['recurrence_months'];
        if ($months <= 0) { qlog($log, "RICORRENZA: #{$oldId} recurrence_months non valido, salto."); continue; }

        $now    = date('Y-m-d H:i:s');
        $dueAt  = date('Y-m-d H:i:s', strtotime("+{$months} months"));
        $offsets = json_decode((string) ($old['reminder_offsets'] ?? '[]'), true);
        $offsets = is_array($offsets) ? array_values(array_filter(array_map('intval', $offsets), fn($o) => $o < 0)) : [];
        $nextReminderAt = computeNextReminder($dueAt, $offsets, $now);
        $nextRecurrence = date('Y-m-d H:i:s', strtotime($dueAt . " +{$months} months"));
        $rawToken = 'sq_' . bin2hex(random_bytes(20));

        try {
            Database::beginTransaction();
            $newId = Database::insert('questionnaire_campaigns', [
                'organization_id'        => (int) $old['organization_id'],
                'supplier_id'            => (int) $old['supplier_id'],
                'template_id'            => $old['template_id'] !== null ? (int) $old['template_id'] : null,
                'template_version'       => $old['template_version'],
                'access_token_hash'      => hash('sha256', $rawToken),
                'status'                 => 'sent',
                'show_score_to_supplier' => (int) $old['show_score_to_supplier'],
                'language'               => $old['language'] ?: 'it',
                'sent_to_email'          => $old['sent_to_email'],
                'sent_at'                => $now,
                'due_at'                 => $dueAt,
                'expires_at'             => $dueAt,
                'reminder_offsets'       => json_encode($offsets),
                'next_reminder_at'       => $nextReminderAt,
                'is_recurring'           => 1,
                'recurrence_months'      => $months,
                'next_recurrence_at'     => $nextRecurrence,
                'created_by'             => $old['created_by'] !== null ? (int) $old['created_by'] : null,
            ]);
            $email = trim((string) ($old['sent_to_email'] ?? ''));
            if ($email !== '') {
                $existing = Database::fetchOne('SELECT id FROM supplier_users WHERE supplier_id = ? AND email = ?',
                    [(int) $old['supplier_id'], $email]);
                if (!$existing) {
                    Database::insert('supplier_users', [
                        'supplier_id' => (int) $old['supplier_id'], 'organization_id' => (int) $old['organization_id'],
                        'email' => $email, 'role' => 'contact', 'is_active' => 1,
                    ]);
                }
            }
            Database::commit();
        } catch (\Throwable $e) {
            if (Database::getInstance()->inTransaction()) Database::rollback();
            qlog($log, "RICORRENZA: #{$oldId} clone FALLITO: " . $e->getMessage());
            continue;
        }
        $created++;
        qlog($log, "RICORRENZA: #{$oldId} riemessa come #{$newId} (due {$dueAt}).");

        $email = trim((string) ($old['sent_to_email'] ?? ''));
        if ($email !== '') {
            sendInviteEmail(['supplier_name' => fetchSupplierName((int) $old['supplier_id']),
                'sent_to_email' => $email, 'due_at' => $dueAt], $rawToken, $log);
        }
    }
    return $created;
}

// ── HELPER ──────────────────────────────────────────────────────────────────
function computeNextReminder(?string $dueAt, array $offsets, ?string $afterTs): ?string
{
    if (!$dueAt || empty($offsets)) return null;
    $due = strtotime($dueAt);
    $after = $afterTs ? strtotime($afterTs) : time();
    $candidates = [];
    foreach ($offsets as $o) {
        $ts = $due + ((int) $o) * 86400;
        if ($ts > $after) $candidates[] = $ts;
    }
    return empty($candidates) ? null : date('Y-m-d H:i:s', min($candidates));
}

function sendReminderEmail(array $c, string $log): bool
{
    if (!class_exists('EmailService')) return false;
    $supplier = htmlspecialchars((string) ($c['supplier_name'] ?? 'fornitore'), ENT_QUOTES, 'UTF-8');
    $dueLabel = !empty($c['due_at']) ? (' entro il <strong>' . date('d/m/Y', strtotime($c['due_at'])) . '</strong>') : '';
    $body = "<p>Gentile referente di <strong>{$supplier}</strong>,</p>"
        . "<p>vi <strong>ricordiamo</strong> di compilare il questionario di sicurezza richiesto dalla normativa NIS2{$dueLabel}.</p>"
        . "<p>Accedete al <strong>portale fornitori</strong> con la vostra email: <a href=\"" . PORTAL_LINK . "\">" . PORTAL_LINK . "</a></p>";
    try {
        return (new EmailService())->send((string) $c['sent_to_email'], 'Promemoria: questionario di sicurezza fornitore - NIS2', $body);
    } catch (\Throwable $e) { qlog($log, '[REMINDER email] ' . $e->getMessage()); return false; }
}

function sendInviteEmail(array $c, string $rawToken, string $log): bool
{
    if (!class_exists('EmailService')) return false;
    $supplier = htmlspecialchars((string) ($c['supplier_name'] ?? 'fornitore'), ENT_QUOTES, 'UTF-8');
    $legacy = LEGACY_BASE . $rawToken;
    $dueLabel = !empty($c['due_at']) ? (' entro il ' . date('d/m/Y', strtotime($c['due_at']))) : '';
    $body = "<p>Gentile referente di <strong>{$supplier}</strong>,</p>"
        . "<p>e' disponibile una <strong>nuova edizione periodica</strong> del questionario di sicurezza NIS2{$dueLabel}.</p>"
        . "<p>Accedete al <strong>portale fornitori</strong>: <a href=\"" . PORTAL_LINK . "\">" . PORTAL_LINK . "</a></p>"
        . "<p style=\"font-size:13px;color:#666\">In alternativa: <a href=\"{$legacy}\">compila qui</a></p>";
    try {
        return (new EmailService())->send((string) $c['sent_to_email'], 'Questionario di sicurezza fornitore - NIS2 (rinnovo periodico)', $body);
    } catch (\Throwable $e) { qlog($log, '[RICORRENZA email] ' . $e->getMessage()); return false; }
}

function fetchSupplierName(int $supplierId): string
{
    $r = Database::fetchOne('SELECT name FROM suppliers WHERE id = ?', [$supplierId]);
    return (string) ($r['name'] ?? 'fornitore');
}

function maskEmail(string $email): string
{
    $at = strpos($email, '@');
    if ($at === false || $at === 0) return '***';
    return $email[0] . '***' . substr($email, $at);
}

function qlog(string $logFile, string $message): void
{
    $line = '[' . date('Y-m-d H:i:s T') . '] ' . $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;
}
