<?php

namespace Alsendo\AlsendoWrapper\Api\Zaslat;

use Alsendo\AlsendoWrapper\Api\Zaslat\Model\Order\Valuation\ZaslatOrderValuation;
use Alsendo\AlsendoWrapper\Api\Zaslat\Model\Order\ZaslatOrderCreatedResponse;
use Alsendo\AlsendoWrapper\Api\Zaslat\Model\Order\ZaslatOrderResponse;
use Alsendo\AlsendoWrapper\Api\Zaslat\Model\ZaslatContact;
use Alsendo\AlsendoWrapper\Api\Zaslat\Wrapper\OrderRequestWrapper;
use Alsendo\AlsendoWrapper\Api\Zaslat\Wrapper\OrderValuationRateWrapper;
use Alsendo\AlsendoWrapper\Api\Zaslat\Wrapper\OrderValuationRequestWrapper;
use Alsendo\AlsendoWrapper\ApiClient;
use Alsendo\AlsendoWrapper\ApiClientInterface;
use Alsendo\AlsendoWrapper\Exception\ResponseException;
use Alsendo\AlsendoWrapper\Exception\UnresponsivenessException;
use Alsendo\AlsendoWrapper\Exception\UnsupportedEndpointException;
use Alsendo\AlsendoWrapper\Exception\ValidationException;
use Alsendo\AlsendoWrapper\Json;
use Alsendo\AlsendoWrapper\Model\Order\OrderInterface;
use Alsendo\AlsendoWrapper\Model\Order\OrderRequest;
use Alsendo\AlsendoWrapper\Model\WayBill\WayBill;
use Alsendo\AlsendoWrapper\Validator;
use DateMalformedStringException;
use GuzzleHttp\Exception\GuzzleException;
use JsonException;
use ReflectionException;

class ApiZaslatClient extends ApiClient implements ApiClientInterface
{
    public const API_URL = 'https://www.zaslat.cz/';
    public const API_URL_TEST = 'https://test.zaslat.cz/';

    public const CONFIG_REQUIRES = [
        'api_key',
    ];

    public function __construct(array $config, bool $test = false)
    {
        parent::__construct(($test) ? self::API_URL_TEST : self::API_URL, $config);
        $this->authorization();
    }

    private function authorization(): void
    {
        $this->options = [
            'headers' => [
                'X-Apikey' => $this->config['api_key'],
            ]
        ];
    }

    /**
     * Retrieves the service structure data for the service.
     *
     * @return array An array of service IDs and their corresponding names.
     */
    public function getServiceStructure(): array
    {
        return [
            'BALIKOVNA' => 'BALIKOVNA',
            'ZASILKOVNA' => 'ZASILKOVNA',
            'GLS' => 'GLS',
            'DPD' => 'DPD',
            'PPL' => 'PPL',
            'ONE' => 'WEDO/One by Allegro',
            'TOPTRANS' => 'TOPTRANS',
            'UPS' => 'UPS',
            'FEDEX' => 'FEDEX',
        ];
    }

    /**
     * Retrieves the valuation for an order based on shipping rates.
     *
     * @param OrderRequest $order The order request containing shipment details for valuation.
     * @return array An array of OrderValuation objects representing the available shipping rates and their valuations.
     * @throws GuzzleException
     * @throws JsonException
     * @throws ResponseException
     * @throws UnresponsivenessException
     * @throws ValidationException
     * @throws ReflectionException
     */
    public function getOrderValuation(OrderRequest $order): array
    {
        $endpoint = '/api/v1/rates/get';

        $validator = new Validator(ValidationRules::ORDER_CREATE);
        $validator->validate($order);

        if ($validator->hasErrors()) {
            throw new ValidationException($validator->getErrors());
        }

        $request = OrderValuationRequestWrapper::wrap($order);


        $order_to_api = $request->toArray();
        if( empty($order_to_api['type']) ) {
            unset( $order_to_api['type'] );
        }
        $debug_place =  __METHOD__ . ': ' . __LINE__;
        $this->zaslat_log_for_woocommerce( $debug_place, $order_to_api );

        $response = $this->makeRequest('POST', $endpoint, [
            'json' => $order_to_api,
        ]);



        $orderValuations = [];

        $response = $this->parseResponse($response);

        $debug_place =  __METHOD__ . ': ' . __LINE__;
        $this->zaslat_log_for_woocommerce( $debug_place, $response );

        if (!empty($response['rates'])) {
            foreach ($response['rates'] as $rate) {
                $rate = OrderValuationRateWrapper::wrap($rate);
                $orderValuations[] = Json::mapArrayToObject($rate, ZaslatOrderValuation::class);
            }
        }

        return $orderValuations;
    }



