#!/usr/bin/env php
<?php
/**
 * Log rotation and purge — application logs.
 *
 * Rotates log files daily (app.log → app.log.2026-05-15) and compresses
 * previous rotations with gzip. Purges rotated logs older than N days.
 *
 * Usage:
 *   php exploitation/logrotate.php [options]
 *
 * Options:
 *   -d, --dir <path>      Log directory (default: log/)
 *   -k, --keep <days>     Delete rotated logs older than N days (default: 30)
 *   -c, --compress        Compress rotated logs with gzip (default: yes)
 *       --no-compress     Do not compress rotated logs
 *   -q, --quiet           Suppress output
 *   -h, --help            Show this help
 *
 * Exit codes:
 *   0  Success
 *   1  Error
 *
 * Managed log files:
 *   app.log      Application messages (warnings, errors)
 *   debug.log    Debug messages (if debug_mode enabled)
 *   trace.log    HTTP trace (if trace_mode enabled)
 *
 * Rotation scheme:
 *   app.log              → current (truncated after rotation)
 *   app.log.2026-05-15   → today's rotation (uncompressed)
 *   app.log.2026-05-14.gz → yesterday's rotation (compressed)
 *
 * Examples:
 *   php exploitation/logrotate.php
 *   php exploitation/logrotate.php --keep 7
 *   php exploitation/logrotate.php --no-compress --keep 90
 *   php exploitation/logrotate.php -d /var/log/myapp -k 14
 *
 * Cron (daily at 00:05):
 *   5 0 * * * cd /opt/app && php exploitation/logrotate.php --quiet
 *
 * Windows Task Scheduler:
 *   php.exe C:\app\exploitation\logrotate.php --quiet
 */

require_once dirname(__DIR__) . '/shared/cli_bootstrap.php';

/* ── CLI arguments ─────────────────────────────────────────────────── */
$opts = getopt('d:k:cqh', ['dir:', 'keep:', 'compress', 'no-compress', 'quiet', 'help']);

if (isset($opts['h']) || isset($opts['help'])) cli_show_help(__FILE__);

$quiet    = isset($opts['q']) || isset($opts['quiet']);
$logDir   = $opts['d'] ?? $opts['dir'] ?? LOG_PATH;
$keepDays = (int)($opts['k'] ?? $opts['keep'] ?? 30);
$compress = true;
if (isset($opts['no-compress'])) $compress = false;

/* Log files to rotate */
$logFiles = ['app.log', 'debug.log', 'trace.log'];

function out(string $msg): void {
    global $quiet;
    if (!$quiet) echo $msg . PHP_EOL;
}

/* ── Validation ────────────────────────────────────────────────────── */
if (!is_dir($logDir)) {
    out("SKIP: Log directory does not exist: {$logDir}");
    exit(0); // not an error — logs may not have been created yet
}

$today = date('Y-m-d');

/* ── Phase 1: Rotate current logs ──────────────────────────────────── */
$rotated = 0;

foreach ($logFiles as $logFile) {
    $current = $logDir . '/' . $logFile;

    if (!file_exists($current)) continue;

    /* Skip empty files */
    if (filesize($current) === 0) continue;

    $rotatedName = $logFile . '.' . $today;
    $rotatedPath = $logDir . '/' . $rotatedName;

    /* Don't rotate if already rotated today */
    if (file_exists($rotatedPath) || file_exists($rotatedPath . '.gz')) continue;

    /* Rotate: copy content then truncate (avoids losing writes during copy) */
    if (copy($current, $rotatedPath)) {
        /* Truncate the original file (don't delete — the app may hold a handle) */
        $fh = fopen($current, 'w');
        if ($fh) fclose($fh);

        $rotated++;
        out("Rotated: {$logFile} → {$rotatedName} (" . round(filesize($rotatedPath) / 1024, 1) . " KB)");
    } else {
        out("ERROR: Could not rotate {$logFile}");
    }
}

/* ── Phase 2: Compress previous rotations ──────────────────────────── */
$compressed = 0;

if ($compress && function_exists('gzopen')) {
    foreach (scandir($logDir) as $f) {
        if ($f === '.' || $f === '..') continue;

        /* Match pattern: *.log.YYYY-MM-DD (uncompressed, not today) */
        if (!preg_match('/^.+\.log\.(\d{4}-\d{2}-\d{2})$/', $f, $m)) continue;
        if ($m[1] === $today) continue; // don't compress today's rotation

        $fullPath = $logDir . '/' . $f;
        $gzPath   = $fullPath . '.gz';

        if (file_exists($gzPath)) continue; // already compressed

        /* Compress */
        $source = fopen($fullPath, 'rb');
        $dest   = gzopen($gzPath, 'wb9');
        if ($source && $dest) {
            while (!feof($source)) {
                gzwrite($dest, fread($source, 65536));
            }
            gzclose($dest);
            fclose($source);
            unlink($fullPath); // remove uncompressed version
            $compressed++;
            out("Compressed: {$f} → {$f}.gz");
        } else {
            if ($source) fclose($source);
            if ($dest) gzclose($dest);
            out("ERROR: Could not compress {$f}");
        }
    }
} elseif ($compress) {
    out("WARN: gzip compression not available (zlib extension missing).");
}

/* ── Phase 3: Purge old rotations ──────────────────────────────────── */
$purged = 0;

if ($keepDays > 0) {
    $cutoff = time() - ($keepDays * 86400);

    foreach (scandir($logDir) as $f) {
        if ($f === '.' || $f === '..') continue;

        /* Match rotated logs: *.log.YYYY-MM-DD or *.log.YYYY-MM-DD.gz */
        if (!preg_match('/^.+\.log\.\d{4}-\d{2}-\d{2}(\.gz)?$/', $f)) continue;

        $fullPath = $logDir . '/' . $f;
        if (filemtime($fullPath) < $cutoff) {
            unlink($fullPath);
            $purged++;
        }
    }
}

/* ── Summary ───────────────────────────────────────────────────────── */
out("Done: {$rotated} rotated, {$compressed} compressed, {$purged} purged (keep {$keepDays}d).");
exit(0);
