<?php

namespace Alsendo\AlsendoWrapper\Api\Apaczka;

use Alsendo\AlsendoWrapper\Api\Apaczka\Model\ApaczkaServiceStructure;
use Alsendo\AlsendoWrapper\Api\Apaczka\Model\Order\ApaczkaOrderResponse;
use Alsendo\AlsendoWrapper\Api\Apaczka\Model\Order\Created\ApaczkaOrderCreatedResponse;
use Alsendo\AlsendoWrapper\Api\Apaczka\Model\Order\Valuation\ApaczkaOrderValuation;
use Alsendo\AlsendoWrapper\Api\Apaczka\Wrapper\ApaczkaOrderValuationWrapper;
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\Order\OrderResponse;
use Alsendo\AlsendoWrapper\Model\TurnIn\TurnIn;
use Alsendo\AlsendoWrapper\Model\WayBill\WayBill;
use Alsendo\AlsendoWrapper\Validator;
use GuzzleHttp\Exception\GuzzleException;
use JsonException;
use ReflectionException;

class ApiApaczkaClient extends ApiClient implements ApiClientInterface
{
    public const API_URL = 'https://www.apaczka.pl/api/v2/';

    public const API_URL_TEST = 'https://panel-sandbox.apaczka.pl/api/v2/';

    public const CONFIG_REQUIRES = [
        'app_id',
        'app_secret'
    ];

    private const EXPIRES = '+20min';

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


    private function authorization(): void
    {

    }

    private function getSignature($string, $key): string
    {
        return hash_hmac('sha256', $string, $key);
    }

    private function stringToSign($appId, $route, $data, $expires): string
    {
        return sprintf("%s:%s:%s:%s", $appId, $route, $data, $expires);
    }

    /**
     * Retrieves the service structure data from the API.
     *
     * @return ApaczkaServiceStructure The parsed service structure data as an object
     * @throws UnresponsivenessException
     * @throws GuzzleException
     * @throws ResponseException
     * @throws JsonException
     */
    public function getServiceStructure()
    {
        $endpoint = 'service_structure/';

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

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

        return Json::mapArrayToObject($response, ApaczkaServiceStructure::class);
    }

    /**
     * Retrieves the valuation for an order based on the specified order request.
     *
     * @param OrderRequest $order The order request containing details for valuation calculation.
     * @return array An associative array where the key is the service ID and the value is a wrapped valuation object for that service.
     * @throws UnresponsivenessException
     * @throws ReflectionException
     * @throws GuzzleException
     * @throws ResponseException
     * @throws JsonException
     * @throws ValidationException
     */
    public function getOrderValuation(OrderRequest $order): array
    {
        $endpoint = 'order_valuation/';

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

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

        $debug_place =  __METHOD__ . ': ' . __LINE__;
        $request_data = $order->toArray();
        $this->apaczka_log_for_woocommerce($debug_place, $request_data );

        $response = $this->makeRequest('POST', $endpoint, [
            'form_params' => $this->buildRequest(['order' => $order->toArray()], $endpoint),
        ]);

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

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

        $orderValuation = [];
        foreach ($response['price_table'] as $serviceId => $priceTableItem) {

            $orderValuation[$serviceId] = ApaczkaOrderValuationWrapper::wrap($serviceId, $priceTableItem);
        }

        return $orderValuation;
    }

