<?php
/**
 * @Escobar_Billing_Core
 * @Protected_File
 * @Patch v2.5 (Fixed):
 *   - BUGFIX: target_timestamp tidak lagi dihitung ulang di controlTV()
 *             Nilai dari caller (process.php) dipakai langsung
 *   - BUGFIX: Raw query di stop diganti prepared statement
 *   - BUGFIX KRITIS: ensureAccessibility() sekarang BACA DULU daftar
 *     accessibility yang aktif, lalu APPEND jika belum ada (bukan REPLACE).
 *     Ini fix masalah: "Accessibility mati sendiri setiap kali kasir
 *     tekan Start/Connect/Resume"
 *   - show_expired_image: HTTP only (ADB push dihapus)
 *     TV download gambar langsung dari PC kasir via WiFi LAN.
 */
ob_start();
if (session_status() === PHP_SESSION_NONE) { session_start(); }
date_default_timezone_set('Asia/Jakarta');

$_CONF = ['h'=>'localhost', 'u'=>'root', 'p'=>'', 'n'=>'db_rental_ps'];

try {
    $conn = new mysqli($_CONF['h'], $_CONF['u'], $_CONF['p'], $_CONF['n']);
    $conn->set_charset("utf8mb4");
    $conn->query("SET time_zone = '+07:00'");
} catch (Exception $e) { die("DB_ERR"); }

// ============================================================
// SETTINGS — Bulk load sekali, cache static, hindari N+1 query
// ============================================================

if (!function_exists('preload_settings')) {
    function preload_settings($c) {
        static $loaded = false;
        if ($loaded) return;
        $r = $c->query("SELECT setting_name, setting_value FROM system_settings");
        if (!$r) return;
        $all = [];
        while ($row = $r->fetch_assoc()) {
            $all[$row['setting_name']] = $row['setting_value'];
        }
        get_setting($c, '__preload__', false, $all);
        $loaded = true;
    }
}

if (!function_exists('get_setting')) {
    function get_setting($c, $k, $invalidate = false, $preload_data = null) {
        static $_CH = [];
        if ($preload_data !== null) {
            $_CH = array_merge($_CH, $preload_data);
            return '';
        }
        if ($invalidate) unset($_CH[$k]);
        if (isset($_CH[$k])) return $_CH[$k];
        $s = $c->prepare("SELECT setting_value FROM system_settings WHERE setting_name = ? LIMIT 1");
        $s->bind_param("s", $k); $s->execute();
        $v = ($r = $s->get_result()->fetch_assoc()) ? $r['setting_value'] : '';
        $s->close();
        return $_CH[$k] = $v;
    }
}

preload_settings($conn);

// ============================================================
// LICENSE
// ============================================================

define('_API', 'https://escobargame.fun/license/api.php');
define('_INT', 86400); // cache 24 jam
$lic  = trim(get_setting($conn, 'license_key'));
$pg   = basename($_SERVER['PHP_SELF']);
$_ALW = ['activate.php', 'login.php', 'logout.php'];

if (!in_array($pg, $_ALW)) {
    if (empty($lic)) { header("Location: activate.php"); exit; }
    if (!defined('LICENSE_KEY')) define('LICENSE_KEY', $lic);
    if ((time() - ($_SESSION['lic_last_check'] ?? 0)) > _INT || ($_SESSION['lic_status'] ?? '') !== 'success') {
        _vls($conn);
    }
}

function _vls($c) {
    $h = $_SERVER['HTTP_HOST'] ?? 'localhost';
    $d = (in_array($h, ['localhost', '127.0.0.1', '::1'])) ? 'localhost' : $h;
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL            => _API,
        CURLOPT_POST           => 1,
        CURLOPT_POSTFIELDS     => http_build_query(['action' => 'validate', 'license_key' => LICENSE_KEY, 'domain' => $d]),
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_TIMEOUT        => 3,
        CURLOPT_CONNECTTIMEOUT => 2,
        CURLOPT_SSL_VERIFYPEER => 0,
    ]);
    $res = curl_exec($ch); $hc = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
    if ($hc == 200 && $res) {
        $r = json_decode($res, 1); $s = strtolower($r['status'] ?? '');
        if ($s === 'success' || $s === 'active') {
            $_SESSION['lic_status']       = 'success';
            $_SESSION['lic_last_check']   = time();
            $_SESSION['lic_last_success'] = time();
            $ld = $r['data'] ?? [];
            if (!empty($ld['client_name']))    $_SESSION['lic_client_name']    = $ld['client_name'];
            if (!empty($ld['expired_at']))     $_SESSION['lic_expired_at']     = $ld['expired_at'];
            if (!empty($ld['days_remaining'])) $_SESSION['lic_days_remaining'] = (int)$ld['days_remaining'];
            if (!empty($ld['max_tv']))         $_SESSION['lic_max_tv']         = (int)$ld['max_tv'];
            return 1;
        }
    }
    if (isset($_SESSION['lic_last_success']) && (time() - $_SESSION['lic_last_success']) < 86400) return 1;
    if (basename($_SERVER['PHP_SELF']) !== 'process.php') _sle('INVALID');
    return 0;
}

