<?php
// includes/functions.php

/**
 * SQLite (PDO) com criação automática:
 * - data/ltapp.db
 * - Tabelas: app_branding, app_notes, app_urls, app_settings, app_users, app_theme
 * - NOVO: app_device_keys (device_id -> public_key)
 */

// ===== Compat PHP 7/8 =====
if (!function_exists('str_starts_with')) {
    function str_starts_with(string $haystack, string $needle): bool {
        return $needle === '' || strncmp($haystack, $needle, strlen($needle)) === 0;
    }
}

const SQLITE_DIR  = __DIR__ . '/../data';
const SQLITE_FILE = SQLITE_DIR . '/ltapp.db';

/** Conexão PDO (SQLite) singleton + init */
function db(): PDO {
    static $pdo;
    if ($pdo instanceof PDO) return $pdo;

    if (!is_dir(SQLITE_DIR)) {
        @mkdir(SQLITE_DIR, 0775, true);
    }

    $dsn = 'sqlite:' . SQLITE_FILE;
    $pdo = new PDO($dsn, null, null, [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,
    ]);

    $pdo->exec("PRAGMA journal_mode = WAL;");
    $pdo->exec("PRAGMA synchronous = NORMAL;");
    $pdo->exec("PRAGMA foreign_keys = ON;");

    init_sqlite_schema($pdo);
    return $pdo;
}

/** Cria tabelas e seeds mínimos */
function init_sqlite_schema(PDO $pdo): void {
    // Branding
    $pdo->exec("
        CREATE TABLE IF NOT EXISTS app_branding (
            id             INTEGER PRIMARY KEY AUTOINCREMENT,
            logo_url       TEXT NOT NULL DEFAULT '',
            background_url TEXT NOT NULL DEFAULT '',
            is_active      INTEGER NOT NULL DEFAULT 1,
            created_at     TEXT NOT NULL DEFAULT (datetime('now'))
        );
    ");

    // Notes
    $pdo->exec("
        CREATE TABLE IF NOT EXISTS app_notes (
            id           INTEGER PRIMARY KEY AUTOINCREMENT,
            note_title   TEXT NOT NULL DEFAULT '',
            note_content TEXT NOT NULL DEFAULT '',
            is_active    INTEGER NOT NULL DEFAULT 1,
            created_at   TEXT NOT NULL DEFAULT (datetime('now'))
        );
    ");

    // URLs (DNS)
    $pdo->exec("
        CREATE TABLE IF NOT EXISTS app_urls (
            id         INTEGER PRIMARY KEY AUTOINCREMENT,
            name       TEXT NOT NULL DEFAULT '',
            url        TEXT NOT NULL DEFAULT '',
            sort       INTEGER NOT NULL DEFAULT 0,
            is_active  INTEGER NOT NULL DEFAULT 1,
            created_at TEXT NOT NULL DEFAULT (datetime('now'))
        );
    ");

    // Settings
    $pdo->exec("
        CREATE TABLE IF NOT EXISTS app_settings (
            id           INTEGER PRIMARY KEY AUTOINCREMENT,
            expired_date TEXT NOT NULL DEFAULT '',
            is_trial     INTEGER NOT NULL DEFAULT 0,
            pin_code     TEXT NOT NULL DEFAULT '0000',
            created_at   TEXT NOT NULL DEFAULT (datetime('now'))
        );
    ");

    // Suporte / WhatsApp QR Code
    $pdo->exec("
        CREATE TABLE IF NOT EXISTS app_support_settings (
            id INTEGER PRIMARY KEY CHECK (id = 1),
            whatsapp_number TEXT NOT NULL DEFAULT '',
            whatsapp_message TEXT NOT NULL DEFAULT 'Ola, vim pelo aplicativo. Preciso de suporte ou renovacao.',
            updated_at TEXT NOT NULL DEFAULT (datetime('now'))
        );
    ");

    $countSupport = (int)$pdo->query("SELECT COUNT(*) FROM app_support_settings WHERE id = 1")->fetchColumn();
    if ($countSupport === 0) {
        $pdo->prepare("INSERT INTO app_support_settings (id, whatsapp_number, whatsapp_message) VALUES (1, ?, ?)")
            ->execute(['', 'Ola, vim pelo aplicativo. Preciso de suporte ou renovacao.']);
    }

    // Users (login do painel / revenda)
    $pdo->exec("
        CREATE TABLE IF NOT EXISTS app_users (
            id               INTEGER PRIMARY KEY AUTOINCREMENT,
            username         TEXT NOT NULL UNIQUE,
            display_name     TEXT NOT NULL DEFAULT '',
            password_hash    TEXT NOT NULL,
            role             TEXT NOT NULL DEFAULT 'admin',
            panel_expires_at TEXT NOT NULL DEFAULT '',
            client_limit     INTEGER NOT NULL DEFAULT 0,
            reseller_whatsapp TEXT NOT NULL DEFAULT '',
            is_active        INTEGER NOT NULL DEFAULT 1,
            created_at       TEXT NOT NULL DEFAULT (datetime('now'))
        );
    ");

    // Theme (novo)
    $pdo->exec("
        CREATE TABLE IF NOT EXISTS app_theme (
            id         INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id    INTEGER NOT NULL,
            theme_mode TEXT NOT NULL DEFAULT 'tvexpress',
            custom_css TEXT NOT NULL DEFAULT '',
            created_at TEXT NOT NULL DEFAULT (datetime('now')),
            FOREIGN KEY (user_id) REFERENCES app_users(id) ON DELETE CASCADE
        );
    ");

    // ✅✅✅ NOVO: Device Keys (public key por device_id)
    $pdo->exec("
        CREATE TABLE IF NOT EXISTS app_device_keys (
            id            INTEGER PRIMARY KEY AUTOINCREMENT,
            device_id     TEXT NOT NULL UNIQUE,
            public_key_b64 TEXT NOT NULL DEFAULT '',
            created_at    TEXT NOT NULL DEFAULT (datetime('now')),
            updated_at    TEXT NOT NULL DEFAULT (datetime('now'))
        );
    ");

    // Clientes ativados por código (lista M3U + código único de 12 dígitos)
    $pdo->exec("
        CREATE TABLE IF NOT EXISTS app_clients (
            id               INTEGER PRIMARY KEY AUTOINCREMENT,
            name             TEXT NOT NULL DEFAULT '',
            reseller_user_id INTEGER DEFAULT NULL,
            m3u_url          TEXT NOT NULL DEFAULT '',
            server_url       TEXT NOT NULL DEFAULT '',
            username         TEXT NOT NULL DEFAULT '',
            password         TEXT NOT NULL DEFAULT '',
            activation_code  TEXT NOT NULL UNIQUE,
            card_image       TEXT NOT NULL DEFAULT '',
            expires_at       TEXT NOT NULL DEFAULT '',
            expires_source   TEXT NOT NULL DEFAULT 'xtream',
            is_active        INTEGER NOT NULL DEFAULT 1,
            created_at       TEXT NOT NULL DEFAULT (datetime('now'))
        );
    ");

    $pdo->exec("
        CREATE TABLE IF NOT EXISTS app_client_devices (
            id              INTEGER PRIMARY KEY AUTOINCREMENT,
            client_id        INTEGER NOT NULL,
            activation_code  TEXT NOT NULL DEFAULT '',
            device_id        TEXT NOT NULL DEFAULT '',
            first_seen_at    TEXT NOT NULL DEFAULT (datetime('now')),
            last_seen_at     TEXT NOT NULL DEFAULT (datetime('now')),
            UNIQUE(client_id, device_id),
            FOREIGN KEY (client_id) REFERENCES app_clients(id) ON DELETE CASCADE
        );
    ");

    $pdo->exec("
        CREATE TABLE IF NOT EXISTS app_trial_settings (
            id             INTEGER PRIMARY KEY CHECK (id = 1),
            is_enabled     INTEGER NOT NULL DEFAULT 0,
            m3u_url        TEXT NOT NULL DEFAULT '',
            server_url     TEXT NOT NULL DEFAULT '',
            username       TEXT NOT NULL DEFAULT '',
            password       TEXT NOT NULL DEFAULT '',
            duration_hours INTEGER NOT NULL DEFAULT 4,
            updated_at     TEXT NOT NULL DEFAULT (datetime('now'))
        );
    ");

    $pdo->exec("
        CREATE TABLE IF NOT EXISTS app_trial_devices (
            id            INTEGER PRIMARY KEY AUTOINCREMENT,
            device_id     TEXT NOT NULL UNIQUE,
            server_url    TEXT NOT NULL DEFAULT '',
            username      TEXT NOT NULL DEFAULT '',
            password      TEXT NOT NULL DEFAULT '',
            m3u_url       TEXT NOT NULL DEFAULT '',
            started_at    TEXT NOT NULL DEFAULT '',
            expires_at    TEXT NOT NULL DEFAULT '',
            status        TEXT NOT NULL DEFAULT 'active',
            last_seen_at  TEXT NOT NULL DEFAULT ''
        );
    ");

    $countTrialSettings = (int)$pdo->query("SELECT COUNT(*) FROM app_trial_settings WHERE id = 1")->fetchColumn();
    if ($countTrialSettings === 0) {
        $pdo->prepare("INSERT INTO app_trial_settings (id, is_enabled, duration_hours) VALUES (1, 0, 4)")
            ->execute();
    }

    $clientCols = $pdo->query('PRAGMA table_info(app_clients)')->fetchAll();
    $hasCardCol = false;
    foreach ($clientCols as $clientCol) {
        if (($clientCol['name'] ?? '') === 'card_image') {
            $hasCardCol = true;
            break;
        }
    }
    if (!$hasCardCol) {
        $pdo->exec("ALTER TABLE app_clients ADD COLUMN card_image TEXT NOT NULL DEFAULT ''");
    }

    $hasCardPlanCol = false;
    foreach ($clientCols as $clientCol) {
        if (($clientCol['name'] ?? '') === 'card_plan') {
            $hasCardPlanCol = true;
            break;
        }
    }
    if (!$hasCardPlanCol) {
        $pdo->exec("ALTER TABLE app_clients ADD COLUMN card_plan TEXT NOT NULL DEFAULT 'mensal'");
    }

    $hasClientExpiresCol = false;
    foreach ($clientCols as $clientCol) {
        if (($clientCol['name'] ?? '') === 'expires_at') {
            $hasClientExpiresCol = true;
            break;
        }
    }
    if (!$hasClientExpiresCol) {
        $pdo->exec("ALTER TABLE app_clients ADD COLUMN expires_at TEXT NOT NULL DEFAULT ''");
    }

    $hasClientExpiresSourceCol = false;
    foreach ($clientCols as $clientCol) {
        if (($clientCol['name'] ?? '') === 'expires_source') {
            $hasClientExpiresSourceCol = true;
            break;
        }
    }
    if (!$hasClientExpiresSourceCol) {
        $pdo->exec("ALTER TABLE app_clients ADD COLUMN expires_source TEXT NOT NULL DEFAULT 'xtream'");
    }

    $hasClientResellerCol = false;
    foreach ($clientCols as $clientCol) {
        if (($clientCol['name'] ?? '') === 'reseller_user_id') {
            $hasClientResellerCol = true;
            break;
        }
    }
    if (!$hasClientResellerCol) {
        $pdo->exec("ALTER TABLE app_clients ADD COLUMN reseller_user_id INTEGER DEFAULT NULL");
    }

    $userCols = $pdo->query('PRAGMA table_info(app_users)')->fetchAll();
    $userColNames = [];
    foreach ($userCols as $userCol) {
        $userColNames[] = (string)($userCol['name'] ?? '');
    }
    if (!in_array('display_name', $userColNames, true)) {
        $pdo->exec("ALTER TABLE app_users ADD COLUMN display_name TEXT NOT NULL DEFAULT ''");
    }
    if (!in_array('role', $userColNames, true)) {
        $pdo->exec("ALTER TABLE app_users ADD COLUMN role TEXT NOT NULL DEFAULT 'admin'");
    }
    if (!in_array('panel_expires_at', $userColNames, true)) {
        $pdo->exec("ALTER TABLE app_users ADD COLUMN panel_expires_at TEXT NOT NULL DEFAULT ''");
    }
    if (!in_array('client_limit', $userColNames, true)) {
        $pdo->exec("ALTER TABLE app_users ADD COLUMN client_limit INTEGER NOT NULL DEFAULT 0");
    }
    if (!in_array('reseller_whatsapp', $userColNames, true)) {
        $pdo->exec("ALTER TABLE app_users ADD COLUMN reseller_whatsapp TEXT NOT NULL DEFAULT ''");
    }

    // Seeds mínimos (APENAS SE NUNCA FORAM CRIADOS)

    // Branding - só cria se NUNCA existiu nenhum registro
    $count = (int)$pdo->query("SELECT COUNT(*) FROM app_branding")->fetchColumn();
    if ($count === 0) {
        $pdo->prepare("INSERT INTO app_branding (logo_url, background_url, is_active) VALUES (?, ?, 1)")
            ->execute(['https://exemplo.com/logo.png', 'https://exemplo.com/background.jpg']);
    }

    // Notes - só cria se NUNCA existiu nenhum registro
    $count = (int)$pdo->query("SELECT COUNT(*) FROM app_notes")->fetchColumn();
    if ($count === 0) {
        $pdo->prepare("INSERT INTO app_notes (note_title, note_content, is_active) VALUES (?, ?, 1)")
            ->execute(['Bem-vindo', 'Aproveite o app! Este aviso vem do painel (SQLite).']);
    }

    // URLs - só cria se NUNCA existiu NENHUM registro (ativo ou inativo)
    $count = (int)$pdo->query("SELECT COUNT(*) FROM app_urls")->fetchColumn();
    if ($count === 0) {
        $s = $pdo->prepare("INSERT INTO app_urls (name, url, sort, is_active) VALUES (?, ?, ?, 1)");
        $s->execute(['Servidor 1', 'http://seu-dns-1.com', 1]);
        $s->execute(['Servidor 2', 'http://seu-dns-2.com', 2]);
    }

    // Settings - só cria se NUNCA existiu nenhum registro
    $count = (int)$pdo->query("SELECT COUNT(*) FROM app_settings")->fetchColumn();
    if ($count === 0) {
        $pdo->prepare("INSERT INTO app_settings (expired_date, is_trial, pin_code) VALUES (?, ?, ?)")
            ->execute(['', 0, '0000']);
    }

    // (Opcional) init themes custom table (se você usa isso em outro lugar)
    $count = (int)$pdo->query("SELECT COUNT(*) FROM app_users")->fetchColumn();
    if ($count === 0) {
        $hash = password_hash('admin123', PASSWORD_BCRYPT);
        $pdo->prepare("INSERT INTO app_users (username, display_name, password_hash, role, panel_expires_at, client_limit, is_active) VALUES (?, ?, ?, 'admin', '', 0, 1)")
            ->execute(['admin', 'Administrador', $hash]);
    }

    init_custom_themes_table();
}

/** JSON + CORS */
function json_response($data, int $status = 200): void {
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
    header('Access-Control-Allow-Headers: Content-Type, Authorization, X-App-Key');
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

/** Sanitizador simples */
function str_in($v): string { return trim((string)$v); }

function user_role(array $user): string {
    return strtolower(trim((string)($user['role'] ?? 'admin')));
}

function user_is_admin(array $user): bool {
    return user_role($user) === 'admin';
}

function user_is_reseller(array $user): bool {
    return user_role($user) === 'reseller';
}

function user_display_name(array $user): string {
    $display = trim((string)($user['display_name'] ?? ''));
    return $display !== '' ? $display : (string)($user['username'] ?? 'Usuario');
}

function normalize_panel_expires_at_input(string $raw): string {
    return normalize_client_expires_at_input($raw);
}

function user_panel_is_expired(array $user): bool {
    if (!user_is_reseller($user)) {
        return false;
    }
    $expiresAt = trim((string)($user['panel_expires_at'] ?? ''));
    if ($expiresAt === '') {
        return false;
    }
    $ts = strtotime($expiresAt);
    return $ts > 0 && $ts < time();
}

function user_panel_expiry_label(array $user): string {
    $expiresAt = trim((string)($user['panel_expires_at'] ?? ''));
    if ($expiresAt === '') {
        return '--/--/----';
    }
    $ts = strtotime($expiresAt);
    return $ts ? date('d/m/Y', $ts) : '--/--/----';
}

function reseller_exists(): bool {
    $stmt = db()->query("SELECT COUNT(*) FROM app_users WHERE role = 'reseller'");
    return ((int)$stmt->fetchColumn()) > 0;
}

function reseller_used_client_count(int $resellerUserId): int {
    $stmt = db()->prepare("SELECT COUNT(*) FROM app_clients WHERE reseller_user_id = ?");
    $stmt->execute([$resellerUserId]);
    return (int)$stmt->fetchColumn();
}

function user_client_limit(array $user): int {
    return max(0, (int)($user['client_limit'] ?? 0));
}

function user_client_usage_summary(array $user): array {
    if (!user_is_reseller($user)) {
        return ['contracted' => 0, 'used' => 0, 'remaining' => 0];
    }
    $contracted = user_client_limit($user);
    $used = reseller_used_client_count((int)($user['id'] ?? 0));
    return [
        'contracted' => $contracted,
        'used' => $used,
        'remaining' => max(0, $contracted - $used),
    ];
}

function user_can_create_client(array $user): bool {
    if (user_is_admin($user)) {
        return true;
    }
    if (!user_is_reseller($user) || user_panel_is_expired($user) || (int)($user['is_active'] ?? 0) !== 1) {
        return false;
    }
    $usage = user_client_usage_summary($user);
    return $usage['used'] < $usage['contracted'];
}

function get_reseller_by_id(int $id): ?array {
    $stmt = db()->prepare("SELECT * FROM app_users WHERE id = ? AND role = 'reseller' LIMIT 1");
    $stmt->execute([$id]);
    $row = $stmt->fetch();
    return $row ?: null;
}

function get_resellers(): array {
    $stmt = db()->query("SELECT * FROM app_users WHERE role = 'reseller' ORDER BY created_at DESC, id DESC");
    return $stmt->fetchAll() ?: [];
}

function create_reseller(string $displayName, string $username, string $password, string $panelExpiresAt, int $clientLimit, string $whatsapp = ''): array {
    $displayName = str_in($displayName);
    $username = str_in($username);
    $password = str_in($password);
    $panelExpiresAt = normalize_panel_expires_at_input($panelExpiresAt);
    $clientLimit = max(0, $clientLimit);
    $whatsapp = normalize_whatsapp_number($whatsapp);

    if ($displayName === '' || strlen($username) < 3 || strlen($password) < 6) {
        return ['ok' => false, 'message' => 'Informe nome, usuario valido e senha com pelo menos 6 caracteres.'];
    }
    if ($panelExpiresAt === '') {
        return ['ok' => false, 'message' => 'Informe a data de vencimento da revenda.'];
    }

    $stmt = db()->prepare("SELECT id FROM app_users WHERE username = ? LIMIT 1");
    $stmt->execute([$username]);
    if ($stmt->fetch()) {
        return ['ok' => false, 'message' => 'Ja existe um usuario com esse login.'];
    }

    $hash = password_hash($password, PASSWORD_BCRYPT);
    $stmt = db()->prepare("
        INSERT INTO app_users (username, display_name, password_hash, role, panel_expires_at, client_limit, reseller_whatsapp, is_active)
        VALUES (?, ?, ?, 'reseller', ?, ?, ?, 1)
    ");
    $stmt->execute([$username, $displayName, $hash, $panelExpiresAt, $clientLimit, $whatsapp]);
    return ['ok' => true, 'message' => 'Revenda criada com sucesso.', 'id' => (int)db()->lastInsertId()];
}

function update_reseller(int $id, string $displayName, string $panelExpiresAt, int $clientLimit, int $isActive, string $newPassword = '', string $whatsapp = ''): array {
    $reseller = get_reseller_by_id($id);
    if (!$reseller) {
        return ['ok' => false, 'message' => 'Revenda nao encontrada.'];
    }

    $displayName = str_in($displayName);
    $panelExpiresAt = normalize_panel_expires_at_input($panelExpiresAt);
    $clientLimit = max(0, $clientLimit);
    $newPassword = str_in($newPassword);
    $whatsapp = normalize_whatsapp_number($whatsapp);

    if ($displayName === '' || $panelExpiresAt === '') {
        return ['ok' => false, 'message' => 'Nome e vencimento sao obrigatorios.'];
    }
    if ($newPassword !== '' && strlen($newPassword) < 6) {
        return ['ok' => false, 'message' => 'A nova senha deve ter pelo menos 6 caracteres.'];
    }

    if ($newPassword !== '') {
        $hash = password_hash($newPassword, PASSWORD_BCRYPT);
        $stmt = db()->prepare("
            UPDATE app_users
               SET display_name = ?, panel_expires_at = ?, client_limit = ?, reseller_whatsapp = ?, is_active = ?, password_hash = ?
             WHERE id = ? AND role = 'reseller'
        ");
        $stmt->execute([$displayName, $panelExpiresAt, $clientLimit, $whatsapp, $isActive ? 1 : 0, $hash, $id]);
    } else {
        $stmt = db()->prepare("
            UPDATE app_users
               SET display_name = ?, panel_expires_at = ?, client_limit = ?, reseller_whatsapp = ?, is_active = ?
             WHERE id = ? AND role = 'reseller'
        ");
        $stmt->execute([$displayName, $panelExpiresAt, $clientLimit, $whatsapp, $isActive ? 1 : 0, $id]);
    }

    return ['ok' => true, 'message' => 'Revenda atualizada com sucesso.'];
}

function set_reseller_active(int $id, int $isActive): array {
    $reseller = get_reseller_by_id($id);
    if (!$reseller) {
        return ['ok' => false, 'message' => 'Revenda nao encontrada.'];
    }

    $stmt = db()->prepare("UPDATE app_users SET is_active = ? WHERE id = ? AND role = 'reseller'");
    $stmt->execute([$isActive ? 1 : 0, $id]);

    return ['ok' => true, 'message' => $isActive ? 'Revenda ativada com sucesso.' : 'Revenda desativada com sucesso.'];
}

function delete_reseller_by_id(int $id): array {
    $reseller = get_reseller_by_id($id);
    if (!$reseller) {
        return ['ok' => false, 'message' => 'Revenda nao encontrada.'];
    }

    $pdo = db();
    try {
        $pdo->beginTransaction();

        $stmt = $pdo->prepare("
            DELETE FROM app_client_devices
             WHERE client_id IN (
                 SELECT id
                   FROM app_clients
                  WHERE reseller_user_id = ?
             )
        ");
        $stmt->execute([$id]);

        $stmt = $pdo->prepare("DELETE FROM app_clients WHERE reseller_user_id = ?");
        $stmt->execute([$id]);

        $stmt = $pdo->prepare("DELETE FROM app_users WHERE id = ? AND role = 'reseller'");
        $stmt->execute([$id]);

        $pdo->commit();
        return ['ok' => true, 'message' => 'Revenda deletada. Clientes vinculados tambem foram deletados.'];
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        return ['ok' => false, 'message' => 'Erro ao deletar revenda.'];
    }
}

function delete_orphaned_reseller_clients(): int {
    $pdo = db();
    try {
        $pdo->beginTransaction();

        $stmt = $pdo->query("
            SELECT c.id
              FROM app_clients c
              LEFT JOIN app_users u ON u.id = c.reseller_user_id
             WHERE c.reseller_user_id IS NOT NULL
               AND u.id IS NULL
        ");
        $ids = array_map('intval', $stmt ? ($stmt->fetchAll(PDO::FETCH_COLUMN) ?: []) : []);

        if (empty($ids)) {
            $pdo->commit();
            return 0;
        }

        $placeholders = implode(',', array_fill(0, count($ids), '?'));

        $stmt = $pdo->prepare("DELETE FROM app_client_devices WHERE client_id IN ($placeholders)");
        $stmt->execute($ids);

        $stmt = $pdo->prepare("DELETE FROM app_clients WHERE id IN ($placeholders)");
        $stmt->execute($ids);
        $deleted = $stmt->rowCount();

        $pdo->commit();
        return (int)$deleted;
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        return 0;
    }
}

function client_blocked_by_reseller(array $client): bool {
    $resellerId = (int)($client['reseller_user_id'] ?? 0);
    if ($resellerId <= 0) {
        return false;
    }
    $reseller = get_reseller_by_id($resellerId);
    if (!$reseller || (int)($reseller['is_active'] ?? 0) !== 1) {
        return true;
    }
    return user_panel_is_expired($reseller);
}

function client_belongs_to_user(array $user, array $client): bool {
    $resellerId = (int)($client['reseller_user_id'] ?? 0);
    if (user_is_admin($user)) {
        return $resellerId === 0;
    }
    return user_is_reseller($user) && $resellerId === (int)($user['id'] ?? 0);
}

function get_visible_clients_for_user(array $user): array {
    if (user_is_admin($user)) {
        $stmt = db()->query("SELECT * FROM app_clients WHERE reseller_user_id IS NULL ORDER BY is_active DESC, id DESC");
        return $stmt->fetchAll() ?: [];
    }
    $stmt = db()->prepare("SELECT * FROM app_clients WHERE reseller_user_id = ? ORDER BY is_active DESC, id DESC");
    $stmt->execute([(int)($user['id'] ?? 0)]);
    return $stmt->fetchAll() ?: [];
}

function get_all_clients_for_admin(?int $resellerFilter = null): array {
    if ($resellerFilter && $resellerFilter > 0) {
        $stmt = db()->prepare("
            SELECT c.*, u.display_name AS reseller_name, u.username AS reseller_username
              FROM app_clients c
              LEFT JOIN app_users u ON u.id = c.reseller_user_id
             WHERE c.reseller_user_id = ?
             ORDER BY c.is_active DESC, c.id DESC
        ");
        $stmt->execute([$resellerFilter]);
        return $stmt->fetchAll() ?: [];
    }

    $stmt = db()->query("
        SELECT c.*, u.display_name AS reseller_name, u.username AS reseller_username
          FROM app_clients c
          LEFT JOIN app_users u ON u.id = c.reseller_user_id
         ORDER BY c.is_active DESC, c.id DESC
    ");
    return $stmt->fetchAll() ?: [];
}

/** Normaliza URL: garante http:// se faltar esquema */
function normalize_url(string $raw): string {
    $u = trim($raw);
    if ($u === '') return '';
    if (stripos($u, 'http://') === 0 || stripos($u, 'https://') === 0) return $u;
    return 'http://' . $u;
}

/** Lista DNS/URLs ativas */
function get_urls(): array {
    $sql = "SELECT name, url FROM app_urls WHERE is_active = 1 ORDER BY sort ASC, id ASC";
    $stmt = db()->query($sql);
    $out = [];
    while ($r = $stmt->fetch()) {
        $out[] = ['name' => $r['name'] ?? '', 'url' => $r['url'] ?? ''];
    }
    return $out;
}

/** Verifica duplicidade (mesmo URL) */
function url_exists(string $url): bool {
    $stmt = db()->prepare("SELECT 1 FROM app_urls WHERE LOWER(url) = LOWER(?) LIMIT 1");
    $stmt->execute([$url]);
    return (bool)$stmt->fetchColumn();
}

/** Insere nova DNS */
function add_url(string $name, string $url, int $sort = 0, int $is_active = 1): array {
    $name = str_in($name);
    $url  = normalize_url($url);
    $sort = (int)$sort;
    $is_active = $is_active ? 1 : 0;

    if ($name === '' || $url === '') {
        return ['ok' => false, 'message' => 'Nome e URL são obrigatórios.'];
    }
    if (!filter_var($url, FILTER_VALIDATE_URL)) {
        return ['ok' => false, 'message' => 'URL inválida.'];
    }
    if (url_exists($url)) {
        return ['ok' => false, 'message' => 'Esta URL já está cadastrada.'];
    }

    $stmt = db()->prepare("INSERT INTO app_urls (name, url, sort, is_active) VALUES (?, ?, ?, ?)");
    $stmt->execute([$name, $url, $sort, $is_active]);

    return [
        'ok' => true,
        'id' => db()->lastInsertId(),
        'name' => $name,
        'url' => $url,
        'sort' => $sort,
        'is_active' => $is_active
    ];
}

/** Opcional: desativar (soft delete) */
function deactivate_url_by_id(int $id): bool {
    $stmt = db()->prepare("UPDATE app_urls SET is_active = 0 WHERE id = ?");
    return $stmt->execute([$id]);
}

/** Ativar URL */
function activate_url_by_id(int $id): bool {
    $stmt = db()->prepare("UPDATE app_urls SET is_active = 1 WHERE id = ?");
    return $stmt->execute([$id]);
}

/** Excluir URL permanentemente */
function delete_url_by_id(int $id): bool {
    $stmt = db()->prepare("DELETE FROM app_urls WHERE id = ?");
    return $stmt->execute([$id]);
}

/** ========== CLIENTES (código de ativação + M3U) ========== */

/** Remove tudo que não for dígito e valida 12 caracteres */
function normalize_activation_code(string $raw): string {
    $digits = preg_replace('/\D+/', '', $raw);
    return strlen($digits) === 12 ? $digits : '';
}

/** Formata 12 dígitos como XXXX-XXXX-XXXX */
function format_activation_code(string $digits): string {
    $digits = preg_replace('/\D+/', '', $digits);
    if (strlen($digits) !== 12) return $digits;
    return substr($digits, 0, 4) . '-' . substr($digits, 4, 4) . '-' . substr($digits, 8, 4);
}

/** Gera código único de 12 dígitos */
function generate_activation_code(): string {
    $pdo = db();
    for ($i = 0; $i < 50; $i++) {
        $digits = '';
        for ($j = 0; $j < 12; $j++) {
            $digits .= (string)random_int(0, 9);
        }
        $stmt = $pdo->prepare("SELECT 1 FROM app_clients WHERE activation_code = ? LIMIT 1");
        $stmt->execute([$digits]);
        if (!$stmt->fetchColumn()) {
            return $digits;
        }
    }
    throw new RuntimeException('Não foi possível gerar um código único.');
}

/** Extrai servidor, usuário e senha de uma URL M3U/Xtream */
function parse_m3u_url(string $raw): array {
    $raw = trim($raw);
    if ($raw === '') {
        return ['ok' => false, 'message' => 'Informe a URL da lista M3U.'];
    }

    if (!preg_match('~^https?://~i', $raw)) {
        $raw = 'http://' . $raw;
    }

    $parts = parse_url($raw);
    if (!$parts || empty($parts['host'])) {
        return ['ok' => false, 'message' => 'URL M3U inválida.'];
    }

    $scheme = strtolower($parts['scheme'] ?? 'http');
    $host = $parts['host'];
    $port = isset($parts['port']) ? ':' . $parts['port'] : '';
    $server_url = $scheme . '://' . $host . $port;

    $query = [];
    if (!empty($parts['query'])) {
        parse_str($parts['query'], $query);
    }

    $username = str_in($query['username'] ?? '');
    $password = str_in($query['password'] ?? '');

    if ($username === '' || $password === '') {
        return ['ok' => false, 'message' => 'A URL M3U deve conter username e password (ex: get.php?username=...&password=...).'];
    }

    return [
        'ok' => true,
        'server_url' => $server_url,
        'username' => $username,
        'password' => $password,
        'm3u_url' => $raw,
    ];
}

/** Cadastra novo cliente e gera código */
function add_client(string $name, string $m3u_url, string $expires_at = '', ?int $reseller_user_id = null, string $expires_source = 'manual'): array {
    $name = str_in($name);
    $parsed = parse_m3u_url($m3u_url);
    $expires_source = normalize_client_expires_source($expires_source);
    $expires_at = normalize_client_expires_at_input($expires_at);
    $reseller_user_id = $reseller_user_id && $reseller_user_id > 0 ? $reseller_user_id : null;

    if (!$parsed['ok']) {
        return $parsed;
    }

    if ($expires_source === 'manual' && $expires_at === '') {
        return ['ok' => false, 'message' => 'Informe a data de vencimento manual.'];
    }

    if ($expires_source === 'xtream') {
        $expires_at = '';
    }

    if ($reseller_user_id !== null) {
        $reseller = get_reseller_by_id($reseller_user_id);
        if (!$reseller) {
            return ['ok' => false, 'message' => 'Revenda nao encontrada.'];
        }
        if (!user_can_create_client($reseller)) {
            return ['ok' => false, 'message' => 'Saldo esgotado ou painel da revenda vencido.'];
        }
    }

    if ($name === '') {
        $name = 'Cliente ' . date('d/m/Y H:i');
    }

    try {
        $code = generate_activation_code();
    } catch (RuntimeException $e) {
        return ['ok' => false, 'message' => $e->getMessage()];
    }

    $stmt = db()->prepare("
        INSERT INTO app_clients (name, reseller_user_id, m3u_url, server_url, username, password, activation_code, card_plan, expires_source, expires_at, is_active)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)
    ");
    $stmt->execute([
        $name,
        $reseller_user_id,
        $parsed['m3u_url'],
        $parsed['server_url'],
        $parsed['username'],
        $parsed['password'],
        $code,
        'manual',
        $expires_source,
        $expires_at,
    ]);

    $client_id = (int)db()->lastInsertId();
    $formatted = format_activation_code($code);
    $client = get_client_by_id($client_id);
    $card = $client ? generate_client_card_png_bytes($client) : ['ok' => false, 'message' => 'Cliente criado, mas nao foi possivel preparar o card.'];
    $expiry = $client ? format_client_expiry_for_panel($client) : ['date' => '--/--/----'];

    return [
        'ok' => true,
        'id' => $client_id,
        'name' => $name,
        'expires_source' => $expires_source,
        'expires_at' => $expires_at,
        'expires_label' => $expiry['date'] ?? '--/--/----',
        'activation_code' => $code,
        'activation_code_formatted' => $formatted,
        'server_url' => $parsed['server_url'],
        'username' => $parsed['username'],
        'card' => $card,
    ];
}

/** Busca cliente ativo pelo código */
function get_client_by_code(string $raw_code): ?array {
    $code = normalize_activation_code($raw_code);
    if ($code === '') return null;

    $stmt = db()->prepare("SELECT * FROM app_clients WHERE activation_code = ? AND is_active = 1 LIMIT 1");
    $stmt->execute([$code]);
    $row = $stmt->fetch();
    if ($row && client_blocked_by_reseller($row)) {
        return null;
    }
    return $row ?: null;
}

/** Lista todos os clientes */
function get_clients(): array {
    $stmt = db()->query("SELECT * FROM app_clients ORDER BY is_active DESC, id DESC");
    return $stmt->fetchAll() ?: [];
}

function get_client_by_id(int $id): ?array {
    $stmt = db()->prepare("SELECT * FROM app_clients WHERE id = ? LIMIT 1");
    $stmt->execute([$id]);
    $row = $stmt->fetch();
    if ($row && client_blocked_by_reseller($row)) {
        $row['blocked_by_reseller'] = 1;
    }
    return $row ?: null;
}

function deactivate_client_by_id(int $id): bool {
    $stmt = db()->prepare("UPDATE app_clients SET is_active = 0 WHERE id = ?");
    return (bool)$stmt->execute([$id]);
}

function activate_client_by_id(int $id): bool {
    $stmt = db()->prepare("UPDATE app_clients SET is_active = 1 WHERE id = ?");
    return (bool)$stmt->execute([$id]);
}

function delete_client_by_id(int $id): bool {
    $stmt = db()->prepare("DELETE FROM app_clients WHERE id = ?");
    return (bool)$stmt->execute([$id]);
}

function normalize_client_expires_source(string $source): string {
    $source = strtolower(trim($source));
    return $source === 'manual' ? 'manual' : 'xtream';
}

function normalize_client_expires_at_input(string $raw): string {
    $raw = trim($raw);
    if ($raw === '') return '';

    $tz = new DateTimeZone('America/Sao_Paulo');
    $formats = [
        'Y-m-d\TH:i' => false,
        'Y-m-d H:i:s' => false,
        'Y-m-d H:i' => false,
        'Y-m-d' => true,
    ];

    foreach ($formats as $format => $endOfDay) {
        $dt = DateTime::createFromFormat($format, $raw, $tz);
        if ($dt instanceof DateTime) {
            $errors = DateTime::getLastErrors();
            $warningCount = is_array($errors) ? (int)($errors['warning_count'] ?? 0) : 0;
            $errorCount = is_array($errors) ? (int)($errors['error_count'] ?? 0) : 0;
            if ($warningCount === 0 && $errorCount === 0) {
                if ($endOfDay) {
                    $dt->setTime(23, 59, 59);
                }
                return $dt->format('Y-m-d H:i:s');
            }
        }
    }

    $ts = strtotime($raw);
    return $ts ? date('Y-m-d H:i:s', $ts) : '';
}

function client_manual_expires_timestamp(array $client): string {
    $expires = trim((string)($client['expires_at'] ?? ''));
    if ($expires === '') return '';
    $ts = strtotime($expires);
    return $ts ? (string)$ts : '';
}

function client_expired_date_for_app(array $client): string {
    $source = normalize_client_expires_source((string)($client['expires_source'] ?? 'xtream'));
    if ($source === 'manual') {
        return client_manual_expires_timestamp($client);
    }
    return xtream_expired_date_for_client($client);
}

function client_expires_input_value(array $client): string {
    $expires = trim((string)($client['expires_at'] ?? ''));
    if ($expires === '') return '';
    $ts = strtotime($expires);
    return $ts ? date('Y-m-d', $ts) : '';
}

function format_client_expiry_for_panel(array $client): array {
    $source = normalize_client_expires_source((string)($client['expires_source'] ?? 'xtream'));
    $raw = $source === 'manual' ? client_manual_expires_timestamp($client) : xtream_expired_date_for_client($client);
    $ts = (int)$raw;

    if ($ts <= 0) {
        return [
            'date' => '--/--/----',
            'source' => $source === 'manual' ? 'Manual' : 'Lista',
            'class' => 'unknown',
        ];
    }

    return [
        'date' => date('d/m/Y', $ts),
        'source' => $source === 'manual' ? 'Manual' : 'Lista',
        'class' => $ts < time() ? 'expired' : '',
    ];
}

function update_client(int $id, string $name, string $m3u_url, int $is_active, string $expires_source = 'xtream', string $expires_at = ''): array {
    $name = str_in($name);
    $parsed = parse_m3u_url($m3u_url);
    $expires_source = normalize_client_expires_source($expires_source);
    $expires_at = normalize_client_expires_at_input($expires_at);

    if (!$parsed['ok']) {
        return $parsed;
    }

    if ($name === '') {
        return ['ok' => false, 'message' => 'Nome do cliente é obrigatório.'];
    }

    if ($expires_source === 'manual' && $expires_at === '') {
        return ['ok' => false, 'message' => 'Informe a data de vencimento manual.'];
    }

    if ($expires_source === 'xtream') {
        $expires_at = '';
    }

    $stmt = db()->prepare("
        UPDATE app_clients
           SET name = ?, m3u_url = ?, server_url = ?, username = ?, password = ?, expires_source = ?, expires_at = ?, is_active = ?
         WHERE id = ?
    ");
    $stmt->execute([
        $name,
        $parsed['m3u_url'],
        $parsed['server_url'],
        $parsed['username'],
        $parsed['password'],
        $expires_source,
        $expires_at,
        $is_active ? 1 : 0,
        $id,
    ]);

    return ['ok' => true, 'message' => 'Cliente atualizado com sucesso!'];
}

/** Resposta de login por código para o app Android */
function build_client_login_response(string $raw_code): array {
    $client = get_client_by_code($raw_code);
    if (!$client) {
        return ['ok' => false, 'message' => 'Código inválido ou cliente inativo.'];
    }

    return [
        'ok' => true,
        'client_name' => $client['name'] ?? '',
        'server_url' => $client['server_url'] ?? '',
        'username' => $client['username'] ?? '',
        'password' => $client['password'] ?? '',
        'expired_date' => client_expired_date_for_app($client),
        'activation_code' => format_activation_code($client['activation_code'] ?? ''),
    ];
}

function device_identity_ok(string $device_id): bool {
    $device_id = trim($device_id);
    if ($device_id === '') return false;
    if (strlen($device_id) > 160) return false;
    return (bool)preg_match('/^[a-zA-Z0-9._:-]+$/', $device_id);
}

function save_client_device_access(array $client, string $device_id): void {
    $device_id = str_in($device_id);
    if ($device_id === '' || !device_identity_ok($device_id)) return;

    $pdo = db();
    $stmt = $pdo->prepare("SELECT id FROM app_client_devices WHERE client_id = ? AND device_id = ? LIMIT 1");
    $stmt->execute([(int)($client['id'] ?? 0), $device_id]);
    $id = (int)($stmt->fetchColumn() ?: 0);

    if ($id > 0) {
        $upd = $pdo->prepare("UPDATE app_client_devices SET last_seen_at = datetime('now') WHERE id = ?");
        $upd->execute([$id]);
        return;
    }

    $ins = $pdo->prepare("
        INSERT INTO app_client_devices (client_id, activation_code, device_id, first_seen_at, last_seen_at)
        VALUES (?, ?, ?, datetime('now'), datetime('now'))
    ");
    $ins->execute([
        (int)($client['id'] ?? 0),
        (string)($client['activation_code'] ?? ''),
        $device_id,
    ]);
}

function build_client_login_response_for_device(string $raw_code, string $device_id): array {
    $client = get_client_by_code($raw_code);
    if (!$client) {
        return ['ok' => false, 'message' => 'Código inválido ou cliente inativo.'];
    }

    $device_id = str_in($device_id);
    if ($device_id === '' || !device_identity_ok($device_id)) {
        return ['ok' => false, 'message' => 'Dispositivo inválido.'];
    }

    $stmt = db()->prepare("
        SELECT device_id
          FROM app_client_devices
         WHERE client_id = ?
         ORDER BY id ASC
         LIMIT 1
    ");
    $stmt->execute([(int)($client['id'] ?? 0)]);
    $registeredDevice = (string)($stmt->fetchColumn() ?: '');

    if ($registeredDevice !== '' && $registeredDevice !== $device_id) {
        return [
            'ok' => false,
            'device_blocked' => true,
            'message' => 'Código já vinculado a outro dispositivo.',
        ];
    }

    save_client_device_access($client, $device_id);
    clear_trial_by_device($device_id);

    $data = build_client_login_response($raw_code);
    $data['device_id'] = $device_id;
    $data['device_registered'] = true;
    return $data;
}

function get_active_client_by_device(string $device_id): ?array {
    $device_id = str_in($device_id);
    if ($device_id === '' || !device_identity_ok($device_id)) return null;

    $stmt = db()->prepare("
        SELECT c.*
          FROM app_client_devices d
          JOIN app_clients c ON c.id = d.client_id
         WHERE d.device_id = ?
           AND c.is_active = 1
         ORDER BY d.id ASC
         LIMIT 1
    ");
    $stmt->execute([$device_id]);
    $client = $stmt->fetch();
    if ($client && client_blocked_by_reseller($client)) {
        return null;
    }
    return $client ?: null;
}

function get_client_device_record(string $device_id): ?array {
    $device_id = str_in($device_id);
    if ($device_id === '' || !device_identity_ok($device_id)) return null;

    $stmt = db()->prepare("
        SELECT
            d.id AS device_row_id,
            d.device_id,
            d.activation_code AS linked_activation_code,
            d.first_seen_at,
            d.last_seen_at,
            c.*
          FROM app_client_devices d
          LEFT JOIN app_clients c ON c.id = d.client_id
         WHERE d.device_id = ?
         ORDER BY d.id ASC
         LIMIT 1
    ");
    $stmt->execute([$device_id]);
    $row = $stmt->fetch();
    if ($row && client_blocked_by_reseller($row)) {
        $row['is_active'] = 0;
    }
    return $row ?: null;
}

function build_client_login_response_from_client(array $client, string $device_id): array {
    save_client_device_access($client, $device_id);

    return [
        'ok' => true,
        'trial' => false,
        'official_client' => true,
        'status' => 'official_client',
        'device_id' => $device_id,
        'client_name' => $client['name'] ?? '',
        'server_url' => $client['server_url'] ?? '',
        'username' => $client['username'] ?? '',
        'password' => $client['password'] ?? '',
        'expired_date' => client_expired_date_for_app($client),
        'activation_code' => format_activation_code($client['activation_code'] ?? ''),
    ];
}

function clear_trial_by_device(string $device_id): void {
    $device_id = str_in($device_id);
    if ($device_id === '' || !device_identity_ok($device_id)) return;

    $stmt = db()->prepare("DELETE FROM app_trial_devices WHERE device_id = ?");
    $stmt->execute([$device_id]);
}

function validate_saved_client_login(string $username, string $password, string $device_id): array {
    $username = str_in($username);
    $password = str_in($password);
    $device_id = str_in($device_id);

    if ($username === '' || $password === '') {
        return ['ok' => false, 'message' => 'Credenciais salvas invalidas.'];
    }

    if ($device_id === '' || !device_identity_ok($device_id)) {
        return ['ok' => false, 'message' => 'Dispositivo invalido.'];
    }

    $officialRecord = get_client_device_record($device_id);
    if ($officialRecord) {
        clear_trial_by_device($device_id);

        if ((int)($officialRecord['is_active'] ?? 0) !== 1) {
            return [
                'ok' => false,
                'official_client' => true,
                'device_blocked' => true,
                'message' => 'Dispositivo bloqueado pelo painel.',
            ];
        }

        if (($officialRecord['username'] ?? '') === $username && ($officialRecord['password'] ?? '') === $password) {
            clear_trial_by_device($device_id);
            save_client_device_access($officialRecord, $device_id);
            return [
                'ok' => true,
                'device_id' => $device_id,
                'client_name' => $officialRecord['name'] ?? '',
                'activation_code' => format_activation_code($officialRecord['activation_code'] ?? ''),
            ];
        }

        return [
            'ok' => false,
            'official_client' => true,
            'message' => 'Dispositivo ja possui cliente oficial. Reabra o app para atualizar o acesso.',
        ];
    }

    $trialData = validate_saved_trial_login($username, $password, $device_id);
    if (!empty($trialData['ok']) || !empty($trialData['trial_expired'])) {
        return $trialData;
    }

    $stmt = db()->prepare("
        SELECT *
          FROM app_clients
         WHERE username = ?
           AND password = ?
           AND is_active = 1
         LIMIT 1
    ");
    $stmt->execute([$username, $password]);
    $client = $stmt->fetch();

    if (!$client) {
        return $trialData;
    }

    if (client_blocked_by_reseller($client)) {
        return [
            'ok' => false,
            'official_client' => true,
            'device_blocked' => true,
            'message' => 'Painel da revenda vencido ou bloqueado.',
        ];
    }

    $stmt = db()->prepare("
        SELECT device_id
          FROM app_client_devices
         WHERE client_id = ?
         ORDER BY id ASC
         LIMIT 1
    ");
    $stmt->execute([(int)($client['id'] ?? 0)]);
    $registeredDevice = (string)($stmt->fetchColumn() ?: '');

    if ($registeredDevice === '') {
        return ['ok' => false, 'message' => 'Dispositivo nao vinculado.'];
    }

    if ($registeredDevice !== $device_id) {
        return [
            'ok' => false,
            'device_blocked' => true,
            'message' => 'Codigo ja em uso em outro Dispositivo!',
        ];
    }

    save_client_device_access($client, $device_id);

    return [
        'ok' => true,
        'device_id' => $device_id,
        'client_name' => $client['name'] ?? '',
        'activation_code' => format_activation_code($client['activation_code'] ?? ''),
    ];
}

function trial_duration_options(): array {
    return [
        2 => '2hs',
        4 => '4hs',
        12 => '12hs',
        24 => '24hs',
        168 => '7 dias',
    ];
}

function normalize_trial_duration($value): int {
    $hours = (int)$value;
    $options = trial_duration_options();
    return array_key_exists($hours, $options) ? $hours : 4;
}

function trial_duration_label($hours): string {
    $hours = normalize_trial_duration($hours);
    $options = trial_duration_options();
    return $options[$hours] ?? '4hs';
}

function trial_now(): DateTimeImmutable {
    return new DateTimeImmutable('now', new DateTimeZone('America/Sao_Paulo'));
}

function trial_datetime_to_db(DateTimeImmutable $date): string {
    return $date->format('Y-m-d H:i:s');
}

function trial_is_active(array $trial): bool {
    if (($trial['status'] ?? '') !== 'active') return false;
    $expires = trim((string)($trial['expires_at'] ?? ''));
    if ($expires === '') return false;
    $expiresAt = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $expires, new DateTimeZone('America/Sao_Paulo'));
    if (!$expiresAt) {
        $ts = strtotime($expires);
        if (!$ts) return false;
        $expiresAt = (new DateTimeImmutable('@' . $ts))->setTimezone(new DateTimeZone('America/Sao_Paulo'));
    }
    return $expiresAt > trial_now();
}

function get_trial_settings(): array {
    $stmt = db()->query("SELECT * FROM app_trial_settings WHERE id = 1 LIMIT 1");
    $settings = $stmt ? $stmt->fetch() : null;
    if (!$settings) {
        db()->prepare("INSERT INTO app_trial_settings (id, is_enabled, duration_hours) VALUES (1, 0, 4)")->execute();
        $stmt = db()->query("SELECT * FROM app_trial_settings WHERE id = 1 LIMIT 1");
        $settings = $stmt ? $stmt->fetch() : [];
    }
    $settings = $settings ?: [];
    $settings['duration_hours'] = normalize_trial_duration($settings['duration_hours'] ?? 4);
    return $settings;
}

function save_trial_settings(int $is_enabled, string $m3u_url, int $duration_hours): array {
    $duration_hours = normalize_trial_duration($duration_hours);
    $is_enabled = $is_enabled ? 1 : 0;
    $m3u_url = str_in($m3u_url);

    if ($is_enabled === 1 || $m3u_url !== '') {
        $parsed = parse_m3u_url($m3u_url);
        if (!$parsed['ok']) {
            return $parsed;
        }
    } else {
        $parsed = [
            'm3u_url' => '',
            'server_url' => '',
            'username' => '',
            'password' => '',
        ];
    }

    $stmt = db()->prepare("
        UPDATE app_trial_settings
           SET is_enabled = ?,
               m3u_url = ?,
               server_url = ?,
               username = ?,
               password = ?,
               duration_hours = ?,
               updated_at = ?
         WHERE id = 1
    ");
    $stmt->execute([
        $is_enabled,
        $parsed['m3u_url'],
        $parsed['server_url'],
        $parsed['username'],
        $parsed['password'],
        $duration_hours,
        trial_datetime_to_db(trial_now()),
    ]);

    return ['ok' => true, 'message' => 'Teste automatico salvo com sucesso.'];
}

function get_trial_devices(int $limit = 80): array {
    $limit = max(1, min(300, $limit));
    $stmt = db()->query("
        SELECT *
          FROM app_trial_devices
         ORDER BY id DESC
         LIMIT " . $limit
    );
    return $stmt ? ($stmt->fetchAll() ?: []) : [];
}

function get_trial_by_device(string $device_id): ?array {
    $stmt = db()->prepare("SELECT * FROM app_trial_devices WHERE device_id = ? LIMIT 1");
    $stmt->execute([$device_id]);
    $row = $stmt->fetch();
    return $row ?: null;
}

function mark_trial_seen(int $id): void {
    $stmt = db()->prepare("UPDATE app_trial_devices SET last_seen_at = ? WHERE id = ?");
    $stmt->execute([trial_datetime_to_db(trial_now()), $id]);
}

function mark_trial_expired(int $id): void {
    $stmt = db()->prepare("UPDATE app_trial_devices SET status = 'expired', last_seen_at = ? WHERE id = ?");
    $stmt->execute([trial_datetime_to_db(trial_now()), $id]);
}

function build_trial_success_response(array $trial, string $status = 'active'): array {
    $expiresTs = strtotime((string)($trial['expires_at'] ?? ''));

    return [
        'ok' => true,
        'trial' => true,
        'status' => $status,
        'device_id' => $trial['device_id'] ?? '',
        'server_url' => $trial['server_url'] ?? '',
        'username' => $trial['username'] ?? '',
        'password' => $trial['password'] ?? '',
        'm3u_url' => $trial['m3u_url'] ?? '',
        'started_at' => $trial['started_at'] ?? '',
        'expires_at' => $trial['expires_at'] ?? '',
        'expired_date' => $expiresTs ? (string)$expiresTs : '',
    ];
}

function start_auto_trial(string $device_id): array {
    $device_id = str_in($device_id);
    if ($device_id === '' || !device_identity_ok($device_id)) {
        return ['ok' => false, 'message' => 'Dispositivo invalido.'];
    }

    $officialRecord = get_client_device_record($device_id);
    if ($officialRecord) {
        clear_trial_by_device($device_id);

        if ((int)($officialRecord['is_active'] ?? 0) !== 1) {
            return [
                'ok' => false,
                'official_client' => true,
                'device_blocked' => true,
                'message' => 'Dispositivo bloqueado pelo painel.',
            ];
        }

        return build_client_login_response_from_client($officialRecord, $device_id);
    }

    $settings = get_trial_settings();
    $existing = get_trial_by_device($device_id);

    if ($existing) {
        if (trial_is_active($existing)) {
            mark_trial_seen((int)$existing['id']);
            return build_trial_success_response($existing, 'already_active');
        }
        mark_trial_expired((int)$existing['id']);
        return [
            'ok' => false,
            'trial_expired' => true,
            'message' => 'Ops, Dispositivo ja Cadastrado em nosso sistema!',
        ];
    }

    if ((int)($settings['is_enabled'] ?? 0) !== 1) {
        return [
            'ok' => false,
            'trial_enabled' => false,
            'message' => 'Teste automatico desabilitado.',
        ];
    }

    if (trim((string)($settings['server_url'] ?? '')) === '' || trim((string)($settings['username'] ?? '')) === '' || trim((string)($settings['password'] ?? '')) === '') {
        return [
            'ok' => false,
            'message' => 'Teste automatico sem lista M3U configurada.',
        ];
    }

    $now = trial_now();
    $expires = $now->modify('+' . normalize_trial_duration($settings['duration_hours'] ?? 4) . ' hours');

    $trial = [
        'device_id' => $device_id,
        'server_url' => $settings['server_url'] ?? '',
        'username' => $settings['username'] ?? '',
        'password' => $settings['password'] ?? '',
        'm3u_url' => $settings['m3u_url'] ?? '',
        'started_at' => trial_datetime_to_db($now),
        'expires_at' => trial_datetime_to_db($expires),
        'status' => 'active',
    ];

    $stmt = db()->prepare("
        INSERT INTO app_trial_devices (device_id, server_url, username, password, m3u_url, started_at, expires_at, status, last_seen_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, 'active', ?)
    ");
    $stmt->execute([
        $trial['device_id'],
        $trial['server_url'],
        $trial['username'],
        $trial['password'],
        $trial['m3u_url'],
        $trial['started_at'],
        $trial['expires_at'],
        $trial['started_at'],
    ]);

    return build_trial_success_response($trial, 'created');
}

function migrate_trial_to_client(int $trial_id, string $name, string $m3u_url, string $expires_at = ''): array {
    $stmt = db()->prepare("SELECT * FROM app_trial_devices WHERE id = ? LIMIT 1");
    $stmt->execute([$trial_id]);
    $trial = $stmt->fetch();

    if (!$trial) {
        return ['ok' => false, 'message' => 'Teste nao encontrado.'];
    }

    $device_id = str_in($trial['device_id'] ?? '');
    if ($device_id === '' || !device_identity_ok($device_id)) {
        return ['ok' => false, 'message' => 'Android ID invalido.'];
    }

    if (get_client_device_record($device_id)) {
        clear_trial_by_device($device_id);
        return ['ok' => false, 'message' => 'Este Android ID ja esta vinculado a um cliente. O teste foi removido.'];
    }

    if (trim($expires_at) === '') {
        $expires_at = (string)($trial['expires_at'] ?? '');
    }

    $result = add_client($name, $m3u_url, $expires_at);
    if (empty($result['ok'])) {
        return $result;
    }

    $client = get_client_by_id((int)($result['id'] ?? 0));
    if (!$client) {
        return ['ok' => false, 'message' => 'Cliente criado, mas nao foi possivel vincular o Android ID.'];
    }

    save_client_device_access($client, $device_id);
    clear_trial_by_device($device_id);

    $result['device_id'] = $device_id;
    $result['message'] = 'Cliente migrado com sucesso.';
    return $result;
}

function validate_saved_trial_login(string $username, string $password, string $device_id): array {
    $stmt = db()->prepare("
        SELECT *
          FROM app_trial_devices
         WHERE username = ?
           AND password = ?
           AND device_id = ?
         LIMIT 1
    ");
    $stmt->execute([$username, $password, $device_id]);
    $trial = $stmt->fetch();

    if (!$trial) {
        return ['ok' => false, 'message' => 'Cliente inativo ou credenciais alteradas.'];
    }

    if (!trial_is_active($trial)) {
        mark_trial_expired((int)$trial['id']);
        return [
            'ok' => false,
            'trial_expired' => true,
            'message' => 'Teste automatico vencido.',
        ];
    }

    mark_trial_seen((int)$trial['id']);
    return [
        'ok' => true,
        'trial' => true,
        'device_id' => $device_id,
        'expires_at' => $trial['expires_at'] ?? '',
    ];
}

/** Buscar URL por ID */
function get_url_by_id(int $id): ?array {
    $stmt = db()->prepare("SELECT * FROM app_urls WHERE id = ? LIMIT 1");
    $stmt->execute([$id]);
    $result = $stmt->fetch();
    return $result ?: null;
}

/** Atualizar URL existente */
function update_url(int $id, string $name, string $url, int $sort, int $is_active): array {
    $name = str_in($name);
    $url  = normalize_url($url);
    $sort = (int)$sort;
    $is_active = $is_active ? 1 : 0;

    if ($name === '' || $url === '') {
        return ['ok' => false, 'message' => 'Nome e URL são obrigatórios.'];
    }

    if (!filter_var($url, FILTER_VALIDATE_URL)) {
        return ['ok' => false, 'message' => 'URL inválida.'];
    }

    // Verifica se URL já existe em OUTRO registro
    $stmt = db()->prepare("SELECT id FROM app_urls WHERE LOWER(url) = LOWER(?) AND id != ? LIMIT 1");
    $stmt->execute([$url, $id]);
    if ($stmt->fetch()) {
        return ['ok' => false, 'message' => 'Esta URL já está cadastrada em outro registro.'];
    }

    try {
        $stmt = db()->prepare("UPDATE app_urls SET name = ?, url = ?, sort = ?, is_active = ? WHERE id = ?");
        $stmt->execute([$name, $url, $sort, $is_active, $id]);

        return ['ok' => true, 'message' => 'DNS atualizado com sucesso!'];
    } catch (Exception $e) {
        return ['ok' => false, 'message' => 'Erro ao atualizar: ' . $e->getMessage()];
    }
}

/** Branding ativo */
function get_branding(): array {
    $row = db()->query("SELECT logo_url, background_url FROM app_branding WHERE is_active = 1 ORDER BY id DESC LIMIT 1")->fetch();
    return ['logo_url' => $row['logo_url'] ?? '', 'background_url' => $row['background_url'] ?? ''];
}

/** Notes ativas */
function get_notes(): array {
    $row = db()->query("SELECT note_title, note_content FROM app_notes WHERE is_active = 1 ORDER BY id DESC LIMIT 1")->fetch();
    return ['note_title' => $row['note_title'] ?? '', 'note_content' => $row['note_content'] ?? ''];
}

/** Settings gerais */
function get_general_settings(): array {
    $row = db()->query("SELECT expired_date, is_trial, pin_code FROM app_settings ORDER BY id DESC LIMIT 1")->fetch();
    return [
        'expired_date' => $row['expired_date'] ?? '',
        'is_trial'     => (int)($row['is_trial'] ?? 0),
        'pin_code'     => $row['pin_code'] ?? '0000',
    ];
}

/** Suporte / WhatsApp */
function normalize_whatsapp_number(string $raw): string {
    $digits = preg_replace('/\D+/', '', $raw);
    if ($digits === null) $digits = '';
    // Se o admin colocar DDD+numero do Brasil sem 55, adiciona 55 automaticamente.
    if ($digits !== '' && strlen($digits) >= 10 && strlen($digits) <= 11 && substr($digits, 0, 2) !== '55') {
        $digits = '55' . $digits;
    }
    return $digits;
}

function get_support_settings(): array {
    $row = db()->query("SELECT whatsapp_number, whatsapp_message FROM app_support_settings WHERE id = 1 LIMIT 1")->fetch();
    return [
        'whatsapp_number' => $row['whatsapp_number'] ?? '',
        'whatsapp_message' => $row['whatsapp_message'] ?? 'Ola, vim pelo aplicativo. Preciso de suporte ou renovacao.',
    ];
}

function update_support_settings(string $number, string $message): array {
    $number = normalize_whatsapp_number($number);
    $message = trim($message);
    if ($message === '') {
        $message = 'Ola, vim pelo aplicativo. Preciso de suporte ou renovacao.';
    }
    if ($number === '' || strlen($number) < 10 || strlen($number) > 15) {
        return ['ok' => false, 'message' => 'Informe um numero de WhatsApp valido com DDD. Exemplo: 5571999999999'];
    }
    $stmt = db()->prepare("INSERT INTO app_support_settings (id, whatsapp_number, whatsapp_message, updated_at) VALUES (1, ?, ?, datetime('now')) ON CONFLICT(id) DO UPDATE SET whatsapp_number = excluded.whatsapp_number, whatsapp_message = excluded.whatsapp_message, updated_at = datetime('now')");
    $stmt->execute([$number, $message]);
    return ['ok' => true, 'message' => 'WhatsApp de suporte salvo com sucesso!', 'whatsapp_number' => $number, 'whatsapp_message' => $message];
}

function build_support_whatsapp_url(?array $settings = null): string {
    $settings = $settings ?: get_support_settings();
    $number = normalize_whatsapp_number($settings['whatsapp_number'] ?? '');
    if ($number === '') return '';
    $msg = trim((string)($settings['whatsapp_message'] ?? ''));
    if ($msg === '') $msg = 'Ola, vim pelo aplicativo. Preciso de suporte ou renovacao.';
    return 'https://wa.me/' . $number . '?text=' . rawurlencode($msg);
}

function build_whatsapp_url_for_number(string $number, string $message): string {
    $number = normalize_whatsapp_number($number);
    if ($number === '') return '';
    $message = trim($message);
    if ($message === '') {
        $message = 'Ola, quero Renovar meu App TV Express!';
    }
    return 'https://wa.me/' . $number . '?text=' . rawurlencode($message);
}

function find_client_for_renewal_access(string $access): ?array {
    $access = str_in($access);
    if ($access === '') return null;

    $code = normalize_activation_code($access);
    $stmt = db()->prepare("
        SELECT
            c.*,
            u.display_name AS reseller_name,
            u.username AS reseller_username,
            u.reseller_whatsapp AS reseller_whatsapp
          FROM app_clients c
          LEFT JOIN app_users u ON u.id = c.reseller_user_id
         WHERE c.username = ?
            OR c.activation_code = ?
         LIMIT 1
    ");
    $stmt->execute([$access, $code]);
    $row = $stmt->fetch();
    return $row ?: null;
}

function build_renewal_whatsapp_url(array $client): string {
    $access = trim((string)($client['username'] ?? ''));
    if ($access === '') {
        $access = format_activation_code((string)($client['activation_code'] ?? ''));
    }
    $clientName = trim((string)($client['name'] ?? ''));
    if ($clientName === '') {
        $clientName = 'Cliente';
    }
    $clientCode = format_activation_code((string)($client['activation_code'] ?? ''));

    $number = '';
    if ((int)($client['reseller_user_id'] ?? 0) > 0) {
        $number = normalize_whatsapp_number((string)($client['reseller_whatsapp'] ?? ''));
    }

    if ($number === '') {
        $support = get_support_settings();
        $number = normalize_whatsapp_number((string)($support['whatsapp_number'] ?? ''));
    }

    if ($number === '') return '';

    $message = "Ola, gostaria de Renovar meu acesso ao App TV Express!\n";
    $message .= 'Me chamo ' . $clientName . ', meu codigo e ' . $clientCode . '.';

    return build_whatsapp_url_for_number($number, $message);
}

function xtream_expired_date_for_client(array $client): string {
    $server = rtrim((string)($client['server_url'] ?? ''), '/');
    $username = (string)($client['username'] ?? '');
    $password = (string)($client['password'] ?? '');

    if ($server === '' || $username === '' || $password === '') return '';

    $url = $server . '/player_api.php?username=' . rawurlencode($username) . '&password=' . rawurlencode($password);
    $context = stream_context_create([
        'http' => [
            'method' => 'GET',
            'timeout' => 4,
            'ignore_errors' => true,
            'header' => "User-Agent: TVExpressPanel/1.0\r\n",
        ],
    ]);

    $raw = @file_get_contents($url, false, $context);
    if (!is_string($raw) || trim($raw) === '') return '';

    $json = json_decode($raw, true);
    if (!is_array($json)) return '';

    $exp = $json['user_info']['exp_date'] ?? '';
    $exp = trim((string)$exp);
    return $exp;
}

function app_info_expired_date_for_device(string $device_id, string $fallback): string {
    $device_id = str_in($device_id);
    if ($device_id === '' || !device_identity_ok($device_id)) return $fallback;

    $trial = get_trial_by_device($device_id);
    if ($trial && trial_is_active($trial)) {
        $ts = strtotime((string)($trial['expires_at'] ?? ''));
        return $ts ? (string)$ts : $fallback;
    }

    $client = get_active_client_by_device($device_id);
    if ($client) {
        $clientExp = client_expired_date_for_app($client);
        if ($clientExp !== '') return $clientExp;
    }

    return $fallback;
}

/** Monta AppInfoModel para o Android */
function build_app_info(string $pkg, int $ver_code, string $ver_name, string $device_id): array {
    $branding = get_branding();
    $notes    = get_notes();
    $urls     = get_urls();
    $gen      = get_general_settings();
    $expiredDate = app_info_expired_date_for_device($device_id, (string)($gen['expired_date'] ?? ''));

    return [
        'expired_date' => $expiredDate,
        'is_trial'     => $gen['is_trial'],
        'pin_code'     => $gen['pin_code'],

        'note_title'   => $notes['note_title'],
        'note_content' => $notes['note_content'],

        'urls'         => $urls,

        'branding'     => [
            'logo_url'       => $branding['logo_url'],
            'background_url' => $branding['background_url'],
        ],

        'support'      => [
            'whatsapp_number' => get_support_settings()['whatsapp_number'] ?? '',
            'whatsapp_url'    => build_support_whatsapp_url(),
        ],
    ];
}

/* ============================================================
 * ✅✅✅ DEVICE KEYS (principal) - usa app_device_keys
 * ============================================================ */

function device_id_ok(string $device_id): bool {
    if ($device_id === '') return false;
    if (strlen($device_id) > 120) return false;
    return (bool)preg_match('/^[a-zA-Z0-9._:-]+$/', $device_id);
}

function public_key_b64_ok(string $b64): bool {
    $b64 = trim($b64);
    if ($b64 === '') return false;
    if (strlen($b64) < 200 || strlen($b64) > 8192) return false;

    $bin = base64_decode($b64, true);
    if ($bin === false || strlen($bin) < 100) return false;

    if (!function_exists('openssl_pkey_get_public')) {
        // openssl ausente — ainda deixa registrar? melhor negar (pra não salvar lixo)
        return false;
    }

    $pem = "-----BEGIN PUBLIC KEY-----\n" . chunk_split($b64, 64, "\n") . "-----END PUBLIC KEY-----\n";
    $pkey = @openssl_pkey_get_public($pem);
    if ($pkey === false) return false;

    if (PHP_VERSION_ID < 80000 && function_exists('openssl_free_key')) {
        @openssl_free_key($pkey);
    }
    return true;
}

function save_device_public_key(string $device_id, string $public_b64): bool {
    $device_id = str_in($device_id);
    $public_b64 = str_in($public_b64);

    if (!device_id_ok($device_id)) return false;
    if ($public_b64 === '' || strlen($public_b64) > 8192) return false;

    if (!public_key_b64_ok($public_b64)) return false;

    $pdo = db();
    $stmt = $pdo->prepare("SELECT id FROM app_device_keys WHERE device_id = ? LIMIT 1");
    $stmt->execute([$device_id]);
    $row = $stmt->fetch();

    if ($row && isset($row['id'])) {
        $u = $pdo->prepare("UPDATE app_device_keys SET public_key_b64 = ?, updated_at = datetime('now') WHERE device_id = ?");
        return (bool)$u->execute([$public_b64, $device_id]);
    } else {
        $i = $pdo->prepare("INSERT INTO app_device_keys (device_id, public_key_b64) VALUES (?, ?)");
        return (bool)$i->execute([$device_id, $public_b64]);
    }
}

function get_device_public_key_b64(string $device_id): string {
    $device_id = str_in($device_id);
    if (!device_id_ok($device_id)) return '';

    $stmt = db()->prepare("SELECT public_key_b64 FROM app_device_keys WHERE device_id = ? LIMIT 1");
    $stmt->execute([$device_id]);
    $v = $stmt->fetchColumn();
    return $v ? (string)$v : '';
}

function public_key_pem_from_b64(string $b64): string {
    $b64 = trim($b64);
    if ($b64 === '') return '';

    // se já é PEM, retorna
    if (strpos($b64, 'BEGIN PUBLIC KEY') !== false) return $b64;

    $raw = base64_decode($b64, true);
    if ($raw === false || $raw === '') return '';

    $pem = "-----BEGIN PUBLIC KEY-----\n";
    $pem .= chunk_split(base64_encode($raw), 64, "\n");
    $pem .= "-----END PUBLIC KEY-----\n";
    return $pem;
}

function encrypt_for_device(string $device_id, array $payload): ?array {
    if (!function_exists('openssl_pkey_get_public') || !function_exists('openssl_encrypt')) {
        return null; // openssl ausente
    }

    $b64 = get_device_public_key_b64($device_id);
    if ($b64 === '') return null;

    $pem = public_key_pem_from_b64($b64);
    if ($pem === '') return null;

    $pub = openssl_pkey_get_public($pem);
    if ($pub === false) return null;

    $plaintext = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    if ($plaintext === false) return null;

    // AES-256-GCM
    $aesKey = random_bytes(32);
    $iv     = random_bytes(12);
    $tag    = '';

    $ciphertext = openssl_encrypt(
        $plaintext,
        'aes-256-gcm',
        $aesKey,
        OPENSSL_RAW_DATA,
        $iv,
        $tag,
        '',
        16
    );

    if ($ciphertext === false || $tag === '') return null;

    // RSA OAEP (OpenSSL padrão)
    $ek = '';
    $ok = openssl_public_encrypt($aesKey, $ek, $pub, OPENSSL_PKCS1_OAEP_PADDING);
    if (!$ok || $ek === '') return null;

    return [
        'ok'  => true,
        'enc' => 1,
        'alg' => 'RSA_OAEP+AES_256_GCM',
        'kid' => $device_id,
        'ek'  => base64_encode($ek),
        'iv'  => base64_encode($iv),
        'ct'  => base64_encode($ciphertext),
        'tag' => base64_encode($tag),
    ];
}

/** ========== FUNÇÕES DE TEMA (mantidas) ========== */

function init_custom_themes_table(): void {
    db()->exec("
        CREATE TABLE IF NOT EXISTS app_custom_themes (
            id         INTEGER PRIMARY KEY AUTOINCREMENT,
            theme_name TEXT NOT NULL,
            primary_bg TEXT NOT NULL,
            sidebar_bg TEXT NOT NULL,
            hover_bg   TEXT NOT NULL,
            link_bg    TEXT NOT NULL,
            link_hover TEXT NOT NULL,
            logout_bg  TEXT NOT NULL,
            logout_hover TEXT NOT NULL,
            created_at TEXT NOT NULL DEFAULT (datetime('now'))
        );
    ");
}

function get_user_theme(int $user_id): array {
    $stmt = db()->prepare("SELECT theme_mode, custom_css FROM app_theme WHERE user_id = ? LIMIT 1");
    $stmt->execute([$user_id]);
    $row = $stmt->fetch();
    return [
        'mode' => $row['theme_mode'] ?? 'tvexpress',
        'custom_css' => $row['custom_css'] ?? ''
    ];
}

function save_user_theme(int $user_id, string $theme_mode, string $custom_css = ''): bool {
    $stmt = db()->prepare("SELECT id FROM app_theme WHERE user_id = ? LIMIT 1");
    $stmt->execute([$user_id]);

    if ($stmt->fetch()) {
        $stmt = db()->prepare("UPDATE app_theme SET theme_mode = ?, custom_css = ? WHERE user_id = ?");
        return $stmt->execute([$theme_mode, $custom_css, $user_id]);
    } else {
        $stmt = db()->prepare("INSERT INTO app_theme (user_id, theme_mode, custom_css) VALUES (?, ?, ?)");
        return $stmt->execute([$user_id, $theme_mode, $custom_css]);
    }
}

function get_theme_css(string $theme_mode, string $custom_css = ''): string {
    if (!empty($custom_css)) return $custom_css;

    switch ($theme_mode) {
        case 'tvexpress':
            return ":root { --primary-bg: linear-gradient(135deg, #b85cff 0%, #8d2cff 48%, #5414bc 100%); }";
        case 'red-gradient':
            return ":root { --primary-bg: linear-gradient(135deg, #c0392b 0%, #e74c3c 100%); }";
        case 'gradient':
            return ":root { --primary-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }";
        case 'blue':
            return ":root { --primary-bg: linear-gradient(135deg, #3498db 0%, #185a9d 100%); }";
        case 'green':
            return ":root { --primary-bg: linear-gradient(135deg, #27ae60 0%, #11998e 100%); }";
        case 'purple':
            return ":root { --primary-bg: linear-gradient(135deg, #9b59b6 0%, #6f1fc7 100%); }";
        case 'dark':
            return ":root { --primary-bg: linear-gradient(135deg, #2f3640 0%, #111827 100%); }";
        case 'red':
        default:
            return ":root { --primary-bg: linear-gradient(135deg, #c0392b 0%, #8e1b1b 100%); }";
    }
}

function get_available_themes(): array {
    return [
        'tvexpress' => [
            'name' => 'TV Express Premium',
            'icon' => '💜',
            'description' => 'Roxo moderno com brilho premium, igual ao aplicativo.',
            'preview_color' => 'linear-gradient(135deg, #b85cff 0%, #8d2cff 48%, #5414bc 100%)'
        ],
        'purple' => [
            'name' => 'Roxo Místico',
            'icon' => '🟣',
            'description' => 'Tema roxo escuro para painel IPTV.',
            'preview_color' => 'linear-gradient(135deg, #9b59b6 0%, #6f1fc7 100%)'
        ],
        'gradient' => [
            'name' => 'Gradiente Moderno',
            'icon' => '🌈',
            'description' => 'Gradiente azul/roxo suave.',
            'preview_color' => 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
        ],
        'blue' => [
            'name' => 'Azul Oceano',
            'icon' => '🔵',
            'description' => 'Azul limpo para uso diário.',
            'preview_color' => 'linear-gradient(135deg, #3498db 0%, #185a9d 100%)'
        ],
        'green' => [
            'name' => 'Verde Natureza',
            'icon' => '🟢',
            'description' => 'Verde com visual de status online.',
            'preview_color' => 'linear-gradient(135deg, #27ae60 0%, #11998e 100%)'
        ],
        'red' => [
            'name' => 'Vermelho Clássico',
            'icon' => '🔴',
            'description' => 'Tema vermelho tradicional.',
            'preview_color' => 'linear-gradient(135deg, #c0392b 0%, #8e1b1b 100%)'
        ],
        'red-gradient' => [
            'name' => 'Vermelho Gradiente',
            'icon' => '🔥',
            'description' => 'Vermelho com efeito gradiente.',
            'preview_color' => 'linear-gradient(135deg, #c0392b 0%, #e74c3c 100%)'
        ],
        'dark' => [
            'name' => 'Dark Mode',
            'icon' => '⚫',
            'description' => 'Tema escuro minimalista.',
            'preview_color' => 'linear-gradient(135deg, #2f3640 0%, #111827 100%)'
        ],
    ];
}

/** ========== UPLOADS (mantidos do seu arquivo) ========== */

const UPLOADS_DIR = __DIR__ . '/../uploads';

function get_base_url(): string {
    $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
    $script = $_SERVER['SCRIPT_NAME'] ?? '';
    $base = dirname($script);
    return $protocol . '://' . $host . ($base === '/' ? '' : $base);
}

function get_panel_public_url(string $path = ''): string {
    $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
    $script = $_SERVER['SCRIPT_NAME'] ?? '';
    $base = dirname($script);
    if (basename(str_replace('\\', '/', $base)) === 'api') {
        $base = dirname($base);
    }
    $base = $base === '/' || $base === '\\' ? '' : $base;
    $path = ltrim($path, '/');
    return $protocol . '://' . $host . $base . ($path !== '' ? '/' . $path : '');
}

function init_uploads_dir(): bool {
    if (!is_dir(UPLOADS_DIR)) {
        return @mkdir(UPLOADS_DIR, 0755, true);
    }
    return true;
}

function upload_image(array $file): array {
    if (!init_uploads_dir()) {
        return ['ok' => false, 'message' => 'Erro ao criar diretório de uploads.'];
    }

    if (!isset($file['error']) || $file['error'] !== UPLOAD_ERR_OK) {
        return ['ok' => false, 'message' => 'Erro no upload do arquivo.'];
    }

    $allowed_types = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
    $file_type = mime_content_type($file['tmp_name']);

    if (!in_array($file_type, $allowed_types)) {
        return ['ok' => false, 'message' => 'Tipo de arquivo não permitido. Use JPG, PNG, GIF ou WEBP.'];
    }

    if ($file['size'] > 5 * 1024 * 1024) {
        return ['ok' => false, 'message' => 'Arquivo muito grande. Máximo: 5MB.'];
    }

    $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
    $filename = 'img_' . uniqid() . time() . '.' . $extension;
    $filepath = UPLOADS_DIR . '/' . $filename;

    if (!move_uploaded_file($file['tmp_name'], $filepath)) {
        return ['ok' => false, 'message' => 'Erro ao salvar arquivo.'];
    }

    $url = get_base_url() . '/uploads/' . $filename;

    return ['ok' => true, 'url' => $url, 'filename' => $filename];
}

function save_logo($input_url, $file = null): array {
    $logo_url = '';

    if ($file && isset($file['tmp_name']) && !empty($file['tmp_name'])) {
        $upload_result = upload_image($file);
        if (!$upload_result['ok']) return $upload_result;
        $logo_url = $upload_result['url'];
    } elseif (!empty($input_url)) {
        $logo_url = str_in($input_url);
        if (!filter_var($logo_url, FILTER_VALIDATE_URL) && !str_starts_with($logo_url, '/')) {
            return ['ok' => false, 'message' => 'URL inválida.'];
        }
    } else {
        return ['ok' => false, 'message' => 'Forneça uma URL ou faça upload de uma imagem.'];
    }

    try {
        db()->exec("UPDATE app_branding SET is_active = 0");
        $stmt = db()->prepare("INSERT INTO app_branding (logo_url, background_url, is_active)
                               SELECT ?, background_url, 1 FROM app_branding WHERE is_active = 0 ORDER BY id DESC LIMIT 1");
        $stmt->execute([$logo_url]);

        return ['ok' => true, 'url' => $logo_url];
    } catch (Exception $e) {
        return ['ok' => false, 'message' => 'Erro ao salvar: ' . $e->getMessage()];
    }
}

function save_background($input_url, $file = null): array {
    $bg_url = '';

    if ($file && isset($file['tmp_name']) && !empty($file['tmp_name'])) {
        $upload_result = upload_image($file);
        if (!$upload_result['ok']) return $upload_result;
        $bg_url = $upload_result['url'];
    } elseif (!empty($input_url)) {
        $bg_url = str_in($input_url);
        if (!filter_var($bg_url, FILTER_VALIDATE_URL) && !str_starts_with($bg_url, '/')) {
            return ['ok' => false, 'message' => 'URL inválida.'];
        }
    } else {
        return ['ok' => false, 'message' => 'Forneça uma URL ou faça upload de uma imagem.'];
    }

    try {
        db()->exec("UPDATE app_branding SET is_active = 0");
        $stmt = db()->prepare("INSERT INTO app_branding (background_url, logo_url, is_active)
                               SELECT ?, logo_url, 1 FROM app_branding WHERE is_active = 0 ORDER BY id DESC LIMIT 1");
        $stmt->execute([$bg_url]);

        return ['ok' => true, 'url' => $bg_url];
    } catch (Exception $e) {
        return ['ok' => false, 'message' => 'Erro ao salvar: ' . $e->getMessage()];
    }
}

/* =======================================================================
 * BLOCO DUPLICADO (mantido) - renomeado para NÃO quebrar (redeclare)
 * Você tinha um "app_devices" separado + funções iguais.
 * Eu NÃO removi: só prefixei com dev_ para manter no arquivo sem conflito.
 * ======================================================================= */

function dev_init_devices_table(): void {
    db()->exec("
        CREATE TABLE IF NOT EXISTS app_devices (
            device_id      TEXT PRIMARY KEY,
            public_key_b64 TEXT NOT NULL DEFAULT '',
            created_at     TEXT NOT NULL DEFAULT (datetime('now')),
            updated_at     TEXT NOT NULL DEFAULT (datetime('now'))
        );
    ");
}

function dev_device_id_ok(string $device_id): bool {
    $d = trim($device_id);
    if ($d === '') return false;
    if (preg_match('~^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$~i', $d)) {
        return true;
    }
    return (bool)preg_match('~^[a-zA-Z0-9._:-]{16,80}$~', $d);
}

function dev_public_key_b64_ok(string $b64): bool {
    $b64 = trim($b64);
    if ($b64 === '') return false;
    if (strlen($b64) < 200 || strlen($b64) > 2000) return false;

    $bin = base64_decode($b64, true);
    if ($bin === false || strlen($bin) < 100) return false;

    $pem = "-----BEGIN PUBLIC KEY-----\n" .
        chunk_split($b64, 64, "\n") .
        "-----END PUBLIC KEY-----\n";

    $pkey = @openssl_pkey_get_public($pem);
    if ($pkey === false) return false;

    if (PHP_VERSION_ID < 80000 && function_exists('openssl_free_key')) {
        @openssl_free_key($pkey);
    }
    return true;
}

function dev_save_device_public_key(string $device_id, string $pub_b64): bool {
    dev_init_devices_table();

    $device_id = trim($device_id);
    $pub_b64   = trim($pub_b64);

    if (!dev_device_id_ok($device_id)) return false;
    if (!dev_public_key_b64_ok($pub_b64)) return false;

    $stmt = db()->prepare("SELECT device_id FROM app_devices WHERE device_id = ? LIMIT 1");
    $stmt->execute([$device_id]);
    $exists = (bool)$stmt->fetchColumn();

    if ($exists) {
        $stmt = db()->prepare("
            UPDATE app_devices
               SET public_key_b64 = ?,
                   updated_at = datetime('now')
             WHERE device_id = ?
        ");
        return (bool)$stmt->execute([$pub_b64, $device_id]);
    }

    $stmt = db()->prepare("
        INSERT INTO app_devices (device_id, public_key_b64)
        VALUES (?, ?)
    ");
    return (bool)$stmt->execute([$device_id, $pub_b64]);
}

function dev_get_device_public_key_b64(string $device_id): string {
    dev_init_devices_table();

    $stmt = db()->prepare("SELECT public_key_b64 FROM app_devices WHERE device_id = ? LIMIT 1");
    $stmt->execute([trim($device_id)]);
    $b64 = (string)($stmt->fetchColumn() ?: '');
    return trim($b64);
}

function dev_encrypt_for_device(string $device_id, array $payload) {
    $pubB64 = dev_get_device_public_key_b64($device_id);
    if ($pubB64 === '') {
        return "no_public_key";
    }

    $pem = "-----BEGIN PUBLIC KEY-----\n" .
        chunk_split($pubB64, 64, "\n") .
        "-----END PUBLIC KEY-----\n";

    $pub = @openssl_pkey_get_public($pem);
    if ($pub === false) {
        return "bad_public_key";
    }

    $json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    if ($json === false) {
        return "json_fail";
    }

    $aesKey = random_bytes(32);
    $iv     = random_bytes(12);
    $tag    = '';

    $ciphertext = openssl_encrypt(
        $json,
        'aes-256-gcm',
        $aesKey,
        OPENSSL_RAW_DATA,
        $iv,
        $tag,
        '',
        16
    );

    if ($ciphertext === false) {
        if (PHP_VERSION_ID < 80000 && function_exists('openssl_free_key')) { @openssl_free_key($pub); }
        return "aes_fail";
    }

    $ek = '';
    $ok = openssl_public_encrypt($aesKey, $ek, $pub, OPENSSL_PKCS1_OAEP_PADDING);

    if (PHP_VERSION_ID < 80000 && function_exists('openssl_free_key')) { @openssl_free_key($pub); }

    if (!$ok || $ek === '') {
        return "rsa_fail";
    }

    return [
        'ok'  => true,
        'enc' => true,
        'alg' => 'RSA_OAEP+AES_256_GCM',
        'ek'  => base64_encode($ek),
        'iv'  => base64_encode($iv),
        'ct'  => base64_encode($ciphertext),
        'tag' => base64_encode($tag),
    ];
}

require_once __DIR__ . '/card_generator.php';
