<?php

namespace Tigusigalpa\BingX\Http;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\RequestOptions;
use Tigusigalpa\BingX\Exceptions\ApiException;
use Tigusigalpa\BingX\Exceptions\AuthenticationException;
use Tigusigalpa\BingX\Exceptions\BingxException;
use Tigusigalpa\BingX\Exceptions\RateLimitException;
use Tigusigalpa\BingX\Exceptions\InsufficientBalanceException;

class BaseHttpClient
{
    protected string $apiKey;
    protected string $apiSecret;
    protected string $baseUri;
    protected ?string $sourceKey;
    protected string $signatureEncoding;
    protected Client $http;

    public function __construct(
        string $apiKey, 
        string $apiSecret, 
        string $baseUri = 'https://open-api.bingx.com', 
        ?string $sourceKey = null, 
        string $signatureEncoding = 'hex', 
        ?Client $http = null
    ) {
        $this->apiKey = $apiKey;
        $this->apiSecret = $apiSecret;
        $this->baseUri = $baseUri;
        $this->sourceKey = $sourceKey;
        $this->signatureEncoding = $signatureEncoding;
        $this->http = $http ?: new Client([
            'base_uri' => $this->baseUri,
            'timeout' => 30,
            'connect_timeout' => 10,
        ]);
    }

    protected function timestamp(): string
    {
        return (string)floor(microtime(true) * 1000);
    }

    protected function buildQuery(array $params): string
    {
        if (!$params) return '';
        ksort($params);
        return http_build_query($params);
    }

    protected function signString(string $string): string
    {
        return hash_hmac('sha256', $string, $this->apiSecret, false);
    }

    protected function headers(bool $authenticated = true): array
    {
        $headers = ['Content-Type' => 'application/x-www-form-urlencoded'];
        
        if ($authenticated) {
            $headers['X-BX-APIKEY'] = $this->apiKey;
        }
        
        if ($this->sourceKey) {
            $headers['X-SOURCE-KEY'] = $this->sourceKey;
        }
        
        return $headers;
    }

    protected function handleApiError(array $response): void
    {
        if (!isset($response['code'])) return;
        
        $code = $response['code'];
        $message = $response['msg'] ?? 'Unknown API error';
        
        if ($code === 0 || $code === '0') {
            return;
        }
        
        switch ($code) {
            case 100001:
            case '100001':
            case 100002:
            case '100002':
            case 100003:
            case '100003':
            case 100004:
            case '100004':
                throw new AuthenticationException($message, $response);
            case 100005:
            case '100005':
                throw new RateLimitException($message, $response);
            case 200001:
            case '200001':
                throw new InsufficientBalanceException($message, $response);
            default:
                throw new ApiException($message, $code, $response);
        }
    }

    /**
     * Determine if endpoint requires authentication
     */
    protected function requiresAuth(string $path): bool
    {
        // Public endpoints that don't need authentication
        $publicEndpoints = [
            '/openApi/swap/v2/server/time',
            '/openApi/swap/v2/market/latestPrice',
            '/openApi/swap/v2/quote/price',
            '/openApi/swap/v2/quote/ticker',
            '/openApi/swap/v2/quote/depth',
            '/openApi/swap/v2/quote/trades',
            '/openApi/swap/v2/quote/klines',
            '/openApi/swap/v2/market/depth',
            '/openApi/swap/v2/market/klines',
            '/openApi/swap/v2/market/trades',

        ];
        
        if (in_array($path, $publicEndpoints)) {
            file_put_contents(__DIR__ . '/debug.log', "Checking if path requires auth: $path\n", FILE_APPEND);
            return false;
        }
        
        return true;
    }

    public function request(string $method, string $path, array $params = [], array $options = []): array
    {
        $method = strtoupper($method);
        $requiresAuth = $this->requiresAuth($path);
        
        // Only add timestamp and signature for authenticated endpoints
        if ($requiresAuth) {
            $params['timestamp'] = $params['timestamp'] ?? $this->timestamp();
            $query = $this->buildQuery($params);
            $signature = $this->signString($query);
        } else {
            $query = $this->buildQuery($params);
            $signature = null;
        }
        
        $headers = $this->headers($requiresAuth);
        $opts = [RequestOptions::HEADERS => $headers];
        
        if ($method === 'GET') {
            // GET: params in query string
            if ($signature) {
                $opts[RequestOptions::QUERY] = $params + ['signature' => $signature];
            } else {
                $opts[RequestOptions::QUERY] = $params;
            }
        } else {
            // POST/PUT/DELETE: send as raw body
            if ($signature) {
                $body = $query . '&signature=' . $signature;
            } else {
                $body = $query;
            }
            $opts[RequestOptions::BODY] = $body;
        }
        
        if ($options) $opts = array_replace_recursive($opts, $options);

        try {
            $response = $this->http->request($method, $path, $opts);
            $body = (string)$response->getBody();
            $data = json_decode($body, true);
            
            if (!is_array($data)) {
                throw new BingxException('Invalid JSON response from API', 0, null, ['raw' => $body]);
            }
            
            $this->handleApiError($data);
            
            return $data;
            
        } catch (RequestException $e) {
            $responseBody = $e->hasResponse() ? (string)$e->getResponse()->getBody() : '';
            $responseData = json_decode($responseBody, true) ?: [];
            
            throw new BingxException(
                "HTTP request failed: " . $e->getMessage(),
                $e->getCode(),
                $e,
                $responseData
            );
        }
    }

    public function getEndpoint(): string
    {
        return $this->baseUri;
    }

    public function getApiKey(): string
    {
        return $this->apiKey;
    }
}
