<?php
/**
 * cron/doi_cleanup.php
 * -------------------------------------------------------------------
 *  ▸ Löscht abgelaufene, NIE bestätigte Opt‑Ins (Hard‑Delete)
 *  ▸ Rotiert die IP‑Verschlüsselung, wenn ein NEUER Schlüssel hinterlegt ist
 *  ▸ Parameter stammen jetzt vollständig aus includes/config.php – keine $_ENV‑Aufrufe mehr
 * -------------------------------------------------------------------
 */



require_once __DIR__ . '/../includes/init.php';
require_once __DIR__ . '/../includes/core/SodiumCrypto.php';

// ------------------------------------------------------------------
// 0. Konfiguration laden
// ------------------------------------------------------------------
$config          = require __DIR__ . '/../includes/config.php';
$secConf         = $config['security'];

$daysUntilPurge     = (int) ($secConf['doi_purge_days']  ?? 30);   // Hard‑Delete von offenen Opt‑Ins
$daysUntilRotate    = (int) ($secConf['doi_rotate_days'] ?? 365);  // Re‑Encrypt nach X Tagen

// Schlüssel‑Rotation:  Wenn ein neuer Key definiert ist, werden Daten,
// die älter sind als doi_rotate_days, mit dem NEUEN Schlüssel frisch
// verschlüsselt.  Alt‑Key = aktueller Key zum Zeitpunkt der Anmeldung.
$oldKey = $secConf['doi_key']          ?? null;
$newKey = $secConf['doi_key_new']      ?? null; // Optional in config.php

$db = Database::getInstance();

try {
    //-----------------------------------------------------------------
    // 1. Abgelaufene, NIE bestätigte Opt‑Ins entfernen
    //-----------------------------------------------------------------
    $purgeCutoff = (new DateTimeImmutable("-{$daysUntilPurge} days"))->format('Y-m-d H:i:s');

    $del = $db->prepare(
        'DELETE FROM recipient_list_optin
         WHERE doi_confirmed = 0
           AND doi_requested_at < :cutoff'
    );
    $del->execute([':cutoff' => $purgeCutoff]);
    ErrorLogger::logSqlSuccess('doi_cleanup_purge | file: ' . __FILE__ . ' | line: ' . __LINE__ . ' | context: ' . json_encode(['purged' => $del->rowCount(), 'cutoff' => $purgeCutoff], JSON_UNESCAPED_UNICODE));

    //-----------------------------------------------------------------
    // 2. Key‑Rotation (nur wenn neuer Schlüssel existiert)
    //-----------------------------------------------------------------
    if ($newKey && $oldKey && $newKey !== $oldKey) {
        $rotateCutoff = (new DateTimeImmutable("-{$daysUntilRotate} days"))->format('Y-m-d H:i:s');

        $sel = $db->prepare(
            'SELECT id, doi_ip_signup_enc, doi_ip_confirm_enc
               FROM recipient_list_optin
              WHERE doi_created_at < :cutoff'
        );
        $sel->execute([':cutoff' => $rotateCutoff]);

        $upd = $db->prepare(
            'UPDATE recipient_list_optin
                SET doi_ip_signup_enc  = :signup,
                    doi_ip_confirm_enc = :confirm
              WHERE id = :id'
        );

        $rotated = 0;
        while ($row = $sel->fetch(PDO::FETCH_ASSOC)) {
            try {
                $signupPlain  = $row['doi_ip_signup_enc']  ? SodiumCrypto::decrypt($row['doi_ip_signup_enc'],  $oldKey) : null;
                $confirmPlain = $row['doi_ip_confirm_enc'] ? SodiumCrypto::decrypt($row['doi_ip_confirm_enc'], $oldKey) : null;

                $upd->execute([
                    ':signup'  => $signupPlain  ? SodiumCrypto::encrypt($signupPlain,  $newKey) : null,
                    ':confirm' => $confirmPlain ? SodiumCrypto::encrypt($confirmPlain, $newKey) : null,
                    ':id'      => $row['id'],
                ]);
                $rotated++;
            } catch (Throwable $e) {
                ErrorLogger::logSqlError('doi_key_rotation | file: ' . __FILE__ . ' | line: ' . __LINE__ . ' | error: ' . $e->getMessage());
            }
        }
        ErrorLogger::logSqlSuccess('doi_key_rotation | file: ' . __FILE__ . ' | line: ' . __LINE__ . ' | context: ' . json_encode(['rotated' => $rotated], JSON_UNESCAPED_UNICODE));
    }

} catch (Throwable $e) {
    ErrorLogger::logSqlError('doi_cleanup | file: ' . __FILE__ . ' | line: ' . __LINE__ . ' | error: ' . $e->getMessage());
    // Absichtlich kein exit, damit Cron nicht komplett stirbt
}