    /**
     * Sends an order request to the shipping service and returns the response.
     *
     * @param OrderRequest $order The order data to be sent to the shipping service.
     * @return ZaslatOrderCreatedResponse The response object containing the created order details.
     * @throws GuzzleException
     * @throws JsonException
     * @throws ReflectionException
     * @throws ResponseException
     * @throws UnresponsivenessException
     * @throws ValidationException
     * @throws Exception|DateMalformedStringException
     */
    public function sendOrder(OrderRequest $order): ZaslatOrderCreatedResponse
    {
        $endpoint = '/api/v1/shipments/create';

        $validator = new Validator(ValidationRules::ORDER_CREATE);
        $validator->validate($order);

        if ($validator->hasErrors()) {
            throw new ValidationException($validator->getErrors());
        }

        $zaslatOrderRequest = OrderRequestWrapper::wrap($order);

        $order_to_api = $zaslatOrderRequest->toArray();

        if( empty($order_to_api['type']) ) {
            unset( $order_to_api['type'] );
        }

        $debug_place =  __METHOD__ . ': ' . __LINE__;
        $this->zaslat_log_for_woocommerce( $debug_place, $order_to_api );



        $response = $this->makeRequest('POST', $endpoint, [
            'json' => $order_to_api,
        ]);

        $response = $this->parseResponse($response);

        $debug_place =  __METHOD__ . ': ' . __LINE__;
        $this->zaslat_log_for_woocommerce( $debug_place, $response );


        return Json::mapArrayToObject($response['data'], ZaslatOrderCreatedResponse::class);
    }

    /**
     * Retrieves an order detail by its ID from the shipping service.
     *
     * @param string $orderId The unique identifier of the order to retrieve.
     * @return OrderInterface The order interface object containing the order details.
     * @throws GuzzleException
     * @throws JsonException
     * @throws ResponseException
     * @throws UnresponsivenessException
     */
    public function getOrder($orderId): OrderInterface
    {

        $endpoint = '/api/v1/shipments/detail';
        $response = $this->makeRequest('POST', $endpoint, [
            'json' => [
                'shipments' => [$orderId]
            ],
        ]);

        $response = $this->parseResponse($response);

        $zaslatOrderResponse = Json::mapArrayToObject($response['data'][$orderId], ZaslatOrderResponse::class);
        $zaslatOrderResponse->id = $orderId;

        return $zaslatOrderResponse;
    }

    /**
     * Retrieves order details for the specified order IDs by sending a request to the shipping service.
     *
     * @param array $orderIds An array of order IDs to fetch details for.
     * @return array An associative array where the key is the order ID and the value is a ZaslatOrderResponse object containing the order details.
     * @throws GuzzleException
     * @throws JsonException
     * @throws ResponseException
     * @throws UnresponsivenessException
     */
    public function getOrders(array $orderIds): array
    {
        $endpoint = '/api/v1/shipments/detail';
        $response = $this->makeRequest('POST', $endpoint, [
            'json' => [
                'shipments' => [$orderIds]
            ],
        ]);


        $response = $this->parseResponse($response);

        $zaslatOrdersResponse = [];

        foreach ($response['data'] as $orderId =>$order) {
            $zaslatOrdersResponse[$orderId] = Json::mapArrayToObject($order, ZaslatOrderResponse::class);
            $zaslatOrdersResponse[$orderId]->id = $orderId;
        }


        return $zaslatOrdersResponse;
    }