function _sle($c) {
    unset($_SESSION['lic_status'], $_SESSION['lic_last_check']);
    $msg = "Akses Diblokir. Lisensi Suspended.";
    $wa  = "https://wa.me/62818110242";
    die("<html><head><title>Blocked</title><style>body{background:#030712;color:#fff;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;text-align:center}.box{background:rgba(15,23,42,.6);padding:40px;border-radius:30px;border:1px solid rgba(239,68,68,.2);max-width:400px}h1{color:#ef4444}a{display:inline-block;margin-top:20px;padding:12px 25px;background:#ef4444;color:#fff;text-decoration:none;border-radius:10px}</style></head><body><div class='box'><h1>Akses Diblokir</h1><p>$msg</p><small>Code: $c</small><br><a href='$wa'>Hubungi Dukungan</a></div></body></html>");
}

// ============================================================
// TV DATA
// ============================================================

function getTVData($c, $id) {
    $id = (int)$id;
    $s = $c->prepare("SELECT t.*, trx.jenis_paket, trx.start_time, trx.target_selesai, trx.remaining_seconds, trx.rate_per_jam, m.nama_member FROM tvs t LEFT JOIN transactions trx ON t.current_trx_id = trx.id LEFT JOIN members m ON trx.member_id = m.id WHERE t.id = ?");
    $s->bind_param("i", $id); $s->execute();
    $r = $s->get_result()->fetch_assoc(); $s->close();
    if ($r) $r['trx_data'] = $r['current_trx_id'] ? $r : null;
    return $r;
}

// ============================================================
// ADB HELPERS
// ============================================================

function getADBList() {
    global $conn;
    $p = get_setting($conn, 'adb_path');
    if (empty($p) || !file_exists($p)) return [];
    $o = @shell_exec("\"$p\" devices 2>&1");
    $d = [];
    if ($o) {
        foreach (explode("\n", $o) as $l) {
            if (preg_match('/^([^\s]+)\s+device$/', trim($l), $m)) $d[$m[1]] = 'device';
        }
    }
    return $d;
}

function checkADBStatus($ip, $l = null) {
    if (empty($ip)) return 'offline';
    $l = $l ?? getADBList();
    $s = (strpos($ip, ":5555") === false) ? trim($ip) . ":5555" : trim($ip);
    return isset($l[$s]) ? 'online' : 'offline';
}

function run_async($cmd) {
    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
        pclose(popen("start /B cmd /c \"$cmd\" > NUL 2>&1", "r"));
    } else {
        exec("$cmd > /dev/null 2>&1 &");
    }
}

// ============================================================
// FIX v2.5: ensureAccessibility — APPEND, bukan REPLACE
// ============================================================
// MASALAH LAMA:
//   "settings put secure enabled_accessibility_services <svc>"
//   me-REPLACE seluruh daftar → Android restart semua accessibility
//   services → Escobar accessibility mati sesaat → command TIMER
//   yang dikirim barengan gagal (di-drop).
//
// FIX:
//   1. Baca dulu daftar accessibility services yang aktif
//   2. Jika Escobar SUDAH ADA → SKIP (tidak tulis, tidak restart)
//   3. Jika Escobar BELUM ADA → APPEND ke daftar (bukan replace)
//
// HASIL:
//   - Accessibility tidak pernah mati sendiri lagi
//   - Service accessibility lain (TalkBack dll) tidak terganggu
//   - Command TIMER langsung berhasil karena accessibility tetap hidup
// ============================================================

