<?php

declare(strict_types=1);

/*
|--------------------------------------------------------------------------
| PLAY.PHP UNIFIED - NEXUS + SGX
|--------------------------------------------------------------------------
| Struktur API project:
|
| public_html/_app/src/Adapters/NexusGameApiAdapter.php
| public_html/_app/src/Adapters/SgxGameApiAdapter.php
| public_html/_app/src/GameApi.php
| public_html/_app/config/api.php
|--------------------------------------------------------------------------
*/

require_once __DIR__ . '/bootstrap.php';

header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('Referrer-Policy: same-origin');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');

/*
|--------------------------------------------------------------------------
| Helper error
|--------------------------------------------------------------------------
*/

function playError(
    string $message,
    int $statusCode = 400,
    string $returnUrl = 'index.php'
): void {
    http_response_code($statusCode);

    $message = htmlspecialchars(
        $message,
        ENT_QUOTES | ENT_SUBSTITUTE,
        'UTF-8'
    );

    $returnUrl = htmlspecialchars(
        $returnUrl,
        ENT_QUOTES | ENT_SUBSTITUTE,
        'UTF-8'
    );

    echo '<!doctype html>
<html lang="id">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Game Belum Dapat Dibuka</title>
    <style>
        *{box-sizing:border-box}
        body{
            margin:0;
            min-height:100vh;
            display:grid;
            place-items:center;
            padding:18px;
            color:#fff;
            background:
                radial-gradient(circle at 50% 0,rgba(0,137,255,.28),transparent 300px),
                linear-gradient(180deg,#031431,#010610);
            font-family:Arial,Helvetica,sans-serif
        }
        .box{
            width:min(100%,430px);
            padding:22px;
            border:1px solid rgba(255,198,48,.42);
            border-radius:16px;
            background:linear-gradient(180deg,#092750,#031127);
            box-shadow:0 18px 40px rgba(0,0,0,.48);
            text-align:center
        }
        .icon{
            display:grid;
            width:58px;
            height:58px;
            margin:0 auto 13px;
            place-items:center;
            border:2px solid #ffd44f;
            border-radius:50%;
            color:#2e1800;
            background:linear-gradient(145deg,#fff3a0,#ffbc21);
            font-size:27px;
            font-weight:1000
        }
        h1{
            margin:0;
            color:#ffd34f;
            font-size:21px
        }
        p{
            margin:10px 0 0;
            color:#b3c4d9;
            font-size:13px;
            line-height:1.6
        }
        a{
            display:flex;
            min-height:42px;
            align-items:center;
            justify-content:center;
            margin-top:17px;
            border:1px solid #ffe382;
            border-radius:9px;
            color:#2b1600;
            background:linear-gradient(180deg,#ffe987,#f3ab16);
            font-size:12px;
            font-weight:1000;
            text-decoration:none
        }
    </style>
</head>
<body>
    <main class="box">
        <span class="icon">!</span>
        <h1>Game Belum Dapat Dibuka</h1>
        <p>' . $message . '</p>
        <a href="' . $returnUrl . '">KEMBALI KE DAFTAR GAME</a>
    </main>
</body>
</html>';

    exit;
}

/*
|--------------------------------------------------------------------------
| Helper database
|--------------------------------------------------------------------------
*/

function playTableExists(PDO $pdo, string $table): bool
{
    $statement = $pdo->prepare(
        'SELECT COUNT(*)
         FROM information_schema.TABLES
         WHERE TABLE_SCHEMA = DATABASE()
           AND TABLE_NAME = :table_name'
    );

    $statement->execute([
        ':table_name' => $table,
    ]);

    return (int) $statement->fetchColumn() > 0;
}

function playTableColumns(PDO $pdo, string $table): array
{
    $statement = $pdo->prepare(
        'SELECT COLUMN_NAME
         FROM information_schema.COLUMNS
         WHERE TABLE_SCHEMA = DATABASE()
           AND TABLE_NAME = :table_name'
    );

    $statement->execute([
        ':table_name' => $table,
    ]);

    $columns = [];

    foreach ($statement->fetchAll() as $row) {
        $columnName = (string) (
            $row['COLUMN_NAME'] ??
            ''
        );

        if ($columnName !== '') {
            $columns[$columnName] = true;
        }
    }

    return $columns;
}

/*
|--------------------------------------------------------------------------
| Muat sistem GameApi dari _app/src
|--------------------------------------------------------------------------
*/

function playLoadGameApi(): void
{
    if (
        class_exists('GameApi') &&
        class_exists('NexusGameApiAdapter') &&
        class_exists('SgxGameApiAdapter')
    ) {
        return;
    }

    $srcPath = defined('APP_SRC_PATH')
        ? APP_SRC_PATH
        : __DIR__ . '/_app/src';

    $requiredFiles = [
        $srcPath . '/GameApiException.php',
        $srcPath . '/Contracts/GameApiAdapterInterface.php',
        $srcPath . '/Adapters/AbstractGameApiAdapter.php',
        $srcPath . '/Adapters/SgxGameApiAdapter.php',
        $srcPath . '/Adapters/NexusGameApiAdapter.php',
        $srcPath . '/GameApi.php',
    ];

    foreach ($requiredFiles as $requiredFile) {
        if (!is_file($requiredFile)) {
            throw new RuntimeException(
                'File sistem API tidak ditemukan: ' .
                str_replace(
                    __DIR__ . '/',
                    '',
                    $requiredFile
                )
            );
        }

        require_once $requiredFile;
    }

    if (!class_exists('GameApi')) {
        throw new RuntimeException(
            'Class GameApi tidak berhasil dimuat.'
        );
    }
}

/*
|--------------------------------------------------------------------------
| Ambil konfigurasi API
|--------------------------------------------------------------------------
*/

function playInitGameApi(): void
{
    playLoadGameApi();

    $configPath = defined('APP_CONFIG_PATH')
        ? APP_CONFIG_PATH . '/api.php'
        : __DIR__ . '/_app/config/api.php';

    if (!is_file($configPath)) {
        throw new RuntimeException(
            'Konfigurasi API tidak ditemukan: ' .
            str_replace(
                __DIR__ . '/',
                '',
                $configPath
            )
        );
    }

    $apiConfig = require $configPath;

    if (!is_array($apiConfig)) {
        throw new RuntimeException(
            'File _app/config/api.php harus mengembalikan array konfigurasi.'
        );
    }

    GameApi::init($apiConfig);
}

/*
|--------------------------------------------------------------------------
| Cari player code user
|--------------------------------------------------------------------------
*/

function playResolvePlayerCode(
    array $userRow,
    string $api,
    string $fallbackUsername
): string {
    $columns = $api === 'nexus'
        ? [
            'player_code_nexus',
            'nexus_player_code',
            'extplayer_nexus',
            'nexus_username',
            'extplayer',
        ]
        : [
            'player_code_sgx',
            'sgx_player_code',
            'extplayer_sgx',
            'sgx_username',
            'extplayer',
        ];

    foreach ($columns as $column) {
        if (!array_key_exists($column, $userRow)) {
            continue;
        }

        $value = trim(
            (string) $userRow[$column]
        );

        if ($value !== '') {
            return $value;
        }
    }

    return trim($fallbackUsername);
}

/*
|--------------------------------------------------------------------------
| URL publik website untuk lobby/return provider
|--------------------------------------------------------------------------
| Nexus mewajibkan lobby_url berupa URL penuh.
|--------------------------------------------------------------------------
*/

function playBuildLobbyUrl(): string
{
    $forwardedProto = strtolower(
        trim(
            (string) (
                $_SERVER['HTTP_X_FORWARDED_PROTO'] ??
                ''
            )
        )
    );

    $isHttps =
        (
            !empty($_SERVER['HTTPS']) &&
            strtolower((string) $_SERVER['HTTPS']) !== 'off'
        ) ||
        $forwardedProto === 'https' ||
        (
            isset($_SERVER['SERVER_PORT']) &&
            (int) $_SERVER['SERVER_PORT'] === 443
        );

    $scheme = $isHttps
        ? 'https'
        : 'http';

    $host = trim(
        (string) (
            $_SERVER['HTTP_HOST'] ??
            $_SERVER['SERVER_NAME'] ??
            ''
        )
    );

    /*
     * Cegah Host Header Injection.
     */
    if (
        $host === '' ||
        preg_match(
            '/^[A-Za-z0-9.-]+(?::[0-9]{1,5})?$/',
            $host
        ) !== 1
    ) {
        throw new RuntimeException(
            'Domain website tidak dapat dideteksi untuk lobby_url.'
        );
    }

    $scriptName = str_replace(
        '\\',
        '/',
        (string) (
            $_SERVER['SCRIPT_NAME'] ??
            '/play.php'
        )
    );

    $basePath = rtrim(
        str_replace(
            '\\',
            '/',
            dirname($scriptName)
        ),
        '/'
    );

    if (
        $basePath === '.' ||
        $basePath === '/'
    ) {
        $basePath = '';
    }

    $lobbyUrl =
        $scheme .
        '://' .
        $host .
        $basePath .
        '/index.php';

    if (
        filter_var(
            $lobbyUrl,
            FILTER_VALIDATE_URL
        ) === false
    ) {
        throw new RuntimeException(
            'Lobby URL website tidak valid.'
        );
    }

    return $lobbyUrl;
}

/*
|--------------------------------------------------------------------------
| URL keluar game
|--------------------------------------------------------------------------
| Nexus akan kembali ke URL ini. Token hanya tersimpan di session pengguna.
|--------------------------------------------------------------------------
*/

function playBuildGameExitUrl(
    string $token,
    string $returnUrl
): string {
    if (
        preg_match(
            '/^[a-f0-9]{64}$/',
            $token
        ) !== 1
    ) {
        throw new RuntimeException(
            'Token keluar game tidak valid.'
        );
    }

    $lobbyUrl = playBuildLobbyUrl();

    $exitUrl = preg_replace(
        '~/index\.php$~',
        '/game-exit.php',
        $lobbyUrl
    );

    if (
        !is_string($exitUrl) ||
        $exitUrl === $lobbyUrl
    ) {
        throw new RuntimeException(
            'URL keluar game tidak dapat dibuat.'
        );
    }

    $exitUrl .=
        '?' .
        http_build_query(
            [
                'token' => $token,
                'return' => $returnUrl,
            ],
            '',
            '&',
            PHP_QUERY_RFC3986
        );

    if (
        filter_var(
            $exitUrl,
            FILTER_VALIDATE_URL
        ) === false
    ) {
        throw new RuntimeException(
            'URL keluar game tidak valid.'
        );
    }

    return $exitUrl;
}

/*
|--------------------------------------------------------------------------
| Deteksi request dari halaman website sendiri
|--------------------------------------------------------------------------
*/

function playHasSameOriginReferer(): bool
{
    $referer = trim(
        (string) (
            $_SERVER['HTTP_REFERER'] ??
            ''
        )
    );

    if ($referer === '') {
        return false;
    }

    $refererHost = strtolower(
        (string) parse_url(
            $referer,
            PHP_URL_HOST
        )
    );

    $currentHost = strtolower(
        trim(
            (string) (
                $_SERVER['HTTP_HOST'] ??
                $_SERVER['SERVER_NAME'] ??
                ''
            )
        )
    );

    $currentHost = preg_replace(
        '/:[0-9]+$/',
        '',
        $currentHost
    );

    return
        $refererHost !== '' &&
        $currentHost !== '' &&
        hash_equals(
            $currentHost,
            $refererHost
        );
}

/*
|--------------------------------------------------------------------------
| Validasi login
|--------------------------------------------------------------------------
*/

if (!$isLoggedIn || $userId <= 0) {
    $nextUrl = $_SERVER['REQUEST_URI'] ?? 'index.php';

    header(
        'Location: login.php?next=' .
        rawurlencode($nextUrl)
    );

    exit;
}

/*
|--------------------------------------------------------------------------
| Parameter launch
|--------------------------------------------------------------------------
*/

$api = strtolower(
    trim(
        (string) (
            $_GET['api'] ??
            ''
        )
    )
);

$providerCode = strtoupper(
    trim(
        (string) (
            $_GET['provider'] ??
            ''
        )
    )
);

$gameCode = trim(
    (string) (
        $_GET['game'] ??
        ''
    )
);

$requestedType = strtoupper(
    trim(
        (string) (
            $_GET['type'] ??
            ''
        )
    )
);

$returnUrl = trim(
    (string) (
        $_GET['return'] ??
        'index.php'
    )
);

/*
 * Return URL harus lokal.
 */
if (
    $returnUrl === '' ||
    preg_match(
        '/^[A-Za-z][A-Za-z0-9+.-]*:/',
        $returnUrl
    ) ||
    strpos($returnUrl, '//') === 0 ||
    strpos($returnUrl, '\\') !== false ||
    preg_match('/[\x00-\x1F\x7F]/', $returnUrl)
) {
    $returnUrl = 'index.php';
}

$returnUrl = ltrim($returnUrl, '/');

/*
|--------------------------------------------------------------------------
| Validasi parameter
|--------------------------------------------------------------------------
*/

if (!in_array($api, ['nexus', 'sgx'], true)) {
    playError(
        'Sumber API game tidak valid.',
        400,
        $returnUrl
    );
}

if (
    $providerCode === '' ||
    preg_match(
        '/^[A-Z0-9_.-]{1,80}$/',
        $providerCode
    ) !== 1
) {
    playError(
        'Kode provider tidak valid.',
        400,
        $returnUrl
    );
}

if (
    $gameCode === '' ||
    preg_match(
        '/^[A-Za-z0-9_.:@-]{1,191}$/',
        $gameCode
    ) !== 1
) {
    playError(
        'Kode game tidak valid.',
        400,
        $returnUrl
    );
}

/*
|--------------------------------------------------------------------------
| Validasi game berdasarkan tabel split
|--------------------------------------------------------------------------
*/

$table = $api === 'sgx'
    ? 'games_sgx'
    : 'games_nexus';

$providerColumn = $api === 'sgx'
    ? 'game_provider'
    : 'provider_code';

if (!playTableExists($pdo, $table)) {
    playError(
        'Tabel ' . $table . ' tidak ditemukan.',
        500,
        $returnUrl
    );
}

$gameColumns = playTableColumns(
    $pdo,
    $table
);

foreach (
    [
        $providerColumn,
        'game_code',
        'game_name',
    ] as $requiredColumn
) {
    if (!isset($gameColumns[$requiredColumn])) {
        playError(
            'Kolom ' . $requiredColumn .
            ' tidak ditemukan pada ' . $table . '.',
            500,
            $returnUrl
        );
    }
}

$where = [
    $providerColumn . ' = :provider_code',
    'game_code = :game_code',
];

if (isset($gameColumns['game_status'])) {
    $where[] =
        '(game_status = 1 OR ' .
        "UPPER(CAST(game_status AS CHAR)) " .
        "IN ('ACTIVE','ON','TRUE'))";
}

if (isset($gameColumns['is_active'])) {
    $where[] = 'is_active = 1';
}

if (isset($gameColumns['is_show'])) {
    $where[] = 'is_show = 1';
}

$typeExpression = isset($gameColumns['game_type'])
    ? "UPPER(TRIM(COALESCE(game_type, '')))"
    : "''";

$gameSql =
    'SELECT
        id,
        ' . $providerColumn . ' AS provider_code,
        game_code,
        game_name,
        ' . $typeExpression . ' AS game_type
     FROM ' . $table . '
     WHERE ' . implode(' AND ', $where) . '
     LIMIT 1';

$gameStatement = $pdo->prepare($gameSql);

$gameStatement->execute([
    ':provider_code' => $providerCode,
    ':game_code' => $gameCode,
]);

$game = $gameStatement->fetch();

if (!$game) {
    playError(
        'Game tidak ditemukan, nonaktif, atau belum ditampilkan.',
        404,
        $returnUrl
    );
}

/*
|--------------------------------------------------------------------------
| Ambil data user terbaru dari tabel users
|--------------------------------------------------------------------------
*/

$userStatement = $pdo->prepare(
    'SELECT *
     FROM users
     WHERE id = :user_id
     LIMIT 1'
);

$userStatement->execute([
    ':user_id' => $userId,
]);

$userRow = $userStatement->fetch();

if (!$userRow) {
    playError(
        'Data pengguna login tidak ditemukan.',
        401,
        'login.php'
    );
}

$playerCode = playResolvePlayerCode(
    $userRow,
    $api,
    $username
);

if (
    $playerCode === '' ||
    preg_match(
        '/^[A-Za-z0-9_.-]{3,64}$/',
        $playerCode
    ) !== 1
) {
    playError(
        'Player code pengguna belum tersedia atau tidak valid untuk ' .
        strtoupper($api) . '.',
        422,
        $returnUrl
    );
}

/*
|--------------------------------------------------------------------------
| Wallet lokal -> provider -> launch game
|--------------------------------------------------------------------------
*/

$walletServiceFile = defined('APP_SRC_PATH')
    ? APP_SRC_PATH . '/GameWalletService.php'
    : __DIR__ . '/_app/src/GameWalletService.php';

if (!is_file($walletServiceFile)) {
    playError(
        'GameWalletService.php belum dipasang.',
        500,
        $returnUrl
    );
}

require_once $walletServiceFile;

$walletService = null;
$walletSession = null;

try {
    /*
     * Muat adapter Nexus dan SGX menggunakan konfigurasi yang sudah ada.
     */
    GameWalletService::bootApi();

    $walletService =
        new GameWalletService($pdo);

    /*
     * Browser Back dari halaman provider sering kembali ke play.php.
     * Jangan langsung launch ulang. Bila request berasal dari provider
     * atau referer kosong, selesaikan sesi dan kembali ke daftar game.
     */
    if (
        $walletService->hasOpenSession(
            $userId
        )
    ) {
        if (!playHasSameOriginReferer()) {
            $returnResults =
                $walletService
                    ->exitOpenSessions(
                        $userId,
                        'closed'
                    );

            $returnedAmount = 0.0;

            foreach (
                $returnResults
                as $returnResult
            ) {
                $returnedAmount += (float) (
                    $returnResult[
                        'returned_amount'
                    ] ?? 0
                );
            }

            $returnBalanceStatement =
                $pdo->prepare(
                    'SELECT balance_local
                     FROM users
                     WHERE id = :user_id
                     LIMIT 1'
                );

            $returnBalanceStatement->execute([
                ':user_id' => $userId,
            ]);

            $returnedLocalBalance = (float) (
                $returnBalanceStatement
                    ->fetchColumn() ?:
                0
            );

            $_SESSION['balance_local'] =
                $returnedLocalBalance;

            $_SESSION['balance'] =
                $returnedLocalBalance;

            $_SESSION['game_wallet_notice'] = [
                'type' => 'success',
                'message' =>
                    $returnedAmount > 0
                        ? (
                            'Saldo game IDR ' .
                            number_format(
                                $returnedAmount,
                                0,
                                ',',
                                '.'
                            ) .
                            ' berhasil dikembalikan.'
                        )
                        : 'Sesi game berhasil ditutup.',
            ];

            unset(
                $_SESSION['last_game'],
                $_SESSION[
                    'game_exit_token'
                ]
            );

            header(
                'Location: ' . $returnUrl,
                true,
                302
            );

            exit;
        }

        throw new RuntimeException(
            'Masih ada sesi game aktif. Kembali ke halaman website untuk menyelesaikan sesi sebelum membuka game lain.'
        );
    }

    /*
     * Seluruh saldo lokal dipindahkan ke provider.
     * Sesi provider lama diselesaikan lebih dahulu.
     */
    $walletSession =
        $walletService->enterGame(
            $userId,
            $api,
            $providerCode,
            $gameCode,
            (string) (
                $game['game_name'] ??
                $gameCode
            )
        );

    $playerCode = (string) (
        $walletSession[
            'player_code'
        ] ??
        $playerCode
    );

    $exitToken =
        bin2hex(
            random_bytes(32)
        );

    $_SESSION['game_exit_token'] =
        $exitToken;

    $exitUrl =
        playBuildGameExitUrl(
            $exitToken,
            $returnUrl
        );

    $adapter =
        GameApi::adapter($api);

    $options = [
        'lang' => 'id',
    ];

    if ($api === 'nexus') {
        $options['provider_code'] =
            $providerCode;

        /*
         * Tombol lobby/kembali pada Nexus akan menarik saldo provider
         * lalu mengembalikannya ke balance_local.
         */
        $options['lobby_url'] =
            $exitUrl;
    }

    $launchResult =
        $adapter->launchGame(
            $playerCode,
            $gameCode,
            $options
        );

    $launchUrl = trim(
        (string) (
            $launchResult[
                'launch_url'
            ] ?? ''
        )
    );

    if (
        $launchUrl === '' ||
        filter_var(
            $launchUrl,
            FILTER_VALIDATE_URL
        ) === false
    ) {
        throw new RuntimeException(
            strtoupper($api) .
            ' tidak mengirim launch_url yang valid.'
        );
    }

    /*
     * Sumber saldo tampilan tetap database.
     * Setelah ENTER berhasil, saldo lokal biasanya menjadi nol.
     */
    $balanceStatement =
        $pdo->prepare(
            'SELECT balance_local
             FROM users
             WHERE id = :user_id
             LIMIT 1'
        );

    $balanceStatement->execute([
        ':user_id' => $userId,
    ]);

    $latestLocalBalance = (float) (
        $balanceStatement->fetchColumn() ?:
        0
    );

    $_SESSION['balance_local'] =
        $latestLocalBalance;

    $_SESSION['balance'] =
        $latestLocalBalance;

    $_SESSION['last_game'] = [
        'session_id' => (int) (
            $walletSession[
                'session_id'
            ] ?? 0
        ),
        'session_code' => (string) (
            $walletSession[
                'session_code'
            ] ?? ''
        ),
        'api' => $api,
        'provider' => $providerCode,
        'game' => $gameCode,
        'type' => (string) (
            $game['game_type'] ??
            $requestedType
        ),
        'name' => (string) (
            $game['game_name'] ??
            'Game'
        ),
        'player_code' => $playerCode,
        'exit_url' => $exitUrl,
        'opened_at' => time(),
    ];

    session_write_close();

    header(
        'Location: ' . $launchUrl,
        true,
        302
    );

    exit;
} catch (Throwable $exception) {
    /*
     * Bila wallet session sudah dibuat tetapi launch gagal, saldo aktual
     * provider ditarik kembali dan sesi ditandai failed.
     */
    if (
        $walletService instanceof
            GameWalletService &&
        is_array($walletSession) &&
        !empty(
            $walletSession['session_id']
        )
    ) {
        try {
            $walletService->failSession(
                $userId,
                (int) $walletSession[
                    'session_id'
                ]
            );
        } catch (Throwable $rollbackException) {
            error_log(
                'PLAY WALLET ROLLBACK ERROR: ' .
                $rollbackException->getMessage()
            );
        }
    }

    error_log(
        'PLAY WALLET/LAUNCH ERROR: ' .
        json_encode(
            [
                'api' => $api,
                'provider' => $providerCode,
                'game' => $gameCode,
                'user_id' => $userId,
                'player_code' => $playerCode,
                'wallet_session_id' =>
                    is_array($walletSession)
                        ? (
                            $walletSession[
                                'session_id'
                            ] ?? null
                        )
                        : null,
                'message' =>
                    $exception->getMessage(),
            ],
            JSON_UNESCAPED_SLASHES |
            JSON_UNESCAPED_UNICODE
        )
    );

    playError(
        $exception->getMessage(),
        502,
        $returnUrl
    );
}
