<?php
/**
 * =====================================================================
 * Nadiplayer  -  Standalone PHP Admin Panel
 * =====================================================================
 * Author     : Nadifi abdessamad  <nadiplayer@nadi.kr>
 * Website    : https://nadi.kr
 * Copyright  : (c) 2026 Nadi Player. All rights reserved.
 * Repository : https://github.com/ghizlanefit-cell/Nadiplayer-v1.2.5-ads
 * =====================================================================
 * Proprietary and confidential. Unauthorized copying of this file,
 * via any medium, is strictly prohibited.
 * =====================================================================
 *
 * Drop-in browser admin panel for cPanel. Upload this entire folder to:
 *     public_html/<your-domain>/nadiplayer/admin_panel/
 *
 * It reads the SAME config.php and uses the SAME database as the API in
 * ../api/, so any change here is instantly reflected in the mobile app
 * (and vice-versa).
 *
 * Login with the credentials from ../api/config.php → ADMIN_USERNAME /
 * ADMIN_PASSWORD (default: admin / admin123).
 */

// Bring in the API's config + DB helper. We assume this admin panel sits
// next to the api/ folder (same parent). If you renamed/moved things,
// edit the path below.
require_once __DIR__ . '/../api/config.php';
require_once __DIR__ . '/../api/lib/util.php'; // db.php uses json_error() on connection failure
require_once __DIR__ . '/../api/lib/db.php';
require_once __DIR__ . '/../api/routes/notifications.php'; // FCM helpers + fcm_ensure_schema
require_once __DIR__ . '/../api/routes/stats.php';         // heartbeat + stats_ensure_schema
require_once __DIR__ . '/../api/routes/worldcup.php';      // fixtures + auto-notify helpers

// Make sure all required tables exist on first request (idempotent).
// Without this, the dashboard query "SELECT FROM users" crashes with
// "Table 'nadiplayer.users' doesn't exist" the very first time a fresh
// install loads /admin_panel/.
ensure_schema();
ensure_admin_seed();
fcm_ensure_schema();
stats_ensure_schema();
worldcup_ensure_schema();

session_set_cookie_params(['httponly' => true, 'samesite' => 'Lax']);
session_name('NADI_ADMIN_PANEL');
session_start();

date_default_timezone_set('UTC');

// ── Helpers ───────────────────────────────────────────────────────────
function admin_logged_in(): bool {
    return !empty($_SESSION['admin_user']);
}

function require_login(): void {
    if (!admin_logged_in()) {
        header('Location: index.php?page=login');
        exit;
    }
}

function csrf_token(): string {
    if (empty($_SESSION['csrf'])) {
        $_SESSION['csrf'] = bin2hex(random_bytes(16));
    }
    return $_SESSION['csrf'];
}

function csrf_check(): void {
    $given = $_POST['csrf'] ?? '';
    if (!hash_equals($_SESSION['csrf'] ?? '', $given)) {
        http_response_code(400);
        die('Bad CSRF token. Reload the page and try again.');
    }
}

function flash(string $msg, string $type = 'info'): void {
    $_SESSION['flash'][] = ['msg' => $msg, 'type' => $type];
}

function take_flashes(): array {
    $f = $_SESSION['flash'] ?? [];
    unset($_SESSION['flash']);
    return $f;
}

function h($s): string {
    return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8');
}

function get_settings(): array {
    $pdo = db();
    $row = $pdo->query("SELECT data FROM settings WHERE id = 'global'")->fetch();
    if (!$row) {
        return [];
    }
    $data = json_decode($row['data'], true);
    return is_array($data) ? $data : [];
}

function save_settings(array $data): void {
    $pdo = db();
    $stmt = $pdo->prepare(
        "INSERT INTO settings (id, data) VALUES ('global', ?)
         ON DUPLICATE KEY UPDATE data = VALUES(data)"
    );
    $stmt->execute([json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)]);
}

// ── Routing ───────────────────────────────────────────────────────────
$page   = $_GET['page']   ?? 'dashboard';
$action = $_POST['action'] ?? '';

// Login form submit
if ($page === 'login' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    csrf_check();
    $u = strtolower(trim($_POST['username'] ?? ''));
    $p = $_POST['password'] ?? '';
    if ($u === strtolower(ADMIN_USERNAME) && hash_equals(ADMIN_PASSWORD, $p)) {
        $_SESSION['admin_user'] = $u;
        $_SESSION['csrf'] = bin2hex(random_bytes(16)); // rotate
        flash('Welcome back, ' . $u . '!', 'success');
        header('Location: index.php?page=dashboard');
        exit;
    }
    flash('Invalid credentials.', 'error');
}

// Logout
if ($page === 'logout') {
    $_SESSION = [];
    session_destroy();
    header('Location: index.php?page=login');
    exit;
}

// Everything past this point requires login.
if ($page !== 'login') {
    require_login();
}

// ── Action handlers (POST) ─────────────────────────────────────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST' && admin_logged_in()) {
    csrf_check();

    if ($action === 'save_settings') {
        $cur = get_settings();
        $cur['whatsapp_number']         = trim((string)($_POST['whatsapp_number'] ?? ''));
        $cur['whatsapp_message']        = trim((string)($_POST['whatsapp_message'] ?? ''));
        $cur['support_email']           = trim((string)($_POST['support_email'] ?? ''));
        $cur['privacy_policy_text']     = (string)($_POST['privacy_policy_text'] ?? '');
        $cur['shared_xtream_server']    = trim((string)($_POST['shared_xtream_server'] ?? ''));
        $cur['shared_xtream_username']  = trim((string)($_POST['shared_xtream_username'] ?? ''));
        $cur['shared_xtream_password']  = (string)($_POST['shared_xtream_password'] ?? '');
        $cur['external_player_enabled'] = !empty($_POST['external_player_enabled']);
        $cur['player_notice_enabled']   = !empty($_POST['player_notice_enabled']);
        $cur['creator_name']            = trim((string)($_POST['creator_name'] ?? ''));
        $cur['creator_role']            = trim((string)($_POST['creator_role'] ?? ''));
        $cur['creator_avatar_url']      = trim((string)($_POST['creator_avatar_url'] ?? ''));
        $cur['creator_bio']             = (string)($_POST['creator_bio'] ?? '');
        $cur['push_enabled']            = !empty($_POST['push_enabled']);
        $cur['push_target_app_version'] = trim((string)($_POST['push_target_app_version'] ?? ''));
        save_settings($cur);
        flash('General settings saved.', 'success');
        header('Location: index.php?page=settings');
        exit;
    }

    if ($action === 'save_update') {
        $cur = get_settings();
        $cur['update_enabled']        = !empty($_POST['update_enabled']);
        $cur['app_version']           = trim((string)($_POST['app_version'] ?? '1.0.0'));
        $cur['min_supported_version'] = trim((string)($_POST['min_supported_version'] ?? '1.0.0'));
        $cur['update_apk_url']        = trim((string)($_POST['update_apk_url'] ?? ''));
        $cur['update_play_url']       = trim((string)($_POST['update_play_url'] ?? ''));
        $cur['update_changelog']      = (string)($_POST['update_changelog'] ?? '');
        $delay = (int)($_POST['popup_delay_seconds'] ?? 10);
        if ($delay < 0)   $delay = 0;
        if ($delay > 600) $delay = 600;
        $cur['popup_delay_seconds']   = $delay;
        save_settings($cur);
        flash('Update popup settings saved.', 'success');
        header('Location: index.php?page=update');
        exit;
    }

    if ($action === 'save_worldcup_match') {
        $id = trim((string)($_POST['match_id'] ?? ''));
        if ($id === '') {
            $id = 'wc-' . substr(bin2hex(random_bytes(8)), 0, 12);
        }

        $kickoff = trim((string)($_POST['kickoff_iso'] ?? ''));
        if ($kickoff === '') {
            $kickoff = now_iso();
        }

        $all = worldcup_list_matches();
        $map = [];
        foreach ($all as $m) {
            $mid = (string)($m['id'] ?? '');
            if ($mid !== '') $map[$mid] = $m;
        }

        $map[$id] = [
            'id' => $id,
            'kickoffIso' => $kickoff,
            'status' => strtoupper(trim((string)($_POST['status'] ?? 'UPCOMING'))),
            'group' => trim((string)($_POST['group_name'] ?? '')),
            'venue' => trim((string)($_POST['venue'] ?? '')),
            'home' => [
                'name' => trim((string)($_POST['home_name'] ?? '')),
                'code' => strtolower(trim((string)($_POST['home_code'] ?? ''))),
            ],
            'away' => [
                'name' => trim((string)($_POST['away_name'] ?? '')),
                'code' => strtolower(trim((string)($_POST['away_code'] ?? ''))),
            ],
            'homeScore' => ($_POST['home_score'] ?? '') === '' ? null : (int)$_POST['home_score'],
            'awayScore' => ($_POST['away_score'] ?? '') === '' ? null : (int)$_POST['away_score'],
            'homePenalty' => ($_POST['home_penalty'] ?? '') === '' ? null : (int)$_POST['home_penalty'],
            'awayPenalty' => ($_POST['away_penalty'] ?? '') === '' ? null : (int)$_POST['away_penalty'],
            'broadcaster' => trim((string)($_POST['broadcaster'] ?? '')),
            'liveLabel' => trim((string)($_POST['live_label'] ?? '')),
        ];

        try {
            $result = worldcup_upsert_matches(array_values($map), true);
            $notif_count = is_array($result['notifications'] ?? null) ? count($result['notifications']) : 0;
            flash('Match saved. Total fixtures: ' . (int)($result['saved'] ?? count($map)) . '. Notifications queued: ' . $notif_count . '.', 'success');
        } catch (Throwable $e) {
            flash('Failed to save match: ' . $e->getMessage(), 'error');
        }

        header('Location: index.php?page=worldcup');
        exit;
    }

    if ($action === 'delete_worldcup_match') {
        $id = trim((string)($_POST['match_id'] ?? ''));
        $all = worldcup_list_matches();
        $next = [];
        foreach ($all as $m) {
            if ((string)($m['id'] ?? '') !== $id) $next[] = $m;
        }

        try {
            worldcup_upsert_matches($next, false);
            flash('Match removed.', 'success');
        } catch (Throwable $e) {
            flash('Delete failed: ' . $e->getMessage(), 'error');
        }

        header('Location: index.php?page=worldcup');
        exit;
    }

    if ($action === 'import_worldcup_json') {
        $raw = trim((string)($_POST['matches_json'] ?? ''));
        if ($raw === '') {
            flash('Paste JSON first.', 'error');
            header('Location: index.php?page=worldcup');
            exit;
        }

        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) {
            flash('Invalid JSON. Expecting an array of matches or {"matches": [...]}.', 'error');
            header('Location: index.php?page=worldcup');
            exit;
        }

        $matches = $decoded['matches'] ?? $decoded;
        if (!is_array($matches)) {
            flash('JSON format is not valid for matches.', 'error');
            header('Location: index.php?page=worldcup');
            exit;
        }

        try {
            $result = worldcup_upsert_matches($matches, true);
            $notif_count = is_array($result['notifications'] ?? null) ? count($result['notifications']) : 0;
            flash('Imported ' . (int)($result['saved'] ?? 0) . ' matches. Notifications queued: ' . $notif_count . '.', 'success');
        } catch (Throwable $e) {
            flash('Import failed: ' . $e->getMessage(), 'error');
        }

        header('Location: index.php?page=worldcup');
        exit;
    }

    if ($action === 'import_worldcup_football_data') {
        $api_key = trim((string)($_POST['football_data_api_key'] ?? ''));
        $competition = trim((string)($_POST['football_data_competition'] ?? 'WC'));
        if ($api_key === '') {
            flash('football-data.org API key is required.', 'error');
            header('Location: index.php?page=worldcup');
            exit;
        }

        $url = 'https://api.football-data.org/v4/competitions/' . rawurlencode($competition) . '/matches?status=SCHEDULED,LIVE,IN_PLAY,PAUSED,FINISHED';
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 20,
            CURLOPT_HTTPHEADER => [
                'X-Auth-Token: ' . $api_key,
                'Accept: application/json',
            ],
        ]);
        $resp = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $err  = curl_error($ch);
        curl_close($ch);

        if ($resp === false || $code >= 400) {
            flash('football-data fetch failed: HTTP ' . $code . ' ' . $err, 'error');
            header('Location: index.php?page=worldcup');
            exit;
        }

        $j = json_decode((string)$resp, true);
        $rows = is_array($j['matches'] ?? null) ? $j['matches'] : [];
        if (!$rows) {
            flash('No matches returned from football-data.org.', 'info');
            header('Location: index.php?page=worldcup');
            exit;
        }

        $mapped = [];
        foreach ($rows as $m) {
            $mid = (string)($m['id'] ?? '');
            if ($mid === '') continue;
            $fdStatus = strtoupper((string)($m['status'] ?? 'SCHEDULED'));
            $statusMap = [
                'SCHEDULED' => 'UPCOMING',
                'TIMED' => 'UPCOMING',
                'IN_PLAY' => 'LIVE',
                'LIVE' => 'LIVE',
                'PAUSED' => 'HT',
                'FINISHED' => 'FT',
            ];
            $status = $statusMap[$fdStatus] ?? 'UPCOMING';

            $mapped[] = [
                'id' => 'fd-' . $mid,
                'kickoffIso' => (string)($m['utcDate'] ?? now_iso()),
                'status' => $status,
                'group' => (string)($m['stage'] ?? ''),
                'venue' => (string)($m['venue'] ?? ''),
                'home' => [
                    'name' => (string)(($m['homeTeam']['name'] ?? 'Home')),
                    'code' => strtolower((string)($m['homeTeam']['tla'] ?? '')),
                ],
                'away' => [
                    'name' => (string)(($m['awayTeam']['name'] ?? 'Away')),
                    'code' => strtolower((string)($m['awayTeam']['tla'] ?? '')),
                ],
                'homeScore' => isset($m['score']['fullTime']['home']) ? (int)$m['score']['fullTime']['home'] : null,
                'awayScore' => isset($m['score']['fullTime']['away']) ? (int)$m['score']['fullTime']['away'] : null,
                'broadcaster' => '',
                'liveLabel' => '',
            ];
        }

        try {
            $result = worldcup_upsert_matches($mapped, true);
            $notif_count = is_array($result['notifications'] ?? null) ? count($result['notifications']) : 0;
            flash('Imported ' . (int)($result['saved'] ?? 0) . ' matches from football-data.org. Notifications queued: ' . $notif_count . '.', 'success');
        } catch (Throwable $e) {
            flash('football-data import failed: ' . $e->getMessage(), 'error');
        }

        header('Location: index.php?page=worldcup');
        exit;
    }

    if ($action === 'create_user') {
        $u    = strtolower(trim((string)($_POST['username'] ?? '')));
        $p    = (string)($_POST['password'] ?? '');
        $days = max(1, min(3650, (int)($_POST['days'] ?? 30)));
        $mode = in_array($_POST['xtream_mode'] ?? 'shared', ['shared', 'own'], true)
                ? $_POST['xtream_mode'] : 'shared';

        if (strlen($u) < 2) { flash('Username too short.', 'error'); header('Location: index.php?page=users'); exit; }
        if (strlen($p) < 3) { flash('Password too short.', 'error'); header('Location: index.php?page=users'); exit; }

        $pdo = db();
        $exists = $pdo->prepare('SELECT 1 FROM users WHERE username = ?');
        $exists->execute([$u]);
        if ($exists->fetch()) {
            flash('Username already exists.', 'error');
        } else {
            $now    = new DateTime('now', new DateTimeZone('UTC'));
            $expiry = (clone $now)->modify("+{$days} days");
            $stmt = $pdo->prepare(
                "INSERT INTO users (id, username, password_hash, role, created_at, expiry_at,
                                    xtream_mode, xtream_server, xtream_username, xtream_password,
                                    live_server, live_username, live_password,
                                    vod_server, vod_username, vod_password, note)
                 VALUES (?, ?, ?, 'user', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
            );
            $stmt->execute([
                bin2hex(random_bytes(16)),
                $u,
                password_hash($p, PASSWORD_BCRYPT),
                $now->format('Y-m-d\TH:i:s.uP'),
                $expiry->format('Y-m-d\TH:i:s.uP'),
                $mode,
                trim((string)($_POST['xtream_server']   ?? '')),
                trim((string)($_POST['xtream_username'] ?? '')),
                (string)($_POST['xtream_password'] ?? ''),
                trim((string)($_POST['live_server']   ?? '')),
                trim((string)($_POST['live_username'] ?? '')),
                (string)($_POST['live_password'] ?? ''),
                trim((string)($_POST['vod_server']   ?? '')),
                trim((string)($_POST['vod_username'] ?? '')),
                (string)($_POST['vod_password'] ?? ''),
                trim((string)($_POST['note'] ?? '')),
            ]);
            flash('User "' . $u . '" created.', 'success');
        }
        header('Location: index.php?page=users');
        exit;
    }

    if ($action === 'update_user') {
        $id   = (string)($_POST['user_id'] ?? '');
        $patch = [];
        if (!empty($_POST['new_password'])) {
            $patch['password_hash'] = password_hash((string)$_POST['new_password'], PASSWORD_BCRYPT);
        }
        if (!empty($_POST['extend_days'])) {
            $extra = (int)$_POST['extend_days'];
            $pdo = db();
            $row = $pdo->prepare('SELECT expiry_at FROM users WHERE id = ?');
            $row->execute([$id]);
            $r = $row->fetch();
            try {
                $base = $r && $r['expiry_at'] ? new DateTime($r['expiry_at']) : new DateTime('now', new DateTimeZone('UTC'));
            } catch (Throwable $e) {
                $base = new DateTime('now', new DateTimeZone('UTC'));
            }
            $now = new DateTime('now', new DateTimeZone('UTC'));
            if ($base < $now) $base = $now;
            $patch['expiry_at'] = $base->modify($extra . ' days')->format('Y-m-d\TH:i:s.uP');
        }
        foreach (['xtream_mode', 'xtream_server', 'xtream_username', 'xtream_password',
                  'live_server', 'live_username', 'live_password',
                  'vod_server', 'vod_username', 'vod_password', 'note'] as $f) {
            if (isset($_POST[$f])) $patch[$f] = (string)$_POST[$f];
        }
        if ($patch) {
            $sets = []; $vals = [];
            foreach ($patch as $k => $v) { $sets[] = "$k = ?"; $vals[] = $v; }
            $vals[] = $id;
            $up = db()->prepare('UPDATE users SET ' . implode(', ', $sets) . ' WHERE id = ?');
            $up->execute($vals);
        }
        flash('User updated.', 'success');
        header('Location: index.php?page=users');
        exit;
    }

    if ($action === 'delete_user') {
        $id = (string)($_POST['user_id'] ?? '');
        $del = db()->prepare("DELETE FROM users WHERE id = ? AND role = 'user'");
        $del->execute([$id]);
        flash($del->rowCount() ? 'User deleted.' : 'User not found.', $del->rowCount() ? 'success' : 'error');
        header('Location: index.php?page=users');
        exit;
    }

    // ── Shared Profile management (hybrid engine) ────────────────────
    if ($action === 'add_shared_profile') {
        $cur = get_settings();
        $profiles = $cur['shared_profiles'] ?? [];
        $profiles[] = [
            'id'                => bin2hex(random_bytes(8)),
            'name'              => trim((string)($_POST['sp_name'] ?? '')) ?: 'Profile',
            'kind'              => in_array($_POST['sp_kind'] ?? 'xtream', ['xtream', 'm3u', 'stalker'], true) ? $_POST['sp_kind'] : 'xtream',
            'server'            => trim((string)($_POST['sp_server'] ?? '')),
            'username'          => trim((string)($_POST['sp_username'] ?? '')),
            'password'          => (string)($_POST['sp_password'] ?? ''),
            'm3u_url'           => '',
            'm3u_content'       => '',
            'portal_url'        => '',
            'mac_address'       => '',
            // Hybrid Source A (Live TV)
            'source_a_server'   => trim((string)($_POST['sp_source_a_server'] ?? '')),
            'source_a_username' => trim((string)($_POST['sp_source_a_username'] ?? '')),
            'source_a_password' => (string)($_POST['sp_source_a_password'] ?? ''),
            'source_a_kind'     => 'xtream',
            // Hybrid Source B (Movies & Series)
            'source_b_server'   => trim((string)($_POST['sp_source_b_server'] ?? '')),
            'source_b_username' => trim((string)($_POST['sp_source_b_username'] ?? '')),
            'source_b_password' => (string)($_POST['sp_source_b_password'] ?? ''),
            'source_b_kind'     => 'xtream',
        ];
        $cur['shared_profiles'] = $profiles;
        save_settings($cur);
        flash('Shared profile added.', 'success');
        header('Location: index.php?page=settings');
        exit;
    }

    if ($action === 'delete_shared_profile') {
        $cur = get_settings();
        $profiles = $cur['shared_profiles'] ?? [];
        $idx = (int)($_POST['profile_idx'] ?? -1);
        if (isset($profiles[$idx])) {
            array_splice($profiles, $idx, 1);
            $cur['shared_profiles'] = $profiles;
            save_settings($cur);
            flash('Shared profile deleted.', 'success');
        } else {
            flash('Profile not found.', 'error');
        }
        header('Location: index.php?page=settings');
        exit;
    }

    // ── Master Proxy Toggle ──────────────────────────────────────────
    if ($action === 'toggle_proxy') {
        $cur = get_settings();
        $cur['proxy_enabled'] = !empty($_POST['proxy_enabled']);
        save_settings($cur);
        flash('Stream proxy ' . ($cur['proxy_enabled'] ? 'activated' : 'deactivated') . '.', 'success');
        header('Location: index.php?page=dashboard');
        exit;
    }

    if ($action === 'save_ads') {
        // Persist AdMob config inside the shared settings document.
        $s = get_settings();
        $s['ads_enabled']      = !empty($_POST['ads_enabled']);
        // BUG-2 FIX (2026-05) — Users-vs-Guests sub-toggles.
        // We use array_key_exists + !empty so that:
        //   * unchecked checkbox + hidden "0" sibling → saved as false
        //   * checked checkbox (value="1") → saved as true
        //   * legacy form without the hidden input → key absent → falls
        //     back to the previously-saved value (no silent reset).
        if (array_key_exists('ads_enabled_for_users', $_POST)) {
            $s['ads_enabled_for_users'] = !empty($_POST['ads_enabled_for_users']);
        }
        if (array_key_exists('ads_enabled_for_guests', $_POST)) {
            $s['ads_enabled_for_guests'] = !empty($_POST['ads_enabled_for_guests']);
        }
        $s['ads_testing_mode'] = !empty($_POST['ads_testing_mode']);
        $s['ads_app_id']           = trim((string)($_POST['ads_app_id'] ?? ''));
        $s['ads_banner_id']        = trim((string)($_POST['ads_banner_id'] ?? ''));
        $s['ads_interstitial_id']  = trim((string)($_POST['ads_interstitial_id'] ?? ''));
        $s['ads_native_id']        = trim((string)($_POST['ads_native_id'] ?? ''));
        // BUG FIX (2026-01): persist the new Rewarded Ad Unit ID
        // field exposed in views/ads.php. Without this assignment
        // the input value was silently dropped on save.
        $s['ads_rewarded_id']      = trim((string)($_POST['ads_rewarded_id'] ?? ''));
        $every = (int)($_POST['ads_interstitial_every'] ?? 5);
        if ($every < 0) $every = 0;
        if ($every > 100) $every = 100;
        $s['ads_interstitial_every'] = $every;
        save_settings($s);
        flash('Ad settings saved. Devices will pick them up on next app launch.', 'success');
        header('Location: index.php?page=ads');
        exit;
    }

    // FEATURE (2026-01) — save the Smart-TV House Banner config.
    if ($action === 'save_tv_banner') {
        $s = get_settings();
        $s['tv_banner_enabled'] = !empty($_POST['tv_banner_enabled']);
        $type = (string)($_POST['tv_banner_type'] ?? 'image');
        $s['tv_banner_type']      = in_array($type, ['image', 'html'], true) ? $type : 'image';
        $s['tv_banner_html']      = (string)($_POST['tv_banner_html'] ?? '');
        $s['tv_banner_image_url'] = trim((string)($_POST['tv_banner_image_url'] ?? ''));
        $s['tv_banner_click_url'] = trim((string)($_POST['tv_banner_click_url'] ?? ''));
        save_settings($s);
        flash('Smart-TV banner saved. Tizen / webOS devices will pick it up on next launch.', 'success');
        header('Location: index.php?page=ads');
        exit;
    }

    if ($action === 'reactivate_tokens') {
        // Used after fixing a Firebase project mismatch — re-enables every
        // previously-pruned FCM token so the next broadcast retries them.
        $stmt = db()->prepare(
            'UPDATE fcm_tokens SET is_active = 1, deactivated_at = NULL WHERE is_active = 0'
        );
        $stmt->execute();
        $n = $stmt->rowCount();
        flash("Reactivated $n token(s). Try sending a broadcast now.", $n > 0 ? 'success' : 'info');
        header('Location: index.php?page=broadcast');
        exit;
    }

    if ($action === 'cleanup_old_push_tokens') {
        // Keep only latest token per device_id and deactivate rows from older
        // app versions if admin requests cleanup.
        fcm_ensure_schema();
        $keep_version = trim((string)($_POST['keep_app_version'] ?? ''));

        // 1) Optional version-based deactivate (safe, non-destructive)
        if ($keep_version !== '') {
            $stmt = db()->prepare(
                'UPDATE fcm_tokens
                 SET is_active = 0, deactivated_at = ?
                 WHERE app_version IS NOT NULL AND app_version <> ?'
            );
            $stmt->execute([gmdate('c'), $keep_version]);
        }

        // 2) Hard reset if requested (this is what user asked: back to 0)
        $hard = !empty($_POST['hard_reset_tokens']);
        $hard_legacy = !empty($_POST['hard_reset_legacy_devices']);
        if ($hard) {
            db()->exec('DELETE FROM fcm_tokens');
            if ($hard_legacy) {
                db()->exec('DELETE FROM devices');
            }
            flash(
                'Push tokens cleared (and legacy devices ' . ($hard_legacy ? 'cleared' : 'kept') . '). Open app on devices to re-register.',
                'success'
            );
            header('Location: index.php?page=broadcast');
            exit;
        }

        flash('Push token cleanup complete. Ask users to reopen app so fresh tokens register.', 'success');
        header('Location: index.php?page=broadcast');
        exit;
    }

    if ($action === 'send_broadcast') {
        // FCM helpers + schema are loaded at the top of this file already.
        try {
            // Guard: bail with a friendly flash if the service-account JSON is
            // missing — otherwise fcm_service_account() would call json_error()
            // which exits with raw JSON in the middle of an HTML admin page.
            if (!is_file(fcm_service_account_path())) {
                flash(
                    'Firebase service-account JSON is missing. Upload it to '
                    . 'api/secrets/firebase-service-account.json (chmod 600) and try again.',
                    'error'
                );
                header('Location: index.php?page=broadcast');
                exit;
            }
            $kind = ($_POST['recipient_kind'] ?? 'all') === 'user' ? 'single' : 'broadcast';
            $opts = [
                'title' => trim((string)($_POST['title'] ?? '')),
                'body'  => trim((string)($_POST['body']  ?? '')),
                'route' => trim((string)($_POST['route'] ?? '')) ?: null,
                'data'  => [],
            ];
            if ($opts['title'] === '' || $opts['body'] === '') {
                flash('Title and body are required.', 'error');
                header('Location: index.php?page=broadcast');
                exit;
            }
            if ($kind === 'single') {
                $uid = trim((string)($_POST['target_user_id'] ?? ''));
                if ($uid === '') {
                    flash('User ID is required when sending to a single user.', 'error');
                    header('Location: index.php?page=broadcast');
                    exit;
                }
                $opts['target_user_id'] = $uid;
                $target_ver = trim((string)($_POST['target_app_version'] ?? ''));
                if ($target_ver !== '') {
                    $stmt = db()->prepare(
                        'SELECT device_id, token FROM fcm_tokens WHERE user_id = ? AND is_active = 1 AND app_version = ?'
                    );
                    $stmt->execute([$uid, $target_ver]);
                    $rows = $stmt->fetchAll();
                } else {
                    $stmt = db()->prepare(
                        'SELECT device_id, token FROM fcm_tokens WHERE user_id = ? AND is_active = 1'
                    );
                    $stmt->execute([$uid]);
                    $rows = $stmt->fetchAll();
                }
            } else {
                $target_ver = trim((string)($_POST['target_app_version'] ?? ''));
                if ($target_ver !== '') {
                    $stmt = db()->prepare(
                        'SELECT device_id, token FROM fcm_tokens WHERE is_active = 1 AND app_version = ?'
                    );
                    $stmt->execute([$target_ver]);
                    $rows = $stmt->fetchAll();
                } else {
                    $rows = db()->query(
                        'SELECT device_id, token FROM fcm_tokens WHERE is_active = 1'
                    )->fetchAll();
                }
            }
            $result = fcm_send_batch($rows, $opts, $kind);
            $_SESSION['fcm_last_result'] = $result;
            $msg = sprintf(
                'Sent %d / %d · %d failed · %d pruned',
                (int)$result['sent'], (int)$result['total_tokens'],
                (int)$result['failed'], (int)$result['pruned']
            );
            flash($msg, ((int)$result['sent']) > 0 ? 'success' : 'error');
        } catch (Throwable $e) {
            flash('Broadcast failed: ' . $e->getMessage(), 'error');
        }
        header('Location: index.php?page=broadcast');
        exit;
    }
}

// ── Render ────────────────────────────────────────────────────────────
require __DIR__ . '/views/layout.php';
