<?php

namespace PortalPeople\virtualstock\components;

use yii\base\BaseObject;
use yii\httpclient\Client;
use yii\httpclient\Exception as HttpClientException;
use PortalPeople\virtualstock\exceptions\ApiException;
use PortalPeople\virtualstock\exceptions\AuthenticationException;
use PortalPeople\virtualstock\exceptions\RateLimitException;
use PortalPeople\virtualstock\components\RateLimiter;
use PortalPeople\virtualstock\components\ApiLogger;

class VirtualStockClient extends BaseObject
{
    public string $apiEndpoint = '';
    public string $apiKey = '';
    public string $apiSecret = '';
    public int $timeout = 30;
    public array $headers = [];

    /** Optional rate limiter — set to enforce 60 req/min before sending requests. */
    public ?RateLimiter $rateLimiter = null;

    /** Optional logger — set to record all requests, responses, and errors. */
    public ?ApiLogger $logger = null;

    private Client $httpClient;

    public function init(): void
    {
        parent::init();

        $baseUri = rtrim($this->apiEndpoint, '/') . '/';
        $this->httpClient = new Client([
            'baseUrl' => $baseUri,
            'requestConfig' => [
                'format'  => Client::FORMAT_JSON,
                'options' => [
                    CURLOPT_TIMEOUT        => $this->timeout,
                    CURLOPT_CONNECTTIMEOUT => min($this->timeout, 10),
                ],
            ],
            'responseConfig' => [
                'format' => Client::FORMAT_JSON,
            ],
        ]);
    }

    public function getOrdersUpdatedSince(?string $cursor = null, int $limit = 100): array
    {
        $query = [
            'limit' => $limit,
        ];

        if ($cursor !== null && $cursor !== '') {
            $query['updatedSince'] = $cursor;
        }

        $response = $this->request('GET', 'orders', $query);

        $orders = [];
        if (isset($response['orders']) && is_array($response['orders'])) {
            $orders = $response['orders'];
        } elseif (isset($response['data']) && is_array($response['data'])) {
            $orders = $response['data'];
        } elseif (array_is_list($response)) {
            $orders = $response;
        }

        $nextCursor = null;
        if (isset($response['nextCursor']) && is_string($response['nextCursor']) && $response['nextCursor'] !== '') {
            $nextCursor = $response['nextCursor'];
        }
        if ($nextCursor === null && isset($response['cursor']) && is_string($response['cursor']) && $response['cursor'] !== '') {
            $nextCursor = $response['cursor'];
        }
        if ($nextCursor === null && count($orders) > 0) {
            $last = end($orders);
            if (is_array($last)) {
                if (isset($last['updatedAt']) && is_string($last['updatedAt']) && $last['updatedAt'] !== '') {
                    $nextCursor = $last['updatedAt'];
                } elseif (isset($last['updated_at']) && is_string($last['updated_at']) && $last['updated_at'] !== '') {
                    $nextCursor = $last['updated_at'];
                }
            }
        }

        return [
            'orders' => $orders,
            'nextCursor' => $nextCursor,
            'raw' => $response,
        ];
    }

    public function getOrder(string $orderNo): array
    {
        return $this->request('GET', 'order/' . rawurlencode($orderNo));
    }

    /**
     * @param array<string, mixed> $filters
     */
    public function listProducts(array $filters = []): array
    {
        return $this->request('GET', 'products/', $filters);
    }

    public function getProduct(string $supplierProductNo): array
    {
        return $this->request('GET', 'product/' . rawurlencode($supplierProductNo));
    }

    public function getProductStock(string $supplierProductNo): array
    {
        return $this->request('GET', 'product/' . rawurlencode($supplierProductNo) . '/stock');
    }

    public function request(string $method, string $uri, array $query = []): array
    {
        if ($this->rateLimiter !== null) {
            $this->rateLimiter->check();
        }

        if ($this->logger !== null) {
            $this->logger->logRequest($method, $uri, $query);
        }

        try {
            $request = $this->httpClient->createRequest()
                ->setMethod($method)
                ->setUrl(ltrim($uri, '/'))
                ->addHeaders($this->buildHeaders());

            if ($query !== []) {
                $request->setData($query);
            }

            $response = $request->send();
            $statusCode = (int) $response->statusCode;

            if ($statusCode === 403) {
                throw new AuthenticationException('VirtualStock API authentication failed (403)');
            }

            if ($statusCode === 429) {
                $body = $response->content ?? '';
                $e429 = new RateLimitException('VirtualStock API rate limit exceeded', null, $body);
                if ($this->logger !== null) {
                    $this->logger->logRateLimit($uri);
                }
                throw $e429;
            }

            if ($statusCode < 200 || $statusCode >= 300) {
                $body = $response->content ?? '';
                throw new ApiException('VirtualStock API request failed with status ' . $statusCode, $statusCode, $body);
            }

            $data = $response->getData();

            if ($this->rateLimiter !== null) {
                $this->rateLimiter->record();
            }

            if ($this->logger !== null) {
                $this->logger->logResponse($method, $uri, $statusCode, $data);
            }

            return is_array($data) ? $data : [];
        } catch (HttpClientException $e) {
            if ($this->logger !== null) {
                $this->logger->logError($method, $uri, $e);
            }
            throw new ApiException('VirtualStock API request failed: ' . $e->getMessage(), 0, '', $e);
        }
    }

    private function buildHeaders(): array
    {
        $headers = [
            'Accept' => 'application/json',
            'Content-Type' => 'application/json',
        ];

        if ($this->apiKey !== '' && $this->apiSecret !== '') {
            $headers['Authorization'] = 'Basic ' . base64_encode($this->apiKey . ':' . $this->apiSecret);
        } elseif ($this->apiKey !== '') {
            $headers['Authorization'] = 'Bearer ' . $this->apiKey;
        }

        return array_merge($headers, $this->headers);
    }
}
