<?php
declare(strict_types=1);

namespace App\Infrastructure\Api;

use App\Framework\Api\ApiException;
use App\Framework\Api\ApiRequestTrait;
use App\Framework\Http\Method;
use App\Framework\HttpClient\AuthConfig;
use App\Framework\HttpClient\ClientOptions;
use App\Framework\HttpClient\ClientResponse;
use App\Framework\HttpClient\CurlHttpClient;
use App\Framework\HttpClient\HttpClient;

final class ShopifyClient
{
    use ApiRequestTrait;

    private string $apiVersion;

    /**
     * Erstellt einen neuen Shopify API-Client
     *
     * @param string $shopDomain Die Shopify-Domain (z.B. 'my-store.myshopify.com')
     * @param string $accessToken Das Zugriffstoken für die API
     * @param string $apiVersion Die API-Version (z.B. '2023-10')
     * @param HttpClient|null $httpClient Ein optionaler HTTP-Client
     */
    public function __construct(
        string $shopDomain,
        string $accessToken,
        string $apiVersion = '2024-04',
        ?HttpClient $httpClient = null
    ) {
        $this->baseUrl = "https://{$shopDomain}/admin/api/{$apiVersion}";
        $this->apiVersion = $apiVersion;

        $this->defaultOptions = new ClientOptions(
            auth: AuthConfig::custom([
                'headers' => ['X-Shopify-Access-Token' => $accessToken]
            ])
        );

        $this->httpClient = $httpClient ?? new CurlHttpClient();
    }

    /**
     * Ruft eine Liste aller Produkte ab
     *
     * @param array $options Optionale Parameter (limit, since_id, usw.)
     * @return array Die Produktliste
     */
    public function getProducts(array $options = []): array
    {
        $queryParams = $this->buildQueryParams($options);
        $endpoint = 'products.json' . $queryParams;

        $response = $this->sendRequest(
            method: Method::GET,
            endpoint: $endpoint
        );

        return $this->decodeJson($response)['products'] ?? [];
    }

    /**
     * Ruft ein einzelnes Produkt ab
     *
     * @param int $productId Die Produkt-ID
     * @return array Die Produktdaten
     */
    public function getProduct(int $productId): array
    {
        $response = $this->sendRequest(
            method: Method::GET,
            endpoint: "products/{$productId}.json"
        );

        return $this->decodeJson($response)['product'] ?? [];
    }

    /**
     * Erstellt ein neues Produkt
     *
     * @param array $productData Die Produktdaten
     * @return array Das erstellte Produkt
     */
    public function createProduct(array $productData): array
    {
        $response = $this->sendRequest(
            method: Method::POST,
            endpoint: 'products.json',
            data: ['product' => $productData]
        );

        return $this->decodeJson($response)['product'] ?? [];
    }

    /**
     * Aktualisiert ein bestehendes Produkt
     *
     * @param int $productId Die Produkt-ID
     * @param array $productData Die zu aktualisierenden Produktdaten
     * @return array Das aktualisierte Produkt
     */
    public function updateProduct(int $productId, array $productData): array
    {
        $response = $this->sendRequest(
            method: Method::PUT,
            endpoint: "products/{$productId}.json",
            data: ['product' => $productData]
        );

        return $this->decodeJson($response)['product'] ?? [];
    }

    /**
     * Löscht ein Produkt
     *
     * @param int $productId Die Produkt-ID
     * @return bool Erfolg oder Misserfolg
     */
    public function deleteProduct(int $productId): bool
    {
        $response = $this->sendRequest(
            method: Method::DELETE,
            endpoint: "products/{$productId}.json"
        );

        return $response->status->value === 200;
    }

    /**
     * Ruft eine Liste aller Bestellungen ab
     *
     * @param array $options Optionale Parameter (limit, status, usw.)
     * @return array Die Bestellungsliste
     */
    public function getOrders(array $options = []): array
    {
        $queryParams = $this->buildQueryParams($options);
        $endpoint = 'orders.json' . $queryParams;

        $response = $this->sendRequest(
            method: Method::GET,
            endpoint: $endpoint
        );

        return $this->decodeJson($response)['orders'] ?? [];
    }

    /**
     * Ruft eine einzelne Bestellung ab
     *
     * @param int $orderId Die Bestellungs-ID
     * @return array Die Bestellungsdaten
     */
    public function getOrder(int $orderId): array
    {
        $response = $this->sendRequest(
            method: Method::GET,
            endpoint: "orders/{$orderId}.json"
        );

        return $this->decodeJson($response)['order'] ?? [];
    }

    /**
     * Erstellt eine neue Bestellung
     *
     * @param array $orderData Die Bestellungsdaten
     * @return array Die erstellte Bestellung
     */
    public function createOrder(array $orderData): array
    {
        $response = $this->sendRequest(
            method: Method::POST,
            endpoint: 'orders.json',
            data: ['order' => $orderData]
        );

        return $this->decodeJson($response)['order'] ?? [];
    }

    /**
     * Ruft Informationen über den Shop ab
     *
     * @return array Die Shop-Informationen
     */
    public function getShopInfo(): array
    {
        $response = $this->sendRequest(
            method: Method::GET,
            endpoint: 'shop.json'
        );

        return $this->decodeJson($response)['shop'] ?? [];
    }

    /**
     * Ruft eine Liste aller Kunden ab
     *
     * @param array $options Optionale Parameter (limit, since_id, usw.)
     * @return array Die Kundenliste
     */
    public function getCustomers(array $options = []): array
    {
        $queryParams = $this->buildQueryParams($options);
        $endpoint = 'customers.json' . $queryParams;

        $response = $this->sendRequest(
            method: Method::GET,
            endpoint: $endpoint
        );

        return $this->decodeJson($response)['customers'] ?? [];
    }

    /**
     * Erstellt einen neuen Kunden
     *
     * @param array $customerData Die Kundendaten
     * @return array Der erstellte Kunde
     */
    public function createCustomer(array $customerData): array
    {
        $response = $this->sendRequest(
            method: Method::POST,
            endpoint: 'customers.json',
            data: ['customer' => $customerData]
        );

        return $this->decodeJson($response)['customer'] ?? [];
    }

    /**
     * Ruft einen einzelnen Kunden ab
     *
     * @param int $customerId Die Kunden-ID
     * @return array Die Kundendaten
     */
    public function getCustomer(int $customerId): array
    {
        $response = $this->sendRequest(
            method: Method::GET,
            endpoint: "customers/{$customerId}.json"
        );

        return $this->decodeJson($response)['customer'] ?? [];
    }

    /**
     * Erstellt einen neuen Webhook
     *
     * @param string $topic Das Webhook-Thema (z.B. 'orders/create')
     * @param string $address Die URL, die aufgerufen werden soll
     * @param string $format Das Format (JSON oder XML)
     * @return array Die Webhook-Daten
     */
    public function createWebhook(string $topic, string $address, string $format = 'json'): array
    {
        $response = $this->sendRequest(
            method: Method::POST,
            endpoint: 'webhooks.json',
            data: [
                'webhook' => [
                    'topic' => $topic,
                    'address' => $address,
                    'format' => $format
                ]
            ]
        );

        return $this->decodeJson($response)['webhook'] ?? [];
    }

    /**
     * Ruft alle Webhooks ab
     *
     * @return array Die Liste der Webhooks
     */
    public function getWebhooks(): array
    {
        $response = $this->sendRequest(
            method: Method::GET,
            endpoint: 'webhooks.json'
        );

        return $this->decodeJson($response)['webhooks'] ?? [];
    }

    /**
     * Sucht Produkte anhand von Suchkriterien
     *
     * @param string $query Die Suchanfrage
     * @param array $options Zusätzliche Optionen
     * @return array Die gefundenen Produkte
     */
    public function searchProducts(string $query, array $options = []): array
    {
        $options = array_merge($options, ['query' => $query]);
        $queryParams = $this->buildQueryParams($options);

        $response = $this->sendRequest(
            method: Method::GET,
            endpoint: 'products/search.json' . $queryParams
        );

        return $this->decodeJson($response)['products'] ?? [];
    }

    /**
     * Ruft Metafields für eine Ressource ab
     *
     * @param string $resourceType Der Ressourcentyp (product, customer, usw.)
     * @param int $resourceId Die Ressourcen-ID
     * @return array Die Metafields
     */
    public function getMetafields(string $resourceType, int $resourceId): array
    {
        $response = $this->sendRequest(
            method: Method::GET,
            endpoint: "{$resourceType}s/{$resourceId}/metafields.json"
        );

        return $this->decodeJson($response)['metafields'] ?? [];
    }

    /**
     * Verarbeitet Rate-Limiting-Informationen aus den Headern
     *
     * @param ClientResponse $response Die API-Antwort
     * @return array Rate-Limiting-Informationen
     */
    public function getRateLimitInfo(ClientResponse $response): array
    {
        $headers = $response->headers->all();

        return [
            'limit' => (int)($headers['X-Shopify-Shop-Api-Call-Limit'][0] ?? 0),
            'remaining' => isset($headers['X-Shopify-Shop-Api-Call-Limit'][0])
                ? $this->parseRemainingCalls($headers['X-Shopify-Shop-Api-Call-Limit'][0])
                : null,
        ];
    }

    /**
     * Baut Query-Parameter aus einem Options-Array
     *
     * @param array $options Die Optionen
     * @return string Die Query-Parameter als String
     */
    private function buildQueryParams(array $options): string
    {
        if (empty($options)) {
            return '';
        }

        return '?' . http_build_query($options);
    }

    /**
     * Parst die verbleibenden API-Aufrufe aus dem Header
     *
     * @param string $limitHeader Der Header-Wert
     * @return int|null Die verbleibenden Aufrufe
     */
    private function parseRemainingCalls(string $limitHeader): ?int
    {
        if (preg_match('/^(\d+)\/(\d+)$/', $limitHeader, $matches)) {
            $current = (int)$matches[1];
            $limit = (int)$matches[2];
            return $limit - $current;
        }

        return null;
    }
}
