<?php
/**
 * =====================================================================
 * NADIPLAYER  -  cPanel PHP backend  -  Scheduled tasks (cron)
 * =====================================================================
 * Author     : Nadifi abdessamad  <nadiplayer@nadi.kr>
 * Website    : https://nadi.kr
 * Server     : https://prmtly.com
 * Copyright  : (c) 2026 Nadi Player. All rights reserved.
 * =====================================================================
 *
 * One endpoint, hit from cPanel → "Cron Jobs":
 *
 *   curl -fsS "https://prmtly.com/api/cron?key=YOUR_CRON_SECRET_KEY"
 *
 * Recommended cron schedule (cPanel):
 *   - Every 6 hours:  0 *\/6 * * *   for routine cleanup
 *   - OR Daily 03:00: 0 3 * * *      for subscription expiry sweeps
 *
 * What this task does (idempotent, safe to run repeatedly):
 *   1. Demote expired subscribers   — flips users.role from 'user'/'premium'
 *                                     to 'expired' when expiry_at is in the
 *                                     past. Existing admin accounts are NEVER
 *                                     touched.
 *   2. Prune stale heartbeats       — deletes stats_heartbeats rows whose
 *                                     updated_at_ts is older than 24h (the
 *                                     /admin/stats/live dashboard only ever
 *                                     reads the last 60s, so anything older
 *                                     just bloats the table).
 *   3. Prune broadcast logs         — deletes notifications.broadcasts rows
 *                                     older than 90 days.
 *   4. Prune device trials          — deletes device_trials rows whose
 *                                     last_seen is older than 180 days
 *                                     (a device that hasn't checked in for
 *                                     half a year is effectively gone).
 *
 * Security:
 *   - Requires `?key=CRON_SECRET_KEY` query string (defined in config.php).
 *     We rely on hash_equals() for constant-time comparison so an attacker
 *     can't time-attack the secret.
 *   - Refuses to run if CRON_SECRET_KEY is empty (fail-closed).
 *   - Returns JSON with a summary of work done so the admin can pipe the
 *     cPanel cron output to email and read at a glance whether things ran.
 */

if (!function_exists('route_cron_run')) {

    function route_cron_run(): void {
        // ── 1. Authenticate (constant-time) ──────────────────────────
        $configured = defined('CRON_SECRET_KEY') ? (string) CRON_SECRET_KEY : '';
        if ($configured === '') {
            json_error(503, 'Cron is disabled: CRON_SECRET_KEY is not configured in config.php');
        }
        $provided = (string) ($_GET['key'] ?? '');
        if ($provided === '' || !hash_equals($configured, $provided)) {
            // Deliberately bland response — don't leak that the endpoint exists.
            json_error(401, 'Unauthorized');
        }

        $started_at = microtime(true);
        $now_iso    = gmdate('Y-m-d\TH:i:s\Z');
        $report = [
            'ok'                      => true,
            'server'                  => 'https://prmtly.com',
            'ran_at'                  => $now_iso,
            'expired_users_demoted'   => 0,
            'stale_heartbeats_pruned' => 0,
            'old_broadcasts_pruned'   => 0,
            'old_device_trials_pruned'=> 0,
            'errors'                  => [],
        ];

        $pdo = db();

        // ── 2. Demote expired subscribers ───────────────────────────
        // Compare ISO-8601 strings lexicographically (works because the
        // app stores expiry_at as "YYYY-mm-ddTHH:MM:SS.uuuuuu+00:00").
        try {
            $stmt = $pdo->prepare(
                "UPDATE users
                    SET role = 'expired'
                  WHERE role IN ('user', 'premium', 'trial')
                    AND expiry_at IS NOT NULL
                    AND expiry_at <> ''
                    AND expiry_at < :now"
            );
            $stmt->execute([':now' => $now_iso]);
            $report['expired_users_demoted'] = $stmt->rowCount();
        } catch (Throwable $e) {
            $report['errors'][] = 'expired_users: ' . $e->getMessage();
        }

        // ── 3. Prune stale heartbeats (>24h) ────────────────────────
        try {
            $cutoff = time() - (24 * 60 * 60);
            $stmt = $pdo->prepare('DELETE FROM stats_heartbeats WHERE updated_at_ts < :cutoff');
            $stmt->execute([':cutoff' => $cutoff]);
            $report['stale_heartbeats_pruned'] = $stmt->rowCount();
        } catch (Throwable $e) {
            // Table may not exist on a brand-new install — that's fine.
            if (stripos($e->getMessage(), "doesn't exist") === false &&
                stripos($e->getMessage(), 'no such table') === false) {
                $report['errors'][] = 'heartbeats: ' . $e->getMessage();
            }
        }

        // ── 4. Prune old broadcast logs (>90 days) ──────────────────
        try {
            $cutoff_iso = gmdate('Y-m-d\TH:i:s\Z', time() - (90 * 86400));
            $stmt = $pdo->prepare('DELETE FROM broadcasts WHERE created_at < :cutoff');
            $stmt->execute([':cutoff' => $cutoff_iso]);
            $report['old_broadcasts_pruned'] = $stmt->rowCount();
        } catch (Throwable $e) {
            if (stripos($e->getMessage(), "doesn't exist") === false &&
                stripos($e->getMessage(), 'no such table') === false) {
                $report['errors'][] = 'broadcasts: ' . $e->getMessage();
            }
        }

        // ── 5. Prune ghost device-trials (>180 days no check-in) ────
        try {
            $cutoff_iso = gmdate('Y-m-d\TH:i:s\Z', time() - (180 * 86400));
            $stmt = $pdo->prepare('DELETE FROM device_trials WHERE last_seen < :cutoff');
            $stmt->execute([':cutoff' => $cutoff_iso]);
            $report['old_device_trials_pruned'] = $stmt->rowCount();
        } catch (Throwable $e) {
            if (stripos($e->getMessage(), "doesn't exist") === false &&
                stripos($e->getMessage(), 'no such table') === false) {
                $report['errors'][] = 'device_trials: ' . $e->getMessage();
            }
        }

        // ── 6. World Cup (JSON) auto-sync + match alerts ────────────
        // Modular: only runs when the lib is present AND the module is
        // enabled (wc_cron_tick checks the enable flag internally). Never
        // touches streaming / ads / xtream logic.
        try {
            @include_once __DIR__ . '/../sports/lib_worldcup.php';
            if (function_exists('wc_cron_tick')) {
                $report['worldcup'] = wc_cron_tick();
                if (!empty($report['worldcup']['errors'])) {
                    foreach ($report['worldcup']['errors'] as $we) {
                        $report['errors'][] = 'worldcup: ' . $we;
                    }
                }
            }
        } catch (Throwable $e) {
            $report['errors'][] = 'worldcup: ' . $e->getMessage();
        }

        $report['duration_ms'] = (int) round((microtime(true) - $started_at) * 1000);
        if (!empty($report['errors'])) {
            $report['ok'] = false;
        }
        send_json($report);
    }
}
