<?php

declare(strict_types=1);

/**
 * api.php
 *
 * Unified API endpoint for the modded TiviMate application.
 */

require_once __DIR__ . '/includes/functions.php';

header('Content-Type: application/json; charset=UTF-8');

// =============================================================================
// Request initialization
// =============================================================================

$rawInput = file_get_contents('php://input');

if ($rawInput === false) {
    $rawInput = '';
}

$parsedJson = null;
$jsonDecodeErrorCode = JSON_ERROR_NONE;

if ($rawInput !== '') {
    $parsedJson = json_decode($rawInput, true);
    $jsonDecodeErrorCode = json_last_error();
}

$requestData = [];

if (
    $jsonDecodeErrorCode === JSON_ERROR_NONE &&
    is_array($parsedJson)
) {
    $requestData = $parsedJson;
} elseif (!empty($_POST)) {
    $requestData = $_POST;
}

// =============================================================================
// Action routing
// =============================================================================

$action = '';

if (isset($requestData['action'])) {
    $action = (string) $requestData['action'];
} elseif (isset($requestData['cmd'])) {
    $action = (string) $requestData['cmd'];
} elseif (isset($requestData['request_type'])) {
    $action = (string) $requestData['request_type'];
} elseif (isset($_GET['action'])) {
    $action = (string) $_GET['action'];
} elseif (isset($_POST['action'])) {
    $action = (string) $_POST['action'];
}

// Infer an action when the request did not explicitly provide one.
if ($action === '') {
    if (
        isset($requestData['version_code']) &&
        isset($requestData['pkg_name_crc'])
    ) {
        $action = 'checkAppInfo_v2';
    } elseif (
        isset($requestData['device_id']) &&
        !isset($requestData['version_code'])
    ) {
        $action = 'getDevices_v3';
    } else {
        // No action/cmd/request_type and none of the other identifying
        // fields either — this is the app's lightweight PanelAutoImport
        // auto-sync call (a bare GET with no params at all; see
        // logDevice()'s matching comment above). It's still routed to
        // handleGetDevices() via the switch's default case below, exactly
        // like an explicit getDevices_v3 request — this label just makes
        // that visible in analytics.php instead of showing as "unknown",
        // which reads as an error when it's actually the majority of
        // real, expected app traffic.
        $action = 'getDevices_autoImport';
    }
}

// =============================================================================
// Execute handler
// =============================================================================

logDevice($requestData, $action);
logRequest('api.php:' . ($action ?: 'unknown'));

switch ($action) {
    case 'checkAppInfo_v2':
        handleCheckAppInfo($requestData);
        break;

    case 'addDevice_v2':
        handleAddDevice($requestData);
        break;

    case 'getDevices_v3':
    default:
        handleGetDevices($requestData);
        break;
}

exit;

// =============================================================================
// Device logging
// =============================================================================

/**
 * Record or update a device connection.
 * Logs device from POST data (device_id) when available,
 * or from IP + User-Agent for GET requests (PanelAutoImport).
 */
function logDevice(array $requestData, string $action): void
{
    global $db;

    if (!isset($db)) {
        return;
    }

    $now = date('Y-m-d H:i:s');
    $ip = '';
    // Prefer X-Forwarded-For from reverse proxy, fall back to REMOTE_ADDR
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        // Take the first (leftmost) IP — the original client
        $ip = trim(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]);
    } elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
        $ip = trim($_SERVER['HTTP_X_REAL_IP']);
    } else {
        $ip = (string) ($_SERVER['REMOTE_ADDR'] ?? '');
    }
    $userAgent = (string) ($_SERVER['HTTP_USER_AGENT'] ?? '');

    $deviceId = isset($requestData['device_id'])
        ? trim((string) $requestData['device_id'])
        : '';

    // If no device_id in POST data, generate one from IP + User-Agent
    // This captures GET requests from PanelAutoImport in the app
    if ($deviceId === '') {
        if ($ip === '') {
            return;
        }
        // Use a hash of IP + UA as a synthetic device identifier
        $deviceId = 'auto_' . substr(hash('sha256', $ip . '|' . $userAgent), 0, 16);
    }

    $existing = $db->select(
        'devices',
        '*',
        'device_id = :device_id',
        '',
        [
            ':device_id' => $deviceId,
        ]
    );

    if (empty($existing)) {
        $insertData = [
            'device_id' => $deviceId,
            'device_id2' => (string) ($requestData['device_id2'] ?? ''),
            'mac_address' => (string) ($requestData['mac_address'] ?? ''),
            'version_code' => (string) ($requestData['version_code'] ?? ''),
            'last_seen' => $now,
            'first_seen' => $now,
            'action' => $action ?: 'getDevices_v3',
            'ip_address' => $ip,
        ];

        $db->insert('devices', $insertData);
        return;
    }

    $existingVersion = '';
    if (
        isset($existing[0]) &&
        is_array($existing[0]) &&
        isset($existing[0]['version_code'])
    ) {
        $existingVersion = (string) $existing[0]['version_code'];
    }

    $updateData = [
        'last_seen' => $now,
        'action' => $action ?: 'getDevices_v3',
        'ip_address' => $ip,
        'version_code' => isset($requestData['version_code'])
            ? (string) $requestData['version_code']
            : $existingVersion,
    ];

    $db->update(
        'devices',
        $updateData,
        'device_id = :device_id',
        [
            ':device_id' => $deviceId,
        ]
    );
}

// =============================================================================
// Handler functions
// =============================================================================

/**
 * License and activation check.
 */