function ensureAccessibility($adb, $device) {
    $svc = "com.example.escobar_tv_client/.EscobarAccessibilityService";

    // ── 1. Baca daftar accessibility yang aktif sekarang ──────
    // Harus sinkron (bukan async) karena kita perlu baca hasilnya
    // Output contoh:
    //   "com.google.talkback/.TalkbackService:com.example.escobar_tv_client/.EscobarAccessibilityService"
    //   atau "null" jika kosong
    $current = '';
    $raw = @shell_exec("\"$adb\" -s $device shell settings get secure enabled_accessibility_services 2>&1");
    if ($raw !== null && $raw !== false) {
        $current = trim($raw);
    }

    // Bersihkan — beberapa device return "null" (string) jika kosong
    if (empty($current) || strtolower($current) === 'null') {
        $current = '';
    }

    // ── 2. Cek apakah Escobar sudah terdaftar ────────────────
    if (strpos($current, $svc) !== false) {
        // Sudah aktif → SKIP, jangan tulis ulang!
        // Menulis nilai yang sama pun bisa trigger Android untuk
        // restart accessibility services di beberapa device/ROM.
        return;
    }

    // ── 3. Append Escobar ke daftar yang ada ──────────────────
    // Format Android: service1:service2:service3 (dipisah colon)
    if (!empty($current)) {
        $newList = $current . ':' . $svc;
    } else {
        $newList = $svc;
    }

    // Escape untuk shell safety — hapus karakter berbahaya
    $safeList = str_replace(["'", '"', '`', '$', ';', '|', '&', "\n", "\r"], '', $newList);

    $batch = "\"$adb\" -s $device shell settings put secure enabled_accessibility_services '$safeList'"
           . " && \"$adb\" -s $device shell settings put secure accessibility_enabled 1";

    run_async($batch);

    // Log untuk debugging
    $log_line = date('Y-m-d H:i:s') . " | ensureA11y | Device: $device"
              . " | Before: " . ($current ?: '(empty)')
              . " | After: $safeList\n";
    @file_put_contents(__DIR__ . "/adb_debug.log", $log_line, FILE_APPEND);
}

// ============================================================
// SINKRONISASI TIMER TV ↔ WEBSITE
// ============================================================

function controlTV($ip, $a, $e = null) {
    global $conn;
    $p = get_setting($conn, 'adb_path');
    if (empty($p) || !file_exists($p)) return 0;

    // Validasi IP — cegah shell injection
    if (!preg_match('/^[\d\.]+(?::\d+)?$/', trim($ip))) {
        error_log("controlTV: IP tidak valid: $ip");
        return 0;
    }

    $adb = $p;
    $s   = (strpos($ip, ':') === false) ? trim($ip) . ":5555" : trim($ip);

    if ($a !== 'connect') {
        run_async("\"$adb\" connect $s");
    }

    $cmd = "";

    switch ($a) {

        case 'connect':
            $cmd = "\"$adb\" connect $s";
            ensureAccessibility($adb, $s);
            break;

        case 'enable_a11y':
            ensureAccessibility($adb, $s);
            return 1;

        case 'power':
            $cmd = "\"$adb\" -s $s shell input keyevent 26";
            break;

        case 'home':
            $cmd = "\"$adb\" -s $s shell input keyevent 3";
            break;

        case 'vol_up':
            $cmd = "\"$adb\" -s $s shell input keyevent 24";
            break;

        case 'vol_down':
            $cmd = "\"$adb\" -s $s shell input keyevent 25";
            break;

        case 'wake':
            $cmd = "\"$adb\" -s $s shell input keyevent 224";
            break;

        case 'sleep':
            $cmd = "\"$adb\" -s $s shell input keyevent 223";
            break;

        case 'stop':
            // FIX KRITIS v2.9 ANDROID 11:
            // am force-stop membunuh SELURUH app termasuk EscobarAccessibilityService.
            // Di Android 11, setelah force-stop Android DISABLE accessibility service
            // dan tidak otomatis menyalakannya kembali.
            // Akibat: user harus enable A11y manual setiap kali billing dimulai.
            //
            // Solusi: kirim action STOP via start-foreground-service ke OverlayService saja.
            // Ini hanya menghentikan timer overlay, tanpa membunuh EscobarAccessibilityService.
            // A11y tetap hidup → tidak perlu enable ulang.
            $component = "com.example.escobar_tv_client/.OverlayService";
            $cmd = "\"$adb\" -s $s shell am start-foreground-service -n $component --es action STOP";
            run_async($cmd);
            $log_line = date('Y-m-d H:i:s') . " | Action: stop (gentle broadcast) | IP: $s\n";
            @file_put_contents(__DIR__ . "/adb_debug.log", $log_line, FILE_APPEND);
            return 1;

        case 'force_stop':
            $cmd = "\"$adb\" -s $s shell am force-stop com.example.escobar_tv_client";
            run_async($cmd);
            $log_line = date('Y-m-d H:i:s') . " | Action: force-stop | IP: $s\n";
            @file_put_contents(__DIR__ . "/adb_debug.log", $log_line, FILE_APPEND);
            return 1;



        case 'start_overlay':
            // ─────────────────────────────────────────────────────────
            // BUGFIX v2.4: target_timestamp TIDAK dihitung ulang di sini.
            // Nilai target_timestamp & start_timestamp dikirim dari caller
            // (process.php) yang sudah membacanya langsung dari DB.
            // Menghitung ulang di sini menyebabkan selisih detik karena
            // ada jeda antara query DB dan eksekusi fungsi ini.
            // ─────────────────────────────────────────────────────────
            ensureAccessibility($adb, $s);

            if (is_array($e)) {
                // Tambahkan server_time sebagai referensi sinkronisasi
                // tanpa menimpa nilai timestamp yang sudah ada
                $e['server_time'] = time();

                // Debug log — catat nilai timestamp dari caller
                $log_line = date('Y-m-d H:i:s')
                    . " | start_overlay"
                    . " | action="    . ($e['action']           ?? 'N/A')
                    . " | target_ts=" . ($e['target_timestamp'] ?? 'N/A')
                    . " | start_ts="  . ($e['start_timestamp']  ?? 'N/A')
                    . " | server_ts=" . $e['server_time']
                    . " | secs="      . ($e['seconds']          ?? 'N/A') . "\n";
                @file_put_contents(__DIR__ . "/adb_debug.log", $log_line, FILE_APPEND);
            }

            // Build args & kirim intent
            $args = "";
            if (is_array($e)) {
                $force_string_keys = [
                    'ov_color_normal', 'ov_color_warning', 'ov_color_danger',
                    'ov_color_openbill', 'ov_color_info', 'ov_color_label',
                    'ov_pos_timer', 'ov_pos_tvno', 'ov_pos_customer', 'ov_pos_rental',
                    'action', 'customer', 'rental', 'tv_no',
                ];
                // Timestamp pakai --el (long 64-bit) agar tidak overflow tahun 2038
                $force_long_keys = [
                    'target_timestamp', 'start_timestamp', 'server_time',
                ];

                foreach ($e as $k => $v) {
                    if (in_array($k, $force_string_keys)) {
                        $safe  = preg_replace("/[^a-zA-Z0-9 \.\-\_]/", '', (string)$v);
                        $args .= " --es " . $k . " '" . $safe . "'";
                    } elseif (in_array($k, $force_long_keys)) {
                        $args .= " --el " . $k . " " . (int)$v;
                    } elseif (is_numeric($v)) {
                        $args .= " --ei " . $k . " " . (int)$v;
                    } else {
                        $safe  = preg_replace("/[^a-zA-Z0-9 \.\-\_]/", '', (string)$v);
                        $args .= " --es " . $k . " '" . $safe . "'";
                    }
                }
            }
            $component = "com.example.escobar_tv_client/.OverlayService";
            $cmd       = "\"$adb\" -s $s shell am start-foreground-service -n $component$args";
            break;

        case 'pause_overlay':
            $component = "com.example.escobar_tv_client/.OverlayService";
            $cmd       = "\"$adb\" -s $s shell am start-foreground-service -n $component --es action PAUSE";
            break;

        case 'resume_overlay':
            $component = "com.example.escobar_tv_client/.OverlayService";
            $cmd       = "\"$adb\" -s $s shell am start-foreground-service -n $component --es action RESUME";
            break;

        // ── SHOW EXPIRED IMAGE ─────────────────────────────────
        case 'show_expired_image':
            ensureAccessibility($adb, $s);

            $image_url = '';
            if (is_array($e) && !empty($e['image_url'])) {
                $image_url = $e['image_url'];
            }

            if (empty($image_url)) {
                $log_line = date('Y-m-d H:i:s') . " | EXPIRED_IMAGE: image_url kosong | IP: $s\n";
                @file_put_contents(__DIR__ . "/adb_debug.log", $log_line, FILE_APPEND);
                return 1;
            }

            $safe_url  = str_replace(["'", '"', '`', '$', ';', '|', '&'], '', trim($image_url));
            $component = "com.example.escobar_tv_client/.OverlayService";
            $cmd       = "\"$adb\" -s $s shell am start-foreground-service -n $component"
                       . " --es action EXPIRED_IMAGE"
                       . " --es image_url '" . $safe_url . "'";

            $log_line = date('Y-m-d H:i:s') . " | EXPIRED_IMAGE HTTP | URL: $safe_url | IP: $s\n";
            @file_put_contents(__DIR__ . "/adb_debug.log", $log_line, FILE_APPEND);

            run_async($cmd);
            return 1;
    }

    if ($cmd) {
        $log_line = date('Y-m-d H:i:s') . " | Action: $a | IP: $s | CMD: $cmd\n";
        @file_put_contents(__DIR__ . "/adb_debug.log", $log_line, FILE_APPEND);
        run_async($cmd);
        return 1;
    }
    return 0;
}

// ============================================================
// AUTH
// ============================================================

function checkLogin() {
    if (!isset($_SESSION['user_id'])) {
        header("Location: login.php");
        exit;
    }
}

function onlyAdmin() {
    if (($_SESSION['role'] ?? '') !== 'admin') {
        header("Location: index.php?status=restricted");
        exit;
    }
}

function has_access($c, $p) {
    return ($_SESSION['role'] ?? '') === 'admin' || get_setting($c, $p) === '1';
}

function write_log($c, $a, $d) {
    if (session_status() === PHP_SESSION_NONE) { @session_start(); }
    if ($uid = ($_SESSION['user_id'] ?? 0)) {
        $un = $_SESSION['username'] ?? 'Sys';
        $s  = $c->prepare("INSERT INTO activity_logs (user_id, username, action, details, created_at) VALUES (?, ?, ?, ?, NOW())");
        $s->bind_param("isss", $uid, $un, $a, $d);
        $s->execute();
        $s->close();
    }
}

// ============================================================
// OVERLAY SETTINGS — Single query + static cache per-request
// ============================================================

function getOverlaySettings($conn) {
    static $_OV_CACHE = null;
    if ($_OV_CACHE !== null) return $_OV_CACHE;

    $result = $conn->query("SELECT setting_key, setting_value FROM overlay_settings");
    $map = [];
    if ($result) {
        while ($row = $result->fetch_assoc()) {
            $map[$row['setting_key']] = $row['setting_value'];
        }
    }

    $ov_get    = fn($key, $default) => (isset($map[$key]) && $map[$key] !== '') ? $map[$key] : $default;
    $stripHash = fn($hex) => ltrim(trim($hex), '#');

    $_OV_CACHE = [
        'ov_pos_timer'      => $ov_get('ov_pos_timer',      'BL'),
        'ov_pos_tvno'       => $ov_get('ov_pos_tvno',       'TL'),
        'ov_pos_customer'   => $ov_get('ov_pos_customer',   'TR'),
        'ov_pos_rental'     => $ov_get('ov_pos_rental',     'BR'),
        'ov_timer_size'     => (int) $ov_get('ov_timer_size',    '64'),
        'ov_label_size'     => (int) $ov_get('ov_label_size',    '11'),
        'ov_info_size'      => (int) $ov_get('ov_info_size',     '18'),
        'ov_rental_size'    => (int) $ov_get('ov_rental_size',   '14'),
        'ov_margin'         => (int) $ov_get('ov_margin',        '28'),
        'ov_show_tvno'      => (int) $ov_get('ov_show_tvno',     '1'),
        'ov_show_customer'  => (int) $ov_get('ov_show_customer', '1'),
        'ov_show_rental'    => (int) $ov_get('ov_show_rental',   '1'),
        'ov_show_label'     => (int) $ov_get('ov_show_label',    '1'),
        'ov_color_normal'   => $stripHash($ov_get('ov_color_normal',   '#00FF9D')),
        'ov_color_warning'  => $stripHash($ov_get('ov_color_warning',  '#FFD84D')),
        'ov_color_danger'   => $stripHash($ov_get('ov_color_danger',   '#FF4D6A')),
        'ov_color_openbill' => $stripHash($ov_get('ov_color_openbill', '#00E5FF')),
        'ov_color_info'     => $stripHash($ov_get('ov_color_info',     '#FFFFFF')),
        'ov_color_label'    => $stripHash($ov_get('ov_color_label',    '#CCFFFFFF')),
    ];

    return $_OV_CACHE;
}