<?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)
    $pdo->exec("
        CREATE TABLE IF NOT EXISTS app_users (
            id            INTEGER PRIMARY KEY AUTOINCREMENT,
            username      TEXT NOT NULL UNIQUE,
            password_hash TEXT NOT NULL,
            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 '',
            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 '',
            is_active       INTEGER NOT NULL DEFAULT 1,
            created_at      TEXT NOT NULL DEFAULT (datetime('now'))
        );
    ");

    $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'");
    }

    // 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)
    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); }

/** 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 $card_plan = 'mensal'): array {
    $name = str_in($name);
    $card_plan = normalize_card_plan($card_plan);
    $parsed = parse_m3u_url($m3u_url);

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

    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, m3u_url, server_url, username, password, activation_code, card_plan, is_active)
        VALUES (?, ?, ?, ?, ?, ?, ?, 1)
    ");
    $stmt->execute([
        $name,
        $parsed['m3u_url'],
        $parsed['server_url'],
        $parsed['username'],
        $parsed['password'],
        $code,
        $card_plan,
    ]);

    $client_id = (int)db()->lastInsertId();
    $formatted = format_activation_code($code);
    $card = generate_and_store_client_card($client_id, $formatted, $card_plan);

    return [
        'ok' => true,
        'id' => $client_id,
        'name' => $name,
        'card_plan' => $card_plan,
        'card_plan_label' => card_plan_label($card_plan),
        '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();
    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();
    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 update_client(int $id, string $name, string $m3u_url, int $is_active): array {
    $name = str_in($name);
    $parsed = parse_m3u_url($m3u_url);

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

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

    $stmt = db()->prepare("
        UPDATE app_clients
           SET name = ?, m3u_url = ?, server_url = ?, username = ?, password = ?, is_active = ?
         WHERE id = ?
    ");
    $stmt->execute([
        $name,
        $parsed['m3u_url'],
        $parsed['server_url'],
        $parsed['username'],
        $parsed['password'],
        $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'] ?? '',
        'activation_code' => format_activation_code($client['activation_code'] ?? ''),
    ];
}

/** 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);
}

/** 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();

    return [
        'expired_date' => $gen['expired_date'],
        '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 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';
