<?php

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

class ProviderTransactionService
{
    private $pdo;
    private $userRepository;
    private $transactionRepository;

    public function __construct(
        PDO $pdo,
        UserRepository $userRepository,
        TransactionRepository $transactionRepository
    ) {
        $this->pdo = $pdo;
        $this->userRepository = $userRepository;
        $this->transactionRepository =
            $transactionRepository;
    }

    public function deposit(
        $userId,
        $apiProvider,
        $amount,
        array $options = array()
    ) {
        return $this->transfer(
            $userId,
            $apiProvider,
            $amount,
            'deposit',
            $options
        );
    }

    public function withdraw(
        $userId,
        $apiProvider,
        $amount,
        array $options = array()
    ) {
        return $this->transfer(
            $userId,
            $apiProvider,
            $amount,
            'withdraw',
            $options
        );
    }

    private function transfer(
        $userId,
        $apiProvider,
        $amount,
        $type,
        array $options
    ) {
        $apiProvider = strtolower(
            trim((string) $apiProvider)
        );

        if (
            !in_array(
                $apiProvider,
                array('sgx', 'nexus'),
                true
            )
        ) {
            throw new InvalidArgumentException(
                'API provider tidak valid.'
            );
        }

        if (
            !is_numeric($amount) ||
            (float) $amount <= 0
        ) {
            throw new InvalidArgumentException(
                'Nominal transaksi tidak valid.'
            );
        }

        $playerCode = $this->userRepository
            ->getPlayerCode(
                $userId,
                $apiProvider
            );

        if (
            $playerCode === null ||
            $playerCode === ''
        ) {
            throw new GameApiException(
                'Akun provider user belum tersedia.',
                404
            );
        }

        $transactionId = $this->createTransactionId(
            $apiProvider,
            $type,
            $userId
        );

        $this->transactionRepository->create(
            array(
                'transaction_id' => $transactionId,
                'user_id' => $userId,
                'api_provider' => $apiProvider,
                'transaction_type' => $type,
                'amount' => $amount,
                'status' => 'processing',
                'request_payload' => null,
                'response_payload' => null,
            )
        );

        try {
            $adapter = GameApi::adapter(
                $apiProvider
            );

            if ($type === 'deposit') {
                $result = $adapter->deposit(
                    $playerCode,
                    $amount,
                    $transactionId,
                    $options
                );
            } else {
                $result = $adapter->withdraw(
                    $playerCode,
                    $amount,
                    $transactionId,
                    $options
                );
            }

            $reference = isset(
                $result['provider_reference']
            )
                ? $result['provider_reference']
                : null;

            $this->transactionRepository->updateStatus(
                $transactionId,
                'success',
                $reference,
                json_encode(
                    $result,
                    JSON_UNESCAPED_UNICODE |
                    JSON_UNESCAPED_SLASHES
                )
            );

            return $result;
        } catch (Throwable $e) {
            $status = $this->isAmbiguousFailure($e)
                ? 'pending_reconcile'
                : 'failed';

            $this->transactionRepository->updateStatus(
                $transactionId,
                $status,
                null,
                json_encode(
                    array(
                        'message' => $e->getMessage(),
                        'code' => $e->getCode(),
                        'status' => $status,
                    ),
                    JSON_UNESCAPED_UNICODE |
                    JSON_UNESCAPED_SLASHES
                )
            );

            throw $e;
        }
    }

    /*
    |--------------------------------------------------------------------------
    | Ambiguous provider result
    |--------------------------------------------------------------------------
    | Timeout, connection loss, invalid JSON, atau HTTP 5xx dapat terjadi
    | setelah provider memproses transaksi. Jangan langsung retry atau menandai
    | failed karena dapat menyebabkan saldo ganda.
    */

    private function isAmbiguousFailure(Throwable $e)
    {
        if (!($e instanceof GameApiException)) {
            return false;
        }

        $details = $e->getDetails();

        if (isset($details['provider_status'])) {
            /*
             * Provider mengembalikan business response yang jelas:
             * insufficient funds, invalid user, maintenance, rate limit, dll.
             */
            return false;
        }

        if (
            isset($details['curl_errno']) &&
            (int) $details['curl_errno'] !== 0
        ) {
            return true;
        }

        $httpCode = 0;

        if (isset($details['http_code'])) {
            $httpCode = (int) $details['http_code'];
        } elseif (
            isset($details['connection']['http_code'])
        ) {
            $httpCode = (int)
                $details['connection']['http_code'];
        }

        if ($httpCode >= 500 || $httpCode === 0) {
            return true;
        }

        /*
         * Respons bukan JSON tetapi HTTP 2xx juga ambigu.
         */
        if (
            isset($details['preview']) &&
            $httpCode >= 200 &&
            $httpCode < 300
        ) {
            return true;
        }

        return false;
    }

    private function createTransactionId(
        $apiProvider,
        $type,
        $userId
    ) {
        return strtoupper($apiProvider) . '-' .
            strtoupper($type) . '-' .
            (int) $userId . '-' .
            gmdate('YmdHis') . '-' .
            bin2hex(random_bytes(6));
    }
}
