<?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);

        // Support DRF-style pagination (count/next/previous/results) as well as
        // legacy envelope formats (orders, data) and bare arrays.
        $orders = [];
        if (isset($response['results']) && is_array($response['results'])) {
            $orders = $response['results'];
        } elseif (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;
        }

        // Resolve next-page cursor: prefer explicit cursor fields, then DRF `next` URL,
        // then fall back to the last order's updatedAt / order_date timestamp.
        $nextCursor = null;
        foreach (['nextCursor', 'cursor'] as $key) {
            if (isset($response[$key]) && is_string($response[$key]) && $response[$key] !== '') {
                $nextCursor = $response[$key];
                break;
            }
        }
        if ($nextCursor === null && isset($response['next']) && is_string($response['next']) && $response['next'] !== '') {
            $nextCursor = $response['next'];
        }
        if ($nextCursor === null && count($orders) > 0) {
            $last = end($orders);
            if (is_array($last)) {
                foreach (['updatedAt', 'updated_at', 'order_date'] as $field) {
                    if (isset($last[$field]) && is_string($last[$field]) && $last[$field] !== '') {
                        $nextCursor = $last[$field];
                        break;
                    }
                }
            }
        }

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

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

    /**
     * Fetch a single order line item by its numeric ID.
     * Item IDs are found inside order responses under the `items` array
     * (e.g. items[].url → .../items/577754/).
     *
     * @param string|int $itemId  Numeric item ID
     */
    public function getItem(string $itemId): array
    {
        return $this->request('GET', 'items/' . rawurlencode($itemId));
    }

    /**
     * Retrieve stock information for a product.
     */
    public function getProductStock(string $supplierProductNo): array
    {
        $data = $this->request('GET', 'products/?part_number=' . $supplierProductNo);
        Yii::warning($data);
        return $data;
    }

    /**
     * Update stock information for a product.
     * Returns the API response as array.
     */

    /**
     * Search products by part_number using query param endpoint on a different base URL.
     * Accepts either a supplier SKU or a fully-qualified URL fragment.
     * Example: /products/?part_number={SKU} or full URL starting with http
     */
    public function searchProductByPartNumber(string $supplierProductNo): array
    {
        $encoded = rawurlencode($supplierProductNo);
        // Use query style endpoint. Some environments require a different base URL
        // so callers may pass an absolute URL via $supplierProductNo starting with http
        $uri = 'products/?part_number=' . $encoded;
        return $this->request('GET', $uri);
    }
    public function updateProductStock(string $supplierProductNo, array $payload): array
    {
        return $this->request('PATCH', 'products/' . $supplierProductNo, $payload);
    }

    /**
     * Update an order on the VirtualStock API.
     * Accepts a partial payload and issues a PATCH request to the order resource.
     * Returns parsed JSON response as array.
     *
     * @param string $orderNo
     * @param array $payload
     * @return array
     */
    public function updateOrder(string $orderNo, array $payload): array
    {
        return $this->request('PATCH', 'orders/' . rawurlencode($orderNo), $payload);
    }

    /**
     * Acknowledge an order on the VirtualStock API.
     * Uses the `acknowledge` sub-resource on the order. Returns parsed JSON response.
     *
     * @param string $orderNo
     * @return array
     */
    /**
     * Acknowledge an order on the VirtualStock API.
     * Accepts optional payload (eg. items array) which will be sent as the JSON body.
     * Uses the `acknowledge` sub-resource on the order and appends `?format=json`.
     *
     * @param string $orderUrl  The order URL (e.g. `/orders/572JJ/`) to acknowledge
     * @param array $payload
     * @return array
     */
    public function acknowledgeOrder(string $orderUrl, array $payload = []): array
    {
        $uri = "{$orderUrl}acknowledge/";
        // Try POST as documentation suggests, but fall back to PATCH if the API
        // responds with 405 Method Not Allowed (some environments disallow POST).
        try {
            return $this->request('POST', $uri, $payload);
        } catch (ApiException $e) {
            if ($e->getCode() === 405) {
                if ($this->logger !== null) {
                    $this->logger->logError('acknowledge-fallback', $uri, $e);
                }
                return $this->request('POST', $uri, );
            }
            throw $e;
        }
    }
    /**
     * Dispatch an order on the VirtualStock API.
     * Accepts optional payload (eg. items array) which will be sent as the JSON body.
     * Uses the `dispatch` sub-resource on the order and appends `?format=json`.
     *
     * @param string $orderUrl  The order URL (e.g. `/orders/572JJ/`) to dispatch
     * @param array $payload
     * @return array
     */
    public function dispatchOrder(string $orderUrl, array $payload = []): array
    {
        $uri = "{$orderUrl}dispatch/";
        // Try POST as documentation suggests, but fall back to PATCH if the API
        // responds with 405 Method Not Allowed (some environments disallow POST).
        try {
            $post = $this->request('POST', $uri, $payload);
            \yii::warning($post);
            return $post;
        } catch (ApiException $e) {
            if ($e->getCode() === 405) {
                if ($this->logger !== null) {
                    $this->logger->logError('dispatch-fallback', $uri, $e);
                }
                return $this->request('PATCH', $uri, $payload);
            }
            throw $e;
        }
    }
    /**
     * Acknowledge an order on the VirtualStock API.
     * Accepts optional payload (eg. items array) which will be sent as the JSON body.
     * Uses the `acknowledge` sub-resource on the order and appends `?format=json`.
     *
     * @param string $orderUrl  The order URL (e.g. `/orders/572JJ/`) to acknowledge
     * @param array $payload
     * @return array
     */
    public function deliverOrder(string $orderUrl, array $payload = []): array
    {
        $uri = "{$orderUrl}dispatch/";
        // Try POST as documentation suggests, but fall back to PATCH if the API
        // responds with 405 Method Not Allowed (some environments disallow POST).
        try {
            $post = $this->request('POST', $uri, $payload);
            \yii::warning($post);
            return $post;
        } catch (ApiException $e) {
            if ($e->getCode() === 405) {
                if ($this->logger !== null) {
                    $this->logger->logError('dispatch-fallback', $uri, $e);
                }
                return $this->request('PATCH', $uri, $payload);
            }
            throw $e;
        }
    }

    /**
     * Fetch the retailers/customers list.
     *
     * @param array<string, mixed> $filters  Optional query filters (e.g. ['limit' => 10])
     */
    public function listCustomers(array $filters = []): array
    {
        return $this->request('GET', 'customers', $filters);
    }

    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)
                ->addHeaders($this->buildHeaders());

            // Allow absolute URLs (different base) or relative URIs
            if (preg_match('#^https?://#i', $uri)) {
                $request->setUrl($uri);
            } else {
                $request->setUrl(ltrim($uri, '/'));
            }

            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 ?? '';
                $headers = $response->getHeaders();
                $headerLines = [];
                foreach ($headers as $name => $values) {
                    $headerLines[] = $name . ': ' . implode(', ', (array) $values);
                }
                $responseInfo = sprintf("status=%d; headers=%s; body=%s", $statusCode, implode("; ", $headerLines), is_string($body) ? $body : json_encode($body));

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

                throw new ApiException('VirtualStock API request failed with status ' . $statusCode . ' - ' . $responseInfo, $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);
    }
}