    /**
     * Sends an order request to the API and returns the created order response.
     *
     * @param OrderRequest $order The order data to be sent, containing all required fields.
     * @return ApaczkaOrderCreatedResponse The parsed response object representing the created order.
     * @throws GuzzleException
     * @throws JsonException
     * @throws ReflectionException
     * @throws ResponseException
     * @throws UnresponsivenessException
     * @throws ValidationException
     */
	public function sendOrder(OrderRequest $order)
	{
		$endpoint = 'order_send/';

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

		if ($validator->hasErrors()) {

			$debug_place =  __METHOD__ . ': ' . __LINE__;
			$this->apaczka_log_for_woocommerce( $debug_place, $validator->getErrors() );

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


		$debug_place =  __METHOD__ . ': ' . __LINE__;
		$this->apaczka_log_for_woocommerce( $debug_place, $order->toArray() );


		$response = $this->makeRequest('POST', $endpoint, [
			'form_params' => $this->buildRequest([
				'order'  => $order->toArray(),
				'system' => 'WooCommerce Alsendo Connect',
			], $endpoint),
		]);


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

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


		return Json::mapArrayToObject($response['order'], ApaczkaOrderCreatedResponse::class);
	}

    /**
     * Retrieves an order by its unique identifier from the API and returns it as a response object.
     *
     * @param string $orderId The unique identifier of the order to retrieve.
     * @return OrderInterface The parsed response object representing the requested order.
     * @throws GuzzleException
     * @throws JsonException
     * @throws ResponseException
     * @throws UnresponsivenessException
     */
    public function getOrder($orderId): OrderInterface
    {
        $endpoint = 'order/' . $orderId . '/';
        $response = $this->makeRequest('POST', $endpoint, [
            'form_params' => $this->buildRequest([], $endpoint),
        ]);

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

        return Json::mapArrayToObject($response['order'], ApaczkaOrderResponse::class);
    }

    /**
     * Retrieves order details for the given order IDs by making individual requests for each ID.
     *
     * @param array $orderIds A list of order IDs to fetch order details for.
     * @return array An associative array where the key is the order ID and the value is the order data object.
     * @throws GuzzleException
     * @throws JsonException
     * @throws ResponseException
     * @throws UnresponsivenessException
     */
    public function getOrders(array $orderIds): array
    {
        $orders = [];
        foreach ($orderIds as $orderId) {
            $orders[$orderId] = $this->getOrder($orderId);
        }

        return $orders;
    }

    /**
     * Retrieves a way bill by order ID from the API and returns it as a WayBill object.
     *
     * @param string $orderId The unique identifier of the order for which to retrieve the way bill.
     * @return WayBill The parsed the way bill data as a WayBill object.
     * @throws GuzzleException
     * @throws JsonException
     * @throws ResponseException
     * @throws UnresponsivenessException
     */
    public function getWayBill($orderId)
    {
        $endpoint = 'waybill/' . $orderId . '/';
        $response = $this->makeRequest('POST', $endpoint, [
            'form_params' => $this->buildRequest([], $endpoint),
        ]);

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

        return Json::mapArrayToObject($response, WayBill::class);
    }

    /**
     * Cancels an order by its ID using a POST request to the API endpoint.
     *
     * @param string $orderId The unique identifier of the order to be canceled.
     * @return bool True if the cancellation request was successfully sent, false otherwise.
     * @throws GuzzleException
     * @throws JsonException
     */
    public function cancelOrder($orderId)
    {
        $endpoint = 'cancel_order/' . $orderId . '/';
        $response = $this->makeRequest('POST', $endpoint, [
            'form_params' => $this->buildRequest([], $endpoint),
        ]);

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

        return $response;
    }

    /**
     * Retrieves a list of turn-in records for the given order IDs.
     *
     * @param array $orderIds A list of order IDs to fetch turn-in records for.
     * @return TurnIn[] An array of TurnIn objects representing the turn-in records.
     * @throws GuzzleException
     * @throws JsonException
     * @throws ResponseException
     * @throws UnresponsivenessException
     */
    public function getTurnInList(array $orderIds)
    {
        $endpoint = 'turn_in/';
        $response = $this->makeRequest(
            'POST',
            $endpoint . '/',
            array(
                'form_params' => $this->buildRequest( array( 'order_ids' => $orderIds ), $endpoint ),
            )
        );

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

        $response_obj = Json::mapArrayToObject($response, TurnIn::class);

        return json_encode( $response_obj ) === '{}' ? $response : $response_obj;
    }

    /**
     * Retrieves a list of contacts from the API.
     *
     * This method is not supported and will throw an UnsupportedEndpointException.
     *
     * @throws UnsupportedEndpointException Indicates that the contact_list endpoint is not supported.
     */
    public function getContactList()
    {
        throw new UnsupportedEndpointException('contact_list');
    }

    /**
     * Parses the raw API response into a structured array and validates its integrity.
     *
     * @param string $response The raw JSON response string from the API.
     * @return array The parsed response data, containing the actual response content.
     * @throws UnresponsivenessException If the response structure is invalid or missing required fields.
     * @throws ResponseException If the response status code is not 200.
     * @throws JsonException If the JSON decoding fails.
     */
    private function parseResponse(string $response): array
    {
        $decodedResponse = json_decode($response, true, 10, JSON_THROW_ON_ERROR);

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

            throw new UnresponsivenessException();
        }

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

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


        return $decodedResponse['response'];

    }

    /**
     * Builds request array for Apaczka
     *
     * @param $data
     * @param $route
     * @return array
     * @throws JsonException
     */
    private function buildRequest($data, $route): array
    {

	    /*if ( function_exists( 'wc_get_logger' ) ) {
		    \wc_get_logger()->debug( 'buildRequest in WRAPPER for order ', array( 'source' => 'apaczka-wrapper-build-request' ) );
		    \wc_get_logger()->debug( print_r( $data, true ), array( 'source' => 'apaczka-wrapper-build-request' ) );
	    }*/

        $data = json_encode($data, JSON_THROW_ON_ERROR);
        $expires = strtotime(self::EXPIRES);

        $return = array(
            'app_id' => $this->config['app_id'],
            'request' => $data,
            'expires' => $expires,
            'signature' => $this->getSignature(
                $this->stringToSign(
                    $this->config['app_id'],
                    $route,
                    $data,
                    $expires
                ),
                $this->config['app_secret']
            ),
        );

	    /*if ( function_exists( 'wc_get_logger' ) ) {
		    \wc_get_logger()->debug( 'buildRequest in WRAPPER: $return', array( 'source' => 'apaczka-wrapper-build-request' ) );
		    \wc_get_logger()->debug( print_r( $return, true ), array( 'source' => 'apaczka-wrapper-build-request' ) );
	    }*/

	    return $return;


    }


    public function apaczka_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',
                'APACZKA: for order #: ' . $order_id,
                array(
                    'source'            => 'alsendo-connect-order-' . $order_id,
                    'order_id'          => $order_id,
                    'debug_place'  => $debug_place,
                    'data' => $data,
                )
            );
        }
    }
}