<?php
/**
 * Caterpoint API Client Component
 *
 * @category    PortalPeople
 * @package     Caterpoint
 * @author      Portal People Development Team
 * @copyright   Copyright (c) 2026 Portal People (https://portalpeople.co.uk)
 */

namespace PortalPeople\caterpoint\components;

use yii\base\BaseObject;
use yii\base\InvalidConfigException;
use yii\httpclient\Client;
use yii\httpclient\Exception as HttpClientException;

/**
 * Caterpoint API Client
 * 
 * Handles authentication and API calls to Caterpoint REST API
 */
class CaterpointClient extends BaseObject
{
    /**
     * @var string Caterpoint API base endpoint URL
     */
    public string $apiEndpoint = '';

    /**
     * @var string Caterpoint API authentication token
     */
    public string $apiToken = '';

    /**
     * @var int Request timeout in seconds
     */
    public int $timeout = 30;

    /**
     * @var array Custom headers to include in requests
     */
    public array $headers = [];

    /**
     * @var Client HTTP client instance
     */
    private Client $httpClient;

    /**
     * Initialize the client
     * 
     * @throws InvalidConfigException
     */
    public function init(): void
    {
        parent::init();

        if (empty($this->apiEndpoint)) {
            throw new InvalidConfigException('Caterpoint API endpoint must be configured.');
        }

        if (empty($this->apiToken)) {
            throw new InvalidConfigException('Caterpoint API token must be configured.');
        }

        $defaultHeaders = [
            'Authorization' => 'Bearer ' . $this->apiToken,
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ];

        $this->httpClient = new Client([
            'baseUrl' => rtrim($this->apiEndpoint, '/') . '/',
            'timeout' => $this->timeout,
            'defaultHeaders' => array_merge($defaultHeaders, $this->headers),
        ]);
    }

    /**
     * Get orders from Caterpoint API
     * 
     * @param array $filters Optional query filters
     * @param int $limit Maximum number of orders to retrieve
     * @param int $offset Pagination offset
     * @return array
     * @throws \Exception
     */
    public function getOrders(array $filters = [], int $limit = 100, int $offset = 0): array
    {
        $query = array_merge($filters, [
            'limit' => $limit,
            'offset' => $offset,
        ]);

        $response = $this->request('GET', 'api/integration/distributor/orders', $query);

        return [
            'orders' => $response['data'] ?? $response['orders'] ?? [],
            'total' => $response['total'] ?? count($response['data'] ?? $response['orders'] ?? []),
            'limit' => $limit,
            'offset' => $offset,
        ];
    }

    /**
     * Get orders updated since a specific timestamp
     * 
     * @param string|null $cursor ISO 8601 timestamp or cursor
     * @param int $limit Maximum number of orders to retrieve
     * @return array
     * @throws \Exception
     */
    public function getOrdersUpdatedSince(?string $cursor = null, int $limit = 100): array
    {
        $filters = [];
        if ($cursor !== null && $cursor !== '') {
            $filters['updatedSince'] = $cursor;
        }

        $response = $this->getOrders($filters, $limit);

        // Determine next cursor from response
        $nextCursor = null;
        if (isset($response['nextCursor'])) {
            $nextCursor = $response['nextCursor'];
        } elseif (count($response['orders']) >= $limit) {
            // If we got a full page, use the last order's update timestamp as next cursor
            $lastOrder = end($response['orders']);
            if ($lastOrder && isset($lastOrder['updated_at'])) {
                $nextCursor = $lastOrder['updated_at'];
            }
        }

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

    /**
     * Get a single order by ID
     * 
     * @param string $orderId Caterpoint order ID
     * @return array
     * @throws \Exception
     */
    public function getOrder(string $orderId): array
    {
        return $this->request('GET', "api/integration/distributor/orders/{$orderId}");
    }

    /**
     * Mark an order as exported/deleted
     * 
     * @param string $orderId Caterpoint order ID
     * @return array
     * @throws \Exception
     */
    public function markOrderExported(string $orderId): array
    {
        return $this->request('DELETE', "api/integration/distributor/order/{$orderId}/mark-exported");
    }

    /**
     * Submit order confirmation to Caterpoint
     * 
     * @param array $confirmation Order confirmation data
     * @return array
     * @throws \Exception
     */
    public function submitOrderConfirmation(array $confirmation): array
    {
        return $this->request('POST', 'api/integration/distributor/order-confirmations', [], $confirmation);
    }

    /**
     * Submit invoice to Caterpoint
     * 
     * @param array $invoice Invoice data
     * @return array
     * @throws \Exception
     */
    public function submitInvoice(array $invoice): array
    {
        return $this->request('POST', 'api/integration/distributor/invoices', [], $invoice);
    }

    /**
     * Submit credit note to Caterpoint
     * 
     * @param array $creditNote Credit note data
     * @return array
     * @throws \Exception
     */
    public function submitCreditNote(array $creditNote): array
    {
        return $this->request('POST', 'api/integration/distributor/credit-notes', [], $creditNote);
    }

    /**
     * Execute an HTTP request to the API
     * 
     * @param string $method HTTP method (GET, POST, PUT, DELETE)
     * @param string $endpoint API endpoint
     * @param array $query Query parameters
     * @param array|null $body Request body data
     * @return array
     * @throws \Exception
     */
    private function request(string $method, string $endpoint, array $query = [], ?array $body = null): array
    {
        try {
            $endpointUrl = $this->normalizeEndpoint($endpoint);

            // Append query parameters to URL for GET requests
            if (!empty($query) && $method === 'GET') {
                $endpointUrl .= '?' . http_build_query($query);
            }

            $requestData = $body;
            if ($body !== null && $method !== 'GET') {
                // For POST/PUT/DELETE, pass body as request data
                $requestData = $body;
            }

            $response = $this->httpClient->request($method, $endpointUrl, $requestData);

            if (!$response->isOk) {
                $statusCode = $response->statusCode;
                $responseContent = $response->content;
                throw new \Exception(
                    "Caterpoint API Error ({$statusCode}): {$responseContent}",
                    $statusCode
                );
            }

            return $response->getData() ?? [];
        } catch (HttpClientException $e) {
            throw new \Exception('Caterpoint API Error: ' . $e->getMessage(), 0, $e);
        }
    }

    /**
     * Normalize endpoint path to avoid duplicated /api segments.
     *
     * @param string $endpoint
     * @return string
     */
    private function normalizeEndpoint(string $endpoint): string
    {
        $normalizedEndpoint = ltrim($endpoint, '/');
        $normalizedBase = rtrim($this->apiEndpoint, '/');

        if (str_ends_with($normalizedBase, '/api') && str_starts_with($normalizedEndpoint, 'api/')) {
            return substr($normalizedEndpoint, 4);
        }

        return $normalizedEndpoint;
    }
}
