<?php

defined('APP_BOOTSTRAPPED') or exit('No direct access.');

class NexusGameApiAdapter extends AbstractGameApiAdapter
{
    private $agentCode;
    private $agentToken;

    public function __construct(array $config)
    {
        parent::__construct($config);

        $this->agentCode = trim(
            isset($config['agent_code'])
                ? (string) $config['agent_code']
                : ''
        );

        $this->agentToken = trim(
            isset($config['agent_token'])
                ? (string) $config['agent_token']
                : ''
        );

        if ($this->agentCode === '') {
            throw new GameApiException(
                'Nexus agent code belum dikonfigurasi.',
                500
            );
        }

        if ($this->agentToken === '') {
            throw new GameApiException(
                'Nexus agent token belum dikonfigurasi.',
                500
            );
        }
    }

    public function getApiName()
    {
        return 'nexus';
    }

    /*
    |--------------------------------------------------------------------------
    | Provider list
    |--------------------------------------------------------------------------
    */

    public function providerList()
    {
        $request = $this->postJson(
            $this->authPayload('provider_list')
        );

        $response = $request['body'];

        $this->assertSuccess(
            $response,
            'Nexus provider_list gagal.'
        );

        $rawProviders = isset($response['providers'])
            ? $response['providers']
            : array();

        if (!is_array($rawProviders)) {
            throw new GameApiException(
                'Format provider Nexus tidak valid.',
                502
            );
        }

        $providers = array();

        foreach ($rawProviders as $provider) {
            if (!is_array($provider)) {
                continue;
            }

            $code = strtoupper(trim(
                isset($provider['code'])
                    ? (string) $provider['code']
                    : ''
            ));

            $name = trim(
                isset($provider['name'])
                    ? (string) $provider['name']
                    : ''
            );

            if ($code === '' || $name === '') {
                continue;
            }

            $providers[] = array(
                'api_provider' => 'nexus',
                'provider_code' => $code,
                'provider_name' => $name,
                'provider_type' => null,
                'provider_image' => null,
                'provider_status' => (
                    isset($provider['status']) &&
                    (int) $provider['status'] === 1
                ) ? 1 : 0,
            );
        }

        return array(
            'success' => true,
            'api' => 'nexus',
            'message' => isset($response['msg'])
                ? (string) $response['msg']
                : 'SUCCESS',
            'data' => $providers,
            'total' => count($providers),
            'connection' => $request['connection'],
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Game list
    |--------------------------------------------------------------------------
    */

    public function gameList($providerCode)
    {
        $providerCode = $this->validateProviderCode(
            $providerCode
        );

        $payload = $this->authPayload('game_list');
        $payload['provider_code'] = $providerCode;

        $request = $this->postJson($payload);
        $response = $request['body'];

        $this->assertSuccess(
            $response,
            'Nexus game_list gagal.'
        );

        $rawGames = isset($response['games'])
            ? $response['games']
            : array();

        if (!is_array($rawGames)) {
            throw new GameApiException(
                'Format game Nexus tidak valid.',
                502
            );
        }

        $games = array();

        foreach ($rawGames as $game) {
            if (!is_array($game)) {
                continue;
            }

            $gameCode = trim(
                isset($game['game_code'])
                    ? (string) $game['game_code']
                    : ''
            );

            $gameName = trim(
                isset($game['game_name'])
                    ? (string) $game['game_name']
                    : ''
            );

            if ($gameCode === '' || $gameName === '') {
                continue;
            }

            $games[] = array(
                'api_provider' => 'nexus',
                'game_code' => $gameCode,
                'game_name' => $gameName,
                'game_provider' => $providerCode,
                'game_type' => null,
                'game_image' => $this->normalizeImage(
                    isset($game['banner'])
                        ? $game['banner']
                        : ''
                ),
                'game_status' => (
                    isset($game['status']) &&
                    (int) $game['status'] === 1
                ) ? 1 : 0,
            );
        }

        return array(
            'success' => true,
            'api' => 'nexus',
            'provider_code' => $providerCode,
            'message' => isset($response['msg'])
                ? (string) $response['msg']
                : 'SUCCESS',
            'data' => $games,
            'total' => count($games),
            'connection' => $request['connection'],
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Create user
    |--------------------------------------------------------------------------
    | Belum tersedia karena dokumentasi create-user belum diberikan.
    */

    public function createUser(
        $playerCode,
        array $options = array()
    ) {
        $this->validatePlayerCode($playerCode);

        return $this->unsupported('createUser');
    }

    /*
    |--------------------------------------------------------------------------
    | User balance / money_info
    |--------------------------------------------------------------------------
    */

    public function getBalance($playerCode)
    {
        $playerCode = $this->validatePlayerCode(
            $playerCode
        );

        $payload = $this->authPayload('money_info');
        $payload['user_code'] = $playerCode;

        $request = $this->postJson($payload);
        $response = $request['body'];

        $this->assertSuccess(
            $response,
            'Nexus money_info user gagal.'
        );

        if (
            !isset($response['user']) ||
            !is_array($response['user'])
        ) {
            throw new GameApiException(
                'Data user tidak ditemukan pada respons money_info.',
                502
            );
        }

        $user = $response['user'];
        $agent = (
            isset($response['agent']) &&
            is_array($response['agent'])
        ) ? $response['agent'] : array();

        return array(
            'success' => true,
            'api' => 'nexus',
            'player_code' => isset($user['user_code'])
                ? (string) $user['user_code']
                : $playerCode,
            'balance' => $this->formatMoney(
                isset($user['balance'])
                    ? $user['balance']
                    : 0
            ),
            'agent_balance' => $this->formatMoney(
                isset($agent['balance'])
                    ? $agent['balance']
                    : 0
            ),
            'message' => isset($response['msg'])
                ? (string) $response['msg']
                : 'SUCCESS',
            'connection' => $request['connection'],
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Agent money_info
    |--------------------------------------------------------------------------
    */

    public function getAgentInfo()
    {
        $request = $this->postJson(
            $this->authPayload('money_info')
        );

        $response = $request['body'];

        $this->assertSuccess(
            $response,
            'Nexus money_info agent gagal.'
        );

        if (
            !isset($response['agent']) ||
            !is_array($response['agent'])
        ) {
            throw new GameApiException(
                'Data agent tidak ditemukan pada respons money_info.',
                502
            );
        }

        return array(
            'success' => true,
            'api' => 'nexus',
            'agent_code' => isset(
                $response['agent']['agent_code']
            )
                ? (string) $response['agent']['agent_code']
                : $this->agentCode,
            'balance' => $this->formatMoney(
                isset($response['agent']['balance'])
                    ? $response['agent']['balance']
                    : 0
            ),
            'message' => isset($response['msg'])
                ? (string) $response['msg']
                : 'SUCCESS',
            'connection' => $request['connection'],
        );
    }

    /*
    |--------------------------------------------------------------------------
    | All users money_info
    |--------------------------------------------------------------------------
    */

    public function getAllUsersInfo()
    {
        $payload = $this->authPayload('money_info');
        $payload['all_users'] = true;

        $request = $this->postJson($payload);
        $response = $request['body'];

        $this->assertSuccess(
            $response,
            'Nexus money_info all users gagal.'
        );

        $rawUsers = isset($response['user_list'])
            ? $response['user_list']
            : array();

        if (!is_array($rawUsers)) {
            throw new GameApiException(
                'Format user_list Nexus tidak valid.',
                502
            );
        }

        $users = array();

        foreach ($rawUsers as $user) {
            if (!is_array($user)) {
                continue;
            }

            $userCode = trim(
                isset($user['user_code'])
                    ? (string) $user['user_code']
                    : ''
            );

            if ($userCode === '') {
                continue;
            }

            $users[] = array(
                'player_code' => $userCode,
                'balance' => $this->formatMoney(
                    isset($user['balance'])
                        ? $user['balance']
                        : 0
                ),
            );
        }

        $agent = (
            isset($response['agent']) &&
            is_array($response['agent'])
        ) ? $response['agent'] : array();

        return array(
            'success' => true,
            'api' => 'nexus',
            'agent_balance' => $this->formatMoney(
                isset($agent['balance'])
                    ? $agent['balance']
                    : 0
            ),
            'data' => $users,
            'total' => count($users),
            'message' => isset($response['msg'])
                ? (string) $response['msg']
                : 'SUCCESS',
            'connection' => $request['connection'],
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Deposit user balance
    |--------------------------------------------------------------------------
    */

    public function deposit(
        $playerCode,
        $amount,
        $transactionId,
        array $options = array()
    ) {
        return $this->transferBalance(
            'user_deposit',
            $playerCode,
            $amount,
            $transactionId
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Withdraw user balance
    |--------------------------------------------------------------------------
    */

    public function withdraw(
        $playerCode,
        $amount,
        $transactionId,
        array $options = array()
    ) {
        return $this->transferBalance(
            'user_withdraw',
            $playerCode,
            $amount,
            $transactionId
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Game launch
    |--------------------------------------------------------------------------
    |
    | Wajib pada $options:
    | - provider_code
    |
    | Opsional:
    | - lang
    | - rtp
    | - lobby_url
    |
    | game_code boleh kosong untuk live lobby.
    */

    public function launchGame(
        $playerCode,
        $gameCode,
        array $options = array()
    ) {
        $playerCode = $this->validatePlayerCode(
            $playerCode
        );

        $providerCode = isset($options['provider_code'])
            ? $this->validateProviderCode(
                $options['provider_code']
            )
            : '';

        if ($providerCode === '') {
            throw new GameApiException(
                'provider_code wajib untuk launch game Nexus.',
                422
            );
        }

        $gameCode = trim((string) $gameCode);

        if ($gameCode !== '') {
            $gameCode = $this->validateGameCode(
                $gameCode
            );
        }

        $lang = isset($options['lang'])
            ? strtolower(trim((string) $options['lang']))
            : 'en';

        if (
            preg_match('/^[a-z]{2}(?:-[a-z]{2})?$/', $lang) !== 1
        ) {
            throw new GameApiException(
                'Kode bahasa launch game tidak valid.',
                422
            );
        }

        $payload = $this->authPayload('game_launch');
        $payload['user_code'] = $playerCode;
        $payload['provider_code'] = $providerCode;

        if ($gameCode !== '') {
            $payload['game_code'] = $gameCode;
        }

        $payload['lang'] = $lang;

        if (
            isset($options['rtp']) &&
            $options['rtp'] !== ''
        ) {
            if (
                !is_numeric($options['rtp']) ||
                (float) $options['rtp'] <= 0 ||
                (float) $options['rtp'] > 100
            ) {
                throw new GameApiException(
                    'RTP harus berupa angka lebih dari 0 sampai 100.',
                    422
                );
            }

            $payload['rtp'] = (float) $options['rtp'];
        }

        $lobbyUrl = isset($options['lobby_url'])
            ? trim((string) $options['lobby_url'])
            : '';

        if ($lobbyUrl === '') {
            throw new GameApiException(
                'lobby_url wajib untuk launch game Nexus.',
                422
            );
        }

        $payload['lobby_url'] = $this->validateWebUrl(
            $lobbyUrl,
            'Lobby URL'
        );

        $request = $this->postJson($payload);
        $response = $request['body'];

        $this->assertSuccess(
            $response,
            'Nexus game_launch gagal.'
        );

        $launchUrl = isset($response['launch_url'])
            ? trim((string) $response['launch_url'])
            : '';

        if ($launchUrl === '') {
            throw new GameApiException(
                'Nexus tidak mengirim launch_url.',
                502
            );
        }

        $launchUrl = $this->validateWebUrl(
            $launchUrl,
            'Launch URL'
        );

        return array(
            'success' => true,
            'api' => 'nexus',
            'player_code' => $playerCode,
            'provider_code' => $providerCode,
            'game_code' => $gameCode,
            'launch_url' => $launchUrl,
            'provider_reference' => null,
            'message' => isset($response['msg'])
                ? (string) $response['msg']
                : 'SUCCESS',
            'connection' => $request['connection'],
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Game transaction log / get_game_log
    |--------------------------------------------------------------------------
    |
    | Filter wajib:
    | - start: Y-m-d H:i:s
    | - end: Y-m-d H:i:s
    |
    | Filter opsional:
    | - game_type: slot|live (default slot)
    | - page: default 0
    | - perPage: default 1000, maksimum 1000000
    */

    public function getProviderHistory(
        $playerCode,
        array $filters = array()
    ) {
        $playerCode = $this->validatePlayerCode(
            $playerCode
        );

        $gameType = isset($filters['game_type'])
            ? strtolower(trim((string) $filters['game_type']))
            : 'slot';

        if (!in_array($gameType, array('slot', 'live'), true)) {
            throw new GameApiException(
                'game_type history harus slot atau live.',
                422
            );
        }

        $start = isset($filters['start'])
            ? trim((string) $filters['start'])
            : '';

        $end = isset($filters['end'])
            ? trim((string) $filters['end'])
            : '';

        $this->validateDateTime($start, 'start');
        $this->validateDateTime($end, 'end');

        if (strtotime($start) > strtotime($end)) {
            throw new GameApiException(
                'Waktu start tidak boleh melewati end.',
                422
            );
        }

        $page = isset($filters['page'])
            ? (int) $filters['page']
            : 0;

        $perPage = isset($filters['perPage'])
            ? (int) $filters['perPage']
            : (
                isset($filters['per_page'])
                    ? (int) $filters['per_page']
                    : 1000
            );

        if ($page < 0) {
            throw new GameApiException(
                'Page history tidak boleh negatif.',
                422
            );
        }

        if ($perPage < 1 || $perPage > 1000000) {
            throw new GameApiException(
                'perPage history harus 1 sampai 1000000.',
                422
            );
        }

        $payload = $this->authPayload('get_game_log');
        $payload['user_code'] = $playerCode;
        $payload['game_type'] = $gameType;
        $payload['start'] = $start;
        $payload['end'] = $end;
        $payload['page'] = $page;
        $payload['perPage'] = $perPage;

        $request = $this->postJson($payload);
        $response = $request['body'];

        $this->assertSuccess(
            $response,
            'Nexus get_game_log gagal.'
        );

        $records = isset($response[$gameType])
            ? $response[$gameType]
            : array();

        if (!is_array($records)) {
            throw new GameApiException(
                'Format game log Nexus tidak valid.',
                502
            );
        }

        return array(
            'success' => true,
            'api' => 'nexus',
            'player_code' => $playerCode,
            'game_type' => $gameType,
            'total_count' => isset($response['total_count'])
                ? (int) $response['total_count']
                : count($records),
            'page' => isset($response['page'])
                ? (int) $response['page']
                : $page,
            'per_page' => isset($response['perPage'])
                ? (int) $response['perPage']
                : $perPage,
            'data' => $records,
            'message' => isset($response['msg'])
                ? (string) $response['msg']
                : 'SUCCESS',
            'connection' => $request['connection'],
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Provider in-game history URL
    |--------------------------------------------------------------------------
    | Saat ini dokumentasi menyatakan hanya PRAGMATIC slot.
    */

    public function getGameHistoryLink(
        $playerCode,
        $providerCode,
        $gameCode
    ) {
        $playerCode = $this->validatePlayerCode(
            $playerCode
        );

        $providerCode = $this->validateProviderCode(
            $providerCode
        );

        $gameCode = $this->validateGameCode(
            $gameCode
        );

        if ($providerCode !== 'PRAGMATIC') {
            throw new GameApiException(
                'In-game history Nexus hanya tersedia untuk PRAGMATIC.',
                422
            );
        }

        $payload = $this->authPayload(
            'get_game_history'
        );

        $payload['user_code'] = $playerCode;
        $payload['provider_code'] = $providerCode;
        $payload['game_code'] = $gameCode;

        $request = $this->postJson($payload);
        $response = $request['body'];

        $this->assertSuccess(
            $response,
            'Nexus get_game_history gagal.'
        );

        $historyUrl = isset($response['history_url'])
            ? trim((string) $response['history_url'])
            : '';

        if ($historyUrl === '') {
            throw new GameApiException(
                'Nexus tidak mengirim history_url.',
                502
            );
        }

        return array(
            'success' => true,
            'api' => 'nexus',
            'player_code' => $playerCode,
            'provider_code' => $providerCode,
            'game_code' => $gameCode,
            'history_url' => $this->validateWebUrl(
                $historyUrl,
                'History URL'
            ),
            'message' => isset($response['msg'])
                ? (string) $response['msg']
                : 'SUCCESS',
            'connection' => $request['connection'],
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Reset satu user: tarik seluruh balance user ke agent
    |--------------------------------------------------------------------------
    */

    public function resetUserBalance($playerCode)
    {
        $playerCode = $this->validatePlayerCode(
            $playerCode
        );

        $payload = $this->authPayload(
            'user_withdraw_reset'
        );

        $payload['user_code'] = $playerCode;

        $request = $this->postJson($payload);
        $response = $request['body'];

        $this->assertSuccess(
            $response,
            'Nexus user_withdraw_reset gagal.'
        );

        $user = (
            isset($response['user']) &&
            is_array($response['user'])
        ) ? $response['user'] : array();

        $agent = (
            isset($response['agent']) &&
            is_array($response['agent'])
        ) ? $response['agent'] : array();

        return array(
            'success' => true,
            'api' => 'nexus',
            'player_code' => isset($user['user_code'])
                ? (string) $user['user_code']
                : $playerCode,
            'withdraw_amount' => $this->formatMoney(
                isset($user['withdraw_amount'])
                    ? $user['withdraw_amount']
                    : 0
            ),
            'balance' => $this->formatMoney(
                isset($user['balance'])
                    ? $user['balance']
                    : 0
            ),
            'agent_balance' => $this->formatMoney(
                isset($agent['balance'])
                    ? $agent['balance']
                    : 0
            ),
            'message' => isset($response['msg'])
                ? (string) $response['msg']
                : 'SUCCESS',
            'connection' => $request['connection'],
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Reset seluruh user
    |--------------------------------------------------------------------------
    | Wajib diproteksi admin dan konfirmasi tambahan pada halaman pemanggil.
    */

    public function resetAllUsersBalance()
    {
        $payload = $this->authPayload(
            'user_withdraw_reset'
        );

        $payload['all_users'] = true;

        $request = $this->postJson($payload);
        $response = $request['body'];

        $this->assertSuccess(
            $response,
            'Nexus reset seluruh user gagal.'
        );

        $rawUsers = isset($response['user_list'])
            ? $response['user_list']
            : array();

        if (!is_array($rawUsers)) {
            throw new GameApiException(
                'Format user_list reset Nexus tidak valid.',
                502
            );
        }

        $users = array();

        foreach ($rawUsers as $user) {
            if (!is_array($user)) {
                continue;
            }

            $users[] = array(
                'player_code' => isset($user['user_code'])
                    ? (string) $user['user_code']
                    : '',
                'withdraw_amount' => $this->formatMoney(
                    isset($user['withdraw_amount'])
                        ? $user['withdraw_amount']
                        : 0
                ),
                'balance' => $this->formatMoney(
                    isset($user['balance'])
                        ? $user['balance']
                        : 0
                ),
            );
        }

        $agent = (
            isset($response['agent']) &&
            is_array($response['agent'])
        ) ? $response['agent'] : array();

        return array(
            'success' => true,
            'api' => 'nexus',
            'agent_balance' => $this->formatMoney(
                isset($agent['balance'])
                    ? $agent['balance']
                    : 0
            ),
            'data' => $users,
            'total' => count($users),
            'message' => isset($response['msg'])
                ? (string) $response['msg']
                : 'SUCCESS',
            'connection' => $request['connection'],
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Internal helper
    |--------------------------------------------------------------------------
    */

    private function transferBalance(
        $method,
        $playerCode,
        $amount,
        $transactionId
    ) {
        $playerCode = $this->validatePlayerCode(
            $playerCode
        );

        $amount = $this->normalizeAmount($amount);

        $transactionId = $this->validateTransactionId(
            $transactionId
        );

        $payload = $this->authPayload($method);
        $payload['user_code'] = $playerCode;

        /*
         * API meminta number/double, bukan string.
         */
        $payload['amount'] = (float) $amount;

        /*
         * Idempotency: selalu kirim transaction ID server.
         */
        $payload['agent_sign'] = $transactionId;

        $request = $this->postJson($payload);
        $response = $request['body'];

        $this->assertSuccess(
            $response,
            'Nexus ' . $method . ' gagal.'
        );

        return array(
            'success' => true,
            'api' => 'nexus',
            'operation' => $method === 'user_deposit'
                ? 'deposit'
                : 'withdraw',
            'player_code' => $playerCode,
            'transaction_id' => $transactionId,
            'amount' => $amount,
            'agent_balance' => $this->formatMoney(
                isset($response['agent_balance'])
                    ? $response['agent_balance']
                    : 0
            ),
            'user_balance' => $this->formatMoney(
                isset($response['user_balance'])
                    ? $response['user_balance']
                    : 0
            ),
            'balance' => $this->formatMoney(
                isset($response['user_balance'])
                    ? $response['user_balance']
                    : 0
            ),
            'provider_reference' => $transactionId,
            'message' => isset($response['msg'])
                ? (string) $response['msg']
                : 'SUCCESS',
            'connection' => $request['connection'],
        );
    }

    private function authPayload($method)
    {
        return array(
            'method' => (string) $method,
            'agent_code' => $this->agentCode,
            'agent_token' => $this->agentToken,
        );
    }

    private function assertSuccess(
        array $response,
        $fallbackMessage
    ) {
        $status = isset($response['status'])
            ? (int) $response['status']
            : 0;

        if ($status === 1) {
            return;
        }

        $message = isset($response['msg'])
            ? trim((string) $response['msg'])
            : '';

        $detail = isset($response['detail'])
            ? trim((string) $response['detail'])
            : '';

        if ($message === '') {
            $message = (string) $fallbackMessage;
        }

        if ($detail !== '') {
            $message .= ' — ' . $detail;
        }

        throw new GameApiException(
            $message,
            502,
            array(
                'api' => 'nexus',
                'provider_status' => $status,
            )
        );
    }

    private function formatMoney($value)
    {
        if (!is_numeric($value)) {
            return '0.00';
        }

        return number_format(
            (float) $value,
            2,
            '.',
            ''
        );
    }

    private function validateDateTime(
        $value,
        $fieldName
    ) {
        if ($value === '') {
            throw new GameApiException(
                $fieldName . ' wajib diisi.',
                422
            );
        }

        $date = DateTime::createFromFormat(
            'Y-m-d H:i:s',
            $value
        );

        $errors = DateTime::getLastErrors();

        $hasErrors = is_array($errors) && (
            $errors['warning_count'] > 0 ||
            $errors['error_count'] > 0
        );

        if (
            $date === false ||
            $hasErrors ||
            $date->format('Y-m-d H:i:s') !== $value
        ) {
            throw new GameApiException(
                $fieldName .
                ' harus berformat Y-m-d H:i:s.',
                422
            );
        }
    }

    private function validateWebUrl(
        $url,
        $label
    ) {
        $url = trim((string) $url);

        if (
            $url === '' ||
            filter_var(
                $url,
                FILTER_VALIDATE_URL
            ) === false
        ) {
            throw new GameApiException(
                $label . ' tidak valid.',
                502
            );
        }

        $scheme = strtolower(
            (string) parse_url(
                $url,
                PHP_URL_SCHEME
            )
        );

        /*
         * Dokumentasi Nexus menampilkan launch_url HTTP,
         * jadi HTTP dan HTTPS diterima.
         */
        if (!in_array($scheme, array('http', 'https'), true)) {
            throw new GameApiException(
                $label . ' memiliki protokol tidak aman.',
                502
            );
        }

        return $url;
    }
}