<?php

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

abstract class AbstractGameApiAdapter implements GameApiAdapterInterface
{
    protected $config;
    protected $baseUrl;
    protected $connectTimeout;
    protected $timeout;
    protected $forceIpv4;

    public function __construct(array $config)
    {
        $this->config = $config;

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

        $this->connectTimeout = isset($config['connect_timeout'])
            ? max(1, (int) $config['connect_timeout'])
            : 10;

        $this->timeout = isset($config['timeout'])
            ? max(1, (int) $config['timeout'])
            : 60;

        $this->forceIpv4 = !empty($config['force_ipv4']);

        $this->validateBaseConfiguration();
    }

    protected function validateBaseConfiguration()
    {
        if (
            $this->baseUrl === '' ||
            filter_var($this->baseUrl, FILTER_VALIDATE_URL) === false
        ) {
            throw new GameApiException(
                'URL API ' . strtoupper($this->getApiName()) . ' tidak valid.',
                500
            );
        }

        $scheme = strtolower(
            (string) parse_url($this->baseUrl, PHP_URL_SCHEME)
        );

        if ($scheme !== 'https') {
            throw new GameApiException(
                'Endpoint API ' . strtoupper($this->getApiName()) .
                ' wajib menggunakan HTTPS.',
                500
            );
        }
    }

