<?php

declare(strict_types=1);

/*
 * This file is part of vonRotenberg Shopware API Bundle.
 *
 * (c) vonRotenberg
 *
 * @license proprietary
 */

namespace vonRotenberg\ShopwareApiBundle\API;

use Contao\System;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

class Shopware
{
    protected $serializer;
    protected $httpClient;
    protected $clientId;
    protected $clientSecret;
    protected $apiEndpoint;

    private $token;
    private $tokenType;

    public function __construct(SerializerInterface $serializer, HttpClientInterface $httpClient)
    {
        $this->serializer = $serializer;
        $this->httpClient = $httpClient;

        $this->clientId = System::getContainer()->getParameter('vonrotenberg_shopware_api.credentials.client_id');
        $this->clientSecret = System::getContainer()->getParameter('vonrotenberg_shopware_api.credentials.client_secret');
        $this->apiEndpoint = System::getContainer()->getParameter('vonrotenberg_shopware_api.credentials.api_endpoint');
    }

    protected function getClientId()
    {
        return $this->clientId;
    }

    protected function getClientSecret()
    {
        return $this->clientSecret;
    }

    protected function getApiEndpoint()
    {
        return rtrim($this->apiEndpoint,'/');
    }

    protected function sendRequest(string $relEndpoint, array $options, string $method = 'GET',bool $blnFQDNEndpoint=false)
    {
        if ($blnFQDNEndpoint)
        {
            return $this->httpClient->request($method,$relEndpoint,$options);
        }

        $relEndpoint = '/' . ltrim($relEndpoint,'/');

        $request = $this->httpClient->request($method,$this->getApiEndpoint().$relEndpoint,$options);

        if ($request->getStatusCode() == 401)
        {
            throw new \RuntimeException('Unauthorized');
        }

        return $request;
    }

    protected function getAccessToken()
    {
        $options = [
            'body' => json_encode([
                'grant_type' => 'client_credentials',
                'client_id' => $this->clientId,
                'client_secret' => $this->clientSecret
            ]),
            'headers' => ['Content-Type: application/json', 'Accept: application/json'],
        ];

        $response = $this->sendRequest('oauth/token',$options,'POST');

        if ($response->getStatusCode() == 200)
        {
            $content = $response->getContent();

            return json_decode($content);
        }

        throw new \RuntimeException('Can\'t retrieve access token. Check credentials.');
    }

    protected function getAuthentication()
    {
        $Token = $this->getAccessToken();
        return 'Authorization: ' . $Token->token_type . ' ' . $Token->access_token;
    }

    public function isShopwareRunning(): bool
    {
        $options = [
            'headers' => [
                $this->getAuthentication(),
                'Content-Type: application/json',
                'Accept: application/json'
            ],
        ];

        $response = $this->sendRequest('_info/health-check',$options,'GET');

        if ($response->getStatusCode() == 200)
        {
            return true;
        }

        return false;
    }

    public function getProductsForSku(string $strSku, bool $blnWildcardSearch=false, bool $blnShort=false): ResponseInterface
    {
        $options = [
            'headers' => [
                $this->getAuthentication(),
                'Content-Type: application/json',
                'Accept: application/json'
            ],
            'body' => json_encode([
                'filter' => [
                    [
                        'type' => !$blnWildcardSearch ? 'equals' : 'contains',
                        'field' => 'productNumber',
                        'value' => $strSku
                    ]
                ],
                'sort' => [
                    [
                        'field' => 'productNumber',
                        'order' => 'ASC',
                        'naturalSorting' => true
                    ]
                ]
            ])
        ];

        if ($blnShort)
        {
            $options['body'] = json_encode(
                array_merge(
                    json_decode($options['body'],true),
                    [
                        'fields' => [
                            'name'
                        ]
                    ]
                )
            );
        }

        return $this->sendRequest('search/product',$options,'POST');
    }

    public function findOrdersByFilter(array $filter, bool $blnShort=false): ResponseInterface
    {
        $options = [
            'headers' => [
                $this->getAuthentication(),
                'Content-Type: application/json',
                'Accept: application/json'
            ],
            'body' => json_encode([
                'filter' => $filter,
                'sort' => [
                    [
                        'field' => 'createdAt',
                        'order' => 'ASC',
                        'naturalSorting' => true
                    ]
                ],
                'includes' => [
                    'order' => ['id', 'orderNumber', 'createdAt', 'billingAddress', 'deliveries', 'lineItems','taxStatus','orderCustomer','amountTotal','shippingTotal','transactions'],
                    'order_delivery' => ['shippingOrderAddress','shippingMethod'],
                    'country' => ['name','iso'],
                    'order_line_item' => ['position', 'quantity', 'label', 'unitPrice', 'totalPrice', 'price','priceDefinition', 'productId', 'product', 'type', 'payload'],
                    'product' => ['ean','productNumber','name'],
                    'payment_method' => ['id','name','shortName'],
                    'shipping_method' => ['id','name'],
                    'order_transaction' => ['amount','paymentMethod'],
                ],
                'associations' => [
                    'transactions' => [
                        'associations' => [
                            'paymentMethod' => []
                        ]
                    ],
                    'billingAddress' => [
                        'associations' => [
                            'country' => []
                        ]
                    ],
                    'deliveries' => [
                        'associations' => [
                            'shippingOrderAddress' => [
                                'associations' => [
                                    'country' => []
                                ]
                            ],
                            'shippingMethod' => []
                        ]
                    ],
                    'lineItems' => [
                        'associations' => [
                            'product' => []
                        ]
                    ],
                ]
            ])
        ];

        if ($blnShort)
        {
            $options['body'] = json_encode(
                array_merge(
                    json_decode($options['body'],true),
                    [
                        'fields' => [
                            'orderNumber'
                        ]
                    ]
                )
            );
        }

        return $this->sendRequest('search/order',$options,'POST');
    }

    public function getOrderAddressById(string $strId, bool $blnShort=false): ResponseInterface
    {
        $options = [
            'headers' => [
                $this->getAuthentication(),
                'Content-Type: application/json',
                'Accept: application/json'
            ]
        ];

        return $this->sendRequest('order-address/'.$strId,$options,'GET');
    }

    public function getOrder(string $strId, ?string $pathSuffix=null): ResponseInterface
    {
        $options = [
            'headers' => [
                $this->getAuthentication(),
                'Content-Type: application/json',
                'Accept: application/json'
            ]
        ];

        return $this->sendRequest('order/'.$strId.($pathSuffix ? '/'.$pathSuffix : ''),$options,'GET');
    }

    public function getOrderLineItemsByFilter(array $filter, bool $blnShort=false): ResponseInterface
    {
        $options = [
            'headers' => [
                $this->getAuthentication(),
                'Content-Type: application/json',
                'Accept: application/json'
            ],
            'body' => json_encode([
                'filter' => $filter,
                'sort' => [
                    [
                        'field' => 'createdAt',
                        'order' => 'ASC',
                        'naturalSorting' => true
                    ]
                ]
            ])
        ];

        if ($blnShort)
        {
            $options['body'] = json_encode(
                array_merge(
                    json_decode($options['body'],true),
                    [
                        'fields' => [
                            'id',
                            'label',
                            'quantity',
                        ]
                    ]
                )
            );
        }

        return $this->sendRequest('search/order-line-item',$options,'POST');
    }

    public function queryAPI(string $strUrlFragment, ?string $strBody, string $strMethod = 'GET', bool $blnAuthenticate=true): ResponseInterface
    {
        $options = [
            'headers' => [
                'Content-Type: application/json',
                'Accept: application/json'
            ]
        ];

        if ($blnAuthenticate)
        {
            $options['headers'][] = $this->getAuthentication();
        }

        if ($strBody !== null)
        {
            $options['body'] = $strBody;
        }

        return $this->sendRequest($strUrlFragment,$options,$strMethod);
    }

    protected function checkBySkuIfExists(string $strSku): bool
    {
        $Result = $this->getProductsForSku($strSku,false,true);

        if ($Result->getStatusCode() == 200)
        {
            $Content = json_decode($Result->getContent());

            if ($Content->total)
            {
                return true;
            }
        }
        return false;
    }

    public function addOrUpdateProductBySku(string $strSku, array $arrData): bool
    {
        $blnInsert = true;
        // Update or insert check
        if (($searchResponse = $this->getProductsForSku($strSku))->getStatusCode() == 200)
        {
            $Product = json_decode($searchResponse->getContent());

            if ($Product->total)
            {
                $blnInsert = false;
            }
        }

        // Prepare product data
        $arrData = array_merge([
            'id' => $blnInsert ? $this->createUuid() : $Product->data[0]->id
        ], $arrData);

        $options = [
            'headers' => [
                $this->getAuthentication(),
                'Content-Type: application/json',
                'Accept: application/json'
            ],
            'body' => json_encode($arrData)
        ];

        if ($blnInsert)
        {
            $response = $this->sendRequest('product',$options,'POST');
        } else {
            $response = $this->sendRequest('product/'.$Product->data[0]->id,$options,'PATCH');
        }

        return in_array($response->getStatusCode(),[200,201,202,203,204]);
    }

    public function truncatePropertiesForProductBySku(string $strSku, string|array|null $groupIds = null): bool
    {
        if ($groupIds !== null && !is_array($groupIds))
        {
            $groupIds = (array) $groupIds;
        }

        // Look for product
        if (($searchResponse = $this->getProductsForSku($strSku))->getStatusCode() == 200)
        {
            $Product = json_decode($searchResponse->getContent());

            if (!$Product->total)
            {
                return false;
            }

            // Get property ids
            $arrPropertiesPayload = [];
            foreach (System::getContainer()->getParameter('vonrotenberg_shopware_api.mappings.properties') as $groupId => $properties)
            {
                if ($groupIds !== null && !in_array($groupId,$groupIds))
                {
                    continue;
                }

                foreach (array_keys($properties) as $propertyId)
                {
                    $arrPropertiesPayload[] = [
                        'productId' => $Product->data[0]->id,
                        'optionId' => $propertyId
                    ];
                }
            }

            // Build request data
            $arrData = [
                [
                    'entity' => 'product_property',
                    'action' => 'delete',
                    'payload' => $arrPropertiesPayload
                ]
            ];

            $options = [
                'headers' => [
                    $this->getAuthentication(),
                    'Content-Type: application/json',
                    'Accept: application/json',
                    'fail-on-error: false'
                ],
                'body' => json_encode($arrData)
            ];

            // Send request
            $response = $this->sendRequest('_action/sync',$options,'POST');

            return in_array($response->getStatusCode(),[200]);
        }

        return false;
    }

    protected function createUuid($data = null) {
        // Generate 16 bytes (128 bits) of random data or use the data passed into the function.
        $data = $data ?? random_bytes(16);
        assert(strlen($data) == 16);

        // Set version to 0100
        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
        // Set bits 6-7 to 10
        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);

        // Output the 36 character UUID.
        return bin2hex($data);
    }
}