#!/usr/bin/env php
<?php
/**
 * IPTV Panel - Cron Runner
 * 
 * Dodaj do crontab:
 * * * * * * php /sciezka/do/cron.php >> /sciezka/do/logs/cron.log 2>&1
 * 
 * Lub uruchom ręcznie:
 * php cron.php
 */

// Ustaw strefę czasową
date_default_timezone_set('Europe/Warsaw');

// Załaduj konfigurację
$basePath = __DIR__;
require_once $basePath . '/panel/config/config.php';
require_once $basePath . '/panel/classes/Database.php';
require_once $basePath . '/panel/classes/Logger.php';

// Lockfile - zapobiega równoległemu uruchamianiu
$lockFile = $basePath . '/logs/cron.lock';

if (file_exists($lockFile)) {
    $lockTime = filemtime($lockFile);
    // Jeśli lock starszy niż 10 minut - usuń (zabezpieczenie przed zawieszeniem)
    if (time() - $lockTime > 600) {
        unlink($lockFile);
    } else {
        echo "[" . date('Y-m-d H:i:s') . "] Cron już działa (lock file exists)\n";
        exit(0);
    }
}

// Utwórz lock
file_put_contents($lockFile, getmypid());

try {
    $db = Database::getInstance();
    $logger = new Logger();
    
    echo "[" . date('Y-m-d H:i:s') . "] Cron started\n";
    
    // Załaduj zadania z pliku JSON
    $jobsFile = $basePath . '/panel/config/cron_jobs.json';
    $jobs = [];
    
    if (file_exists($jobsFile)) {
        $jobs = json_decode(file_get_contents($jobsFile), true) ?: [];
    }
    
    // Sprawdź które zadania powinny być uruchomione
    $now = new DateTime();
    $executed = 0;
    
    foreach ($jobs as &$job) {
        if (!$job['enabled']) {
            continue;
        }
        
        // Sprawdź harmonogram (prosty parser cron)
        if (shouldRunJob($job['schedule'], $now)) {
            echo "[" . date('Y-m-d H:i:s') . "] Running job: {$job['name']} ({$job['type']})\n";
            
            $result = executeJob($job['type'], $db, $logger, $basePath);
            
            $job['last_run'] = $now->format('Y-m-d H:i:s');
            $job['last_result'] = $result['success'] ? 'success' : 'error';
            $job['last_message'] = $result['message'];
            
            echo "[" . date('Y-m-d H:i:s') . "] Result: " . ($result['success'] ? 'OK' : 'ERROR') . " - {$result['message']}\n";
            
            $logger->log('cron_job_executed', "Zadanie: {$job['name']} - " . ($result['success'] ? 'OK' : 'BŁĄD'));
            $executed++;
        }
    }
    
    // Zapisz zaktualizowane zadania
    file_put_contents($jobsFile, json_encode($jobs, JSON_PRETTY_PRINT));
    
    echo "[" . date('Y-m-d H:i:s') . "] Cron finished. Executed {$executed} jobs.\n";
    
} catch (Exception $e) {
    echo "[" . date('Y-m-d H:i:s') . "] ERROR: " . $e->getMessage() . "\n";
} finally {
    // Usuń lock
    if (file_exists($lockFile)) {
        unlink($lockFile);
    }
}

/**
 * Sprawdź czy zadanie powinno być uruchomione (prosty parser cron)
 */
function shouldRunJob(string $schedule, DateTime $now): bool
{
    $parts = preg_split('/\s+/', trim($schedule));
    
    if (count($parts) !== 5) {
        return false;
    }
    
    list($minute, $hour, $day, $month, $weekday) = $parts;
    
    return matchCronField($minute, (int)$now->format('i')) &&
           matchCronField($hour, (int)$now->format('H')) &&
           matchCronField($day, (int)$now->format('d')) &&
           matchCronField($month, (int)$now->format('m')) &&
           matchCronField($weekday, (int)$now->format('w'));
}

/**
 * Sprawdź czy wartość pasuje do pola cron
 */