    /**
     * Retrieves the waybill data for a given order ID by making a request to the shipping service.
     *
     * @param string $orderId The unique identifier of the shipment for which to retrieve the waybill.
     * @return array The response data containing the waybill information.
     * @throws GuzzleException
     * @throws JsonException
     * @throws ResponseException
     * @throws UnresponsivenessException
     */
    public function getWayBill($orderId)
    {
        $endpoint = '/api/v1/shipments/label';
        $response = $this->makeRequest('POST', $endpoint, [
            'json' => [
                'shipments' => [$orderId]
            ],
        ]);

        $response = $this->parseResponse($response);

        return $response['data'];
    }

    /**
     * Cancels a shipment order by sending a request to the shipping service.
     *
     * @param string $orderId The unique identifier of the shipment to be canceled.
     * @return bool True if the cancellation request was successfully processed, false otherwise.
     * @throws GuzzleException
     * @throws JsonException
     * @throws ResponseException
     * @throws UnresponsivenessException
     */
    public function cancelOrder($orderId): bool
    {
        $endpoint = '/api/v1/shipments/storno';
        $response = $this->makeRequest('POST', $endpoint, [
            'json' => [
                'shipments' => [$orderId]
            ],
        ]);

        $response = $this->parseResponse($response);

        return true;
    }

    /**
     * Retrieves a list of turn-in records for the specified order IDs.
     *
     * @param array $orderIds A list of order IDs to query turn-in records for.
     * @return array The turn-in records data returned from the API.
     * @throws GuzzleException
     * @throws JsonException
     * @throws ResponseException
     * @throws UnresponsivenessException
     */
    public function getTurnInList(array $orderIds)
    {
        $endpoint = '/api/v1/shipments/manifest';
        $response = $this->makeRequest('POST', $endpoint, [
            'json' => [
                'shipments' => [$orderIds]
            ],
        ]);

        $response = $this->parseResponse($response);

        return $response['data'];
    }

    /**
     * Retrieves a list of contacts from the contact management service.
     *
     * @return array An array of ZaslatContact objects representing the retrieved contact list.
     * @throws GuzzleException
     * @throws JsonException
     * @throws ResponseException
     * @throws UnresponsivenessException
     */
    public function getContactList(): array
    {
        $endpoint = '/api/v1/contacts/list';
        $response = $this->makeRequest('GET', $endpoint);

        $response = $this->parseResponse($response);

        $zaslatContactListResponse = [];

        foreach ($response['data'] as $contact) {
            $zaslatContactListResponse[] = Json::mapArrayToObject($contact, ZaslatContact::class);
        }

        return $zaslatContactListResponse;
    }

    /**
     * Parses the raw response from the API into a structured array.
     *
     * @param string $response The raw response string from the API.
     * @return array The parsed response array containing status, message, and other data.
     * @throws UnresponsivenessException If the response is not a valid array or missing required fields.
     * @throws ResponseException If the response status is not 200.
     * @throws JsonException
     */
    private function parseResponse($response): array
    {
        $decodedResponse = json_decode($response, true, 10, JSON_THROW_ON_ERROR);

        if (!is_array($decodedResponse) || !isset(
                $decodedResponse['status'],
                $decodedResponse['message'],)
        ) {

            throw new UnresponsivenessException();
        }

        $status = (int)$decodedResponse['status'];

        if ($status !== 200) {
            throw new ResponseException($decodedResponse['message'], $status);
        }

        return $decodedResponse;

    }


    public function zaslat_log_for_woocommerce( $debug_place, $data )
    {

        if ( function_exists( 'wc_get_logger' ) ) {

            $order_id = ! empty( $_POST['order_id'] ) && is_numeric( $_POST['order_id'] ) ? sanitize_text_field(wp_unslash($_POST['order_id'])) : '';

            $logger = wc_get_logger();
            $logger->log(
                'debug',
                'ZASLAT: for order #: ' . $order_id,
                array(
                    'source'            => 'alsendo-connect-order-' . $order_id,
                    'order_id'          => $order_id,
                    'debug_place'  => $debug_place,
                    'data' => $data,
                )
            );
        }
    }
}