<?php

declare(strict_types=1);

/**
 * Logpflege für storage/logs/ — NUR CLI, per Cron 1×/Woche.
 *
 * Zwei Aufgaben:
 *   1. Rotation nach Größe: wird eine Datei größer als das Limit, wandert sie nach
 *      <name>.log.1 (eine vorhandene .1 wird ersetzt) und das aktive Log startet leer.
 *      Es bleiben also höchstens zwei Generationen pro Log liegen.
 *   2. Aufbewahrungsfrist: Einträge, die älter als --keep-days sind, werden aus den
 *      aktiven Logs entfernt; rotierte .1-Dateien jenseits der Frist gelöscht. Damit
 *      gibt es eine echte Löschfrist statt nur einer Größengrenze — auf einer ruhigen
 *      Vereinsseite läge ein Eintrag sonst jahrelang herum.
 *
 * Aufruf:  php bin/log-rotate.php [--max-mb=2] [--keep-days=90] [-v]
 */
if (PHP_SAPI !== 'cli') {
    exit(1);
}

require dirname(__DIR__) . '/app/bootstrap.php';

$maxMb = 2.0;
$keepDays = 90;
$verbose = in_array('-v', $argv, true);
foreach ($argv as $arg) {
    if (preg_match('/^--max-mb=([0-9.]+)$/', $arg, $m) === 1) {
        $maxMb = (float) $m[1];
    } elseif (preg_match('/^--keep-days=(\d+)$/', $arg, $m) === 1) {
        $keepDays = max(1, (int) $m[1]);
    }
}
$limit = (int) ($maxMb * 1024 * 1024);
$cutoff = time() - $keepDays * 86400;
$logDir = STORAGE_PATH . '/logs';

/**
 * Zeitstempel am Zeilenanfang: `[ISO]` (log_write), `[27-Jul-2026 08:11:45 UTC]`
 * (PHP selbst) oder ein nackter ISO-Stempel. Null = keiner.
 */
$stampOf = static function (string $line): ?int {
    if (preg_match('/^\[([^\]]+)\]/', $line, $m) === 1 || preg_match('/^(\d{4}-\d{2}-\d{2}\S*)/', $line, $m) === 1) {
        $ts = strtotime($m[1]);
        return $ts === false ? null : $ts;
    }
    return null;
};

/**
 * Einträge vor $cutoff entfernen. Zeilen ohne eigenen Zeitstempel (z. B. PHP-Stacktraces)
 * gehören zum vorherigen Eintrag und teilen dessen Schicksal. Geschrieben wird über eine
 * temporäre Datei + rename; ein Append genau in diesem Moment ginge verloren — bei einem
 * wöchentlichen Lauf nachts ein akzeptables Risiko, dafür bleibt die Datei immer konsistent.
 */
$prune = static function (string $file) use ($cutoff, $stampOf): int {
    $raw = (string) @file_get_contents($file);
    if ($raw === '') {
        return 0;
    }
    $keep = [];
    $dropping = false;
    $removed = 0;
    foreach (explode("\n", $raw) as $line) {
        $ts = $stampOf($line);
        if ($ts !== null) {
            $dropping = $ts < $cutoff;
        }
        if ($dropping) {
            if (trim($line) !== '') {
                $removed++;
            }
            continue;
        }
        $keep[] = $line;
    }
    if ($removed === 0) {
        return 0;
    }
    $tmp = $file . '.tmp';
    if (@file_put_contents($tmp, implode("\n", $keep)) === false || !@rename($tmp, $file)) {
        @unlink($tmp);
        return 0;
    }
    @chmod($file, 0664);
    return $removed;
};

$rotated = 0;
$pruned = 0;
$deleted = 0;

foreach (glob($logDir . '/*.log') ?: [] as $log) {
    $name = basename($log);

    // 1. Alte Einträge raus (vor der Größenprüfung — spart oft die Rotation).
    $removed = $prune($log);
    if ($removed > 0) {
        $pruned += $removed;
        if ($verbose) {
            echo "gekürzt: {$name} ({$removed} Einträge älter als {$keepDays} Tage)\n";
        }
    }

    // 2. Rotation nach Größe.
    $size = (int) @filesize($log);
    if ($size > $limit) {
        if (@rename($log, $log . '.1')) {
            @touch($log);
            @chmod($log, 0664);
            echo "rotiert: {$name} (" . round($size / 1048576, 1) . " MB)\n";
            $rotated++;
        } else {
            fwrite(STDERR, "Rotation fehlgeschlagen: {$name}\n");
        }
    }
}

// 3. Rotierte Generationen jenseits der Frist löschen.
foreach (glob($logDir . '/*.log.1') ?: [] as $old) {
    if ((int) @filemtime($old) < $cutoff && @unlink($old)) {
        echo 'gelöscht: ' . basename($old) . " (älter als {$keepDays} Tage)\n";
        $deleted++;
    }
}

echo $rotated === 0 && $pruned === 0 && $deleted === 0
    ? "Nichts zu tun (alle Logs unter {$maxMb} MB, keine Einträge älter als {$keepDays} Tage).\n"
    : "Fertig: {$rotated} rotiert, {$pruned} alte Einträge entfernt, {$deleted} Altdatei(en) gelöscht.\n";