function handleCheckAppInfo(array $requestData): void
{
    $response = [
        'result' => [
            'newApkUrl' => '',
            'newVersionName' => '',
            'changelog' => '',
            'shouldUpdateGooglePlayVersion' => false,
            'forceAutoUpdate' => false,
            'stagedRolloutDays' => 0,
            'isActivated' => true,
            'deviceName' => 'TV',
            'account' => 'Premium',
        ],
    ];

    echo json_encode(
        $response,
        JSON_PRETTY_PRINT |
        JSON_UNESCAPED_SLASHES
    );
}

/**
 * Return portal and DNS configuration.
 */
function handleGetDevices(array $requestData): void
{
    global $db;

    $res = $db->select(
        'dns',
        '*',
        '',
        ''
    );

    $mes = $db->select(
        'welcome',
        '*',
        'id = :id',
        '',
        [
            ':id' => 1,
        ]
    );

    $rows = [];

    foreach ($res as $index => $row) {
        $url = $row['url'] ?? '';

        // Pass the base server URL as-is. The app prompts the user to log in
        // with their own username/password for Xtream Codes.
        $rows[] = [
            'id' => $index + 1,
            'name' => $row['title'],
            'url' => $url,
            'type' => 'xtream',
        ];
    }

    // Determine the device ID for announcement deduplication
    $deviceId = isset($requestData['device_id'])
        ? trim((string) $requestData['device_id'])
        : '';
    if ($deviceId === '') {
        $ip = '';
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ip = trim(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]);
        } elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
            $ip = trim($_SERVER['HTTP_X_REAL_IP']);
        } else {
            $ip = (string) ($_SERVER['REMOTE_ADDR'] ?? '');
        }
        $userAgent = (string) ($_SERVER['HTTP_USER_AGENT'] ?? '');
        $deviceId = 'auto_' . substr(hash('sha256', $ip . '|' . $userAgent), 0, 16);
    }

    // Get active announcements this device hasn't seen yet
    $now = time();
    $activeAnnouncements = [];

    $allNotes = $db->select('note', '*', '', 'createdate DESC');
    foreach ($allNotes as $note) {
        // Check if within time window
        $startTime = $note['start_time'] ?? '';
        $endTime = $note['end_time'] ?? '';

        // Skip if start_time is in the future
        if ($startTime !== '' && strtotime($startTime) > $now) {
            continue;
        }
        // Skip if end_time has passed
        if ($endTime !== '' && strtotime($endTime) < $now) {
            continue;
        }

        // Return all active announcements - app handles dedup via SharedPreferences
        // broadcast_key changes on re-broadcast so app treats it as new
        $activeAnnouncements[] = [
            'id' => (int) $note['id'],
            'broadcast_key' => (int) $note['id'] . '_' . strtotime($note['createdate'] ?? '0'),
            'title' => $note['note_title'] ?? 'Announcement',
            'message' => $note['note_content'] ?? '',
        ];
    }

    $final = [
        'portals' => $rows,
        'portal_urls' => array_column($rows, 'url'),
        'intro_url' => '',
        'message_one' => $mes[0]['message_one'] ?? 'Welcome',
        'message_two' => $mes[0]['message_two'] ?? '',
        'message_three' => $mes[0]['message_three'] ?? 'Enjoy!',
        'announcements' => $activeAnnouncements,
    ];

    $json = json_encode(
        $final,
        JSON_UNESCAPED_SLASHES |
        JSON_PRETTY_PRINT
    );

    if ($json === false) {
        throw new RuntimeException(
            'Unable to encode portal configuration: ' .
            json_last_error_msg()
        );
    }

    $encryptionKey = generateRandomKey();

    $outputJson =
        '{"dns_backup":"' .
        Encryption::runfake() .
        '","portals":' .
        getCustomValue($encryptionKey) .
        encr($json, $encryptionKey) .
        ',"url_backup":"' .
        Encryption::runfake() .
        '"}';

    echo Encryption::run($outputJson);
}

/**
 * Acknowledge device registration.
 */
function handleAddDevice(array $requestData): void
{
    $response = [
        'result' => [
            'success' => true,
            'deviceName' => isset($requestData['device_id'])
                ? (string) $requestData['device_id']
                : 'Device',
        ],
    ];

    echo json_encode(
        $response,
        JSON_PRETTY_PRINT |
        JSON_UNESCAPED_SLASHES
    );
}

// =============================================================================
// Encryption functions
// =============================================================================

/**
 * Custom hex encoding for the AES key.
 */
function getCustomValue($number): string
{
    $values = [
        0 => '19fe',
        1 => '164f',
        2 => '25cb',
        3 => '55ec',
        4 => '945d',
        5 => '888c',
        6 => '02de',
        7 => '199e',
        8 => '423f',
        9 => '978d',
    ];

    $numberString = (string) $number;
    $result = '';

    for (
        $index = 0;
        $index < strlen($numberString);
        $index++
    ) {
        $digit = (int) $numberString[$index];

        if (isset($values[$digit])) {
            $result .= $values[$digit];
        }
    }

    return $result;
}

/**
 * AES-256-CBC encryption using the native library's fixed IV.
 */
function encr($data, $key): string
{
    $iv = 'R5tghjg^999(@#Gg';

    $encrypted = openssl_encrypt(
        (string) $data,
        'aes-256-cbc',
        (string) $key,
        OPENSSL_RAW_DATA,
        $iv
    );

    if ($encrypted === false) {
        throw new RuntimeException(
            'AES encryption failed.'
        );
    }

    return bin2hex($encrypted);
}

/**
 * Generate a random 32-digit numeric key.
 */
function generateRandomKey(): string
{
    $key = '';

    for ($index = 0; $index < 32; $index++) {
        $key .= (string) random_int(0, 9);
    }

    return $key;
}