    protected function postJson(array $payload, array $acceptedHttpCodes = array())
    {
        $jsonPayload = json_encode(
            $payload,
            JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
        );

        if ($jsonPayload === false) {
            throw new GameApiException(
                'Gagal membuat payload JSON.',
                500,
                array('json_error' => json_last_error_msg())
            );
        }

        $ch = curl_init();

        if ($ch === false) {
            throw new GameApiException(
                'Gagal menginisialisasi cURL.',
                500
            );
        }

        $options = array(
            CURLOPT_URL            => $this->baseUrl,
            CURLOPT_POST           => true,
            CURLOPT_POSTFIELDS     => $jsonPayload,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CONNECTTIMEOUT => $this->connectTimeout,
            CURLOPT_TIMEOUT        => $this->timeout,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_1_1,
            CURLOPT_FOLLOWLOCATION => false,
            CURLOPT_MAXREDIRS      => 0,
            CURLOPT_ENCODING       => '',
            CURLOPT_USERAGENT      => 'Secure-Game-API/1.0',
            CURLOPT_HTTPHEADER     => array(
                'Accept: application/json',
                'Content-Type: application/json; charset=utf-8',
                'Content-Length: ' . strlen($jsonPayload),
                'Connection: close',
            ),
        );

        if ($this->forceIpv4) {
            $options[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
        }

        curl_setopt_array($ch, $options);

        $responseBody = curl_exec($ch);
        $curlError = curl_error($ch);
        $curlErrno = curl_errno($ch);
        $curlInfo = curl_getinfo($ch);

        curl_close($ch);

        $httpCode = isset($curlInfo['http_code'])
            ? (int) $curlInfo['http_code']
            : 0;

        $connection = array(
            'http_code' => $httpCode,
            'local_ip' => isset($curlInfo['local_ip'])
                ? (string) $curlInfo['local_ip']
                : '',
            'remote_ip' => isset($curlInfo['primary_ip'])
                ? (string) $curlInfo['primary_ip']
                : '',
            'content_type' => isset($curlInfo['content_type'])
                ? (string) $curlInfo['content_type']
                : '',
            'total_time' => isset($curlInfo['total_time'])
                ? (float) $curlInfo['total_time']
                : 0.0,
        );

        if ($responseBody === false) {
            $this->writeSafeLog('GAME_API_CURL_FAILED', array(
                'curl_errno' => $curlErrno,
                'curl_error' => $curlError,
                'connection' => $connection,
            ));

            throw new GameApiException(
                'Tidak dapat terhubung ke API ' .
                strtoupper($this->getApiName()) . '.',
                502,
                array(
                    'curl_errno' => $curlErrno,
                    'curl_error' => $curlError,
                )
            );
        }

        $decoded = json_decode((string) $responseBody, true);

        if (!is_array($decoded)) {
            $preview = $this->safeText(
                strip_tags((string) $responseBody),
                500
            );

            $this->writeSafeLog('GAME_API_INVALID_JSON', array(
                'connection' => $connection,
                'body_preview' => $preview,
            ));

            throw new GameApiException(
                'Respons API bukan JSON yang valid.',
                502,
                array(
                    'http_code' => $httpCode,
                    'preview' => $preview,
                )
            );
        }

        $isSuccessfulHttp = (
            $httpCode >= 200 &&
            $httpCode < 300
        );

        $isAcceptedHttp = in_array(
            $httpCode,
            $acceptedHttpCodes,
            true
        );

        if (!$isSuccessfulHttp && !$isAcceptedHttp) {
            $message = '';

            if (isset($decoded['message'])) {
                $message = (string) $decoded['message'];
            } elseif (isset($decoded['msg'])) {
                $message = (string) $decoded['msg'];
            }

            $message = $this->safeText($message, 300);

            $this->writeSafeLog('GAME_API_HTTP_ERROR', array(
                'connection' => $connection,
                'api_message' => $message,
            ));

            throw new GameApiException(
                $message !== ''
                    ? $message
                    : 'API mengembalikan HTTP ' . $httpCode . '.',
                $httpCode,
                array(
                    'http_code' => $httpCode,
                    'connection' => $connection,
                )
            );
        }

        return array(
            'body' => $decoded,
            'connection' => $connection,
        );
    }

    protected function validatePlayerCode($playerCode)
    {
        $playerCode = trim((string) $playerCode);

        if (
            $playerCode === '' ||
            preg_match('/^[A-Za-z0-9_.-]{3,64}$/', $playerCode) !== 1
        ) {
            throw new GameApiException(
                'Player code tidak valid.',
                422
            );
        }

        return $playerCode;
    }

    protected function validateProviderCode($providerCode)
    {
        $providerCode = strtoupper(trim((string) $providerCode));

        if (
            $providerCode === '' ||
            preg_match('/^[A-Z0-9_-]{1,50}$/', $providerCode) !== 1
        ) {
            throw new GameApiException(
                'Provider code tidak valid.',
                422
            );
        }

        return $providerCode;
    }

    protected function validateGameCode($gameCode)
    {
        $gameCode = trim((string) $gameCode);

        if (
            $gameCode === '' ||
            strlen($gameCode) > 150 ||
            preg_match('/^[A-Za-z0-9_.:-]+$/', $gameCode) !== 1
        ) {
            throw new GameApiException(
                'Game code tidak valid.',
                422
            );
        }

        return $gameCode;
    }

    protected function validateTransactionId($transactionId)
    {
        $transactionId = trim((string) $transactionId);

        if (
            $transactionId === '' ||
            strlen($transactionId) > 100 ||
            preg_match('/^[A-Za-z0-9_.:-]+$/', $transactionId) !== 1
        ) {
            throw new GameApiException(
                'Transaction ID tidak valid.',
                422
            );
        }

        return $transactionId;
    }

    protected function normalizeAmount($amount)
    {
        if (!is_numeric($amount)) {
            throw new GameApiException(
                'Nominal transaksi tidak valid.',
                422
            );
        }

        $normalized = number_format((float) $amount, 2, '.', '');

        if ((float) $normalized <= 0) {
            throw new GameApiException(
                'Nominal transaksi harus lebih dari nol.',
                422
            );
        }

        return $normalized;
    }

    protected function normalizeImage($url)
    {
        $url = trim((string) $url);

        if (
            $url === '' ||
            filter_var($url, FILTER_VALIDATE_URL) === false
        ) {
            return null;
        }

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

        return $scheme === 'https' ? $url : null;
    }

    protected function safeText($value, $maximum)
    {
        $value = trim(
            preg_replace('/\s+/', ' ', (string) $value)
        );

        if (function_exists('mb_strlen')) {
            if (mb_strlen($value, 'UTF-8') > $maximum) {
                return mb_substr($value, 0, $maximum, 'UTF-8');
            }

            return $value;
        }

        return strlen($value) > $maximum
            ? substr($value, 0, $maximum)
            : $value;
    }

    protected function unsupported($methodName)
    {
        throw new GameApiException(
            strtoupper($this->getApiName()) . ' ' . $methodName .
            ' belum dikonfigurasi. Masukkan dokumentasi endpoint dan respons API.',
            501
        );
    }

    protected function writeSafeLog($event, array $context)
    {
        $context['event'] = (string) $event;
        $context['api'] = $this->getApiName();

        /*
         * Jangan masukkan signature, token, password,
         * session ID, atau payload autentikasi ke log.
         */
        error_log(json_encode(
            $context,
            JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
        ));
    }
}