function matchCronField(string $field, int $value): bool
{
    // Każda wartość
    if ($field === '*') {
        return true;
    }
    
    // Konkretna wartość
    if (is_numeric($field)) {
        return (int)$field === $value;
    }
    
    // Zakres (np. 1-5)
    if (strpos($field, '-') !== false) {
        list($start, $end) = explode('-', $field);
        return $value >= (int)$start && $value <= (int)$end;
    }
    
    // Lista (np. 1,3,5)
    if (strpos($field, ',') !== false) {
        $values = array_map('intval', explode(',', $field));
        return in_array($value, $values);
    }
    
    // Co N (np. */5)
    if (strpos($field, '/') !== false) {
        list($base, $step) = explode('/', $field);
        $step = (int)$step;
        if ($base === '*') {
            return $value % $step === 0;
        }
    }
    
    return false;
}

/**
 * Wykonaj zadanie
 */
function executeJob(string $type, Database $db, Logger $logger, string $basePath): array
{
    switch ($type) {
        case 'cleanup_logs':
            return jobCleanupLogs($db);
            
        case 'cleanup_sessions':
            return jobCleanupSessions($db);
            
        case 'backup_database':
            return jobBackupDatabase($basePath);
            
        case 'optimize_database':
            return jobOptimizeDatabase($db);
            
        case 'check_playlists':
            return jobCheckPlaylists($db);
            
        default:
            return ['success' => false, 'message' => 'Nieznany typ zadania: ' . $type];
    }
}

function jobCleanupLogs(Database $db): array
{
    $days = 30; // Możesz wczytać z settings
    $db->execute(
        "DELETE FROM logs WHERE created_at < datetime('now', '-' || :days || ' days')",
        ['days' => $days]
    );
    $deleted = $db->getLastStatement()->rowCount();
    
    $db->execute(
        "DELETE FROM api_logs WHERE created_at < datetime('now', '-' || :days || ' days')",
        ['days' => $days]
    );
    $deletedApi = $db->getLastStatement()->rowCount();
    
    return ['success' => true, 'message' => "Usunięto {$deleted} logów aplikacji i {$deletedApi} logów API"];
}

function jobCleanupSessions(Database $db): array
{
    $db->execute("DELETE FROM sessions WHERE expires_at < datetime('now')");
    $deleted1 = $db->getLastStatement()->rowCount();
    
    $db->execute("DELETE FROM active_sessions WHERE expires_at < datetime('now')");
    $deleted2 = $db->getLastStatement()->rowCount();
    
    $db->execute("DELETE FROM login_attempts WHERE blocked_until < datetime('now')");
    $deleted3 = $db->getLastStatement()->rowCount();
    
    return ['success' => true, 'message' => "Usunięto sesje: {$deleted1} + {$deleted2}, blokady IP: {$deleted3}"];
}

function jobBackupDatabase(string $basePath): array
{
    $dbPath = $basePath . '/panel/db/playlist.db';
    $backupDir = $basePath . '/backups';
    
    if (!file_exists($dbPath)) {
        return ['success' => false, 'message' => 'Baza danych nie istnieje'];
    }
    
    if (!is_dir($backupDir)) {
        if (!@mkdir($backupDir, 0755, true)) {
            return ['success' => false, 'message' => 'Nie można utworzyć katalogu backupów'];
        }
    }
    
    $backupFile = $backupDir . '/database_' . date('Y-m-d_His') . '.sqlite';
    
    if (@copy($dbPath, $backupFile)) {
        // Usuń stare backupy (zostaw ostatnie 7)
        $backups = glob($backupDir . '/database_*.sqlite');
        usort($backups, fn($a, $b) => filemtime($b) - filemtime($a));
        
        foreach (array_slice($backups, 7) as $old) {
            @unlink($old);
        }
        
        return ['success' => true, 'message' => 'Backup utworzony: ' . basename($backupFile)];
    }
    
    return ['success' => false, 'message' => 'Błąd kopiowania bazy danych'];
}

function jobOptimizeDatabase(Database $db): array
{
    $db->execute("VACUUM");
    $db->execute("ANALYZE");
    
    return ['success' => true, 'message' => 'Baza zoptymalizowana'];
}

function jobCheckPlaylists(Database $db): array
{
    // Placeholder - można rozbudować o sprawdzanie czy serwery IPTV odpowiadają
    $playlists = $db->fetchAll("SELECT COUNT(*) as count FROM playlists WHERE is_active = 1");
    $count = $playlists[0]['count'] ?? 0;
    
    return ['success' => true, 'message' => "Aktywnych playlist: {$count}"];
}
