<?php
/**
 * Copyright since 2025 InPost S.A.
 *
 * For the full license information, please view the LICENSE file bundled with the module.
 *
 * @author InPost S.A.
 * @copyright since 2025 InPost S.A.
 * @license MIT
 */

declare(strict_types=1);

namespace InPost\International\Checkout\Module;

use InPost\International\Asset\Assets;
use InPost\International\Checkout\AbstractCheckout;
use InPost\International\Checkout\Event\CarrierProcessEvent;
use InPost\International\Checkout\Exception\AbortCheckoutException;
use InPost\International\Checkout\Exception\CheckoutException;
use InPost\International\Checkout\Exception\FormValidationException;
use InPost\International\Checkout\Exception\InvalidPhoneNumberException;
use InPost\International\Checkout\ModuleCheckoutInterface;
use InPost\International\Checkout\Phone\PhoneNumberValidatorInterface;
use InPost\International\Delivery\Util\CarrierFinder;
use InPost\International\PrestaShop\Module\Util\ControllerChecker;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

if (!defined('_PS_VERSION_')) {
    exit;
}

final class SuperCheckout extends AbstractCheckout implements ModuleCheckoutInterface
{
    private const MODULE_NAME = 'supercheckout';
    private const CREATE_ORDER_PARAM = 'supercheckoutPlaceOrder';

    public static function getModuleName(): string
    {
        return self::MODULE_NAME;
    }

    public static function supports(\FrontControllerCore $controller, Request $request): bool
    {
        return ControllerChecker::isModuleController($controller, self::MODULE_NAME, [
            'supercheckout',
            'order',
        ]);
    }

    public static function getSubscribedServices(): array
    {
        return parent::getSubscribedServices() + [
            CarrierFinder::class,
        ];
    }

    public static function getSubscribedEvents(): array
    {
        return [
            CarrierProcessEvent::NAME => 'onCarrierProcess',
        ];
    }

    /**
     * Past versions of the SuperCheckout module used to unset {@see $_POST} superglobal and did not process carrier data
     * when placing the order unless a {@see \Tools::getIsset()} condition was met for the key "delivery_option".
     *
     * @see \SupercheckoutCore::confirmOrder()
     */
    public static function fixRequestParameters(Request $request): void
    {
        if (!$request->request->has(self::CREATE_ORDER_PARAM)) {
            return;
        }

        $params = $request->request->all();

        if (null === $deliveryOption = $params['delivery_option'] ?? null) {
            return;
        }

        $_GET['delivery_option'] = $deliveryOption;
    }

    public function onCarrierProcess(CarrierProcessEvent $event): void
    {
        $request = $event->getRequest();

        if (!$request->request->has(self::CREATE_ORDER_PARAM)) {
            return;
        }

        $cart = $event->getCart();
        $deliveryOption = $cart->getDeliveryOption();

        if ([] === $this->get(CarrierFinder::class)->findByDeliveryOption($deliveryOption)) {
            return;
        }

        try {
            $this->processDeliveryForm($cart, $request);
        } catch (FormValidationException $e) {
            foreach ($e->getErrors() as $error) {
                $formName = self::getFormName($error);

                self::abortCheckout($error->getMessage(), sprintf('checkout_option.%s', $formName));
            }
        } catch (CheckoutException $e) {
            self::abortCheckout($e->getMessage());
        }

        $address = $this->getDeliveryAddress($cart, $request->request->all());

        try {
            $this->get(PhoneNumberValidatorInterface::class)->validate($address);
        } catch (InvalidPhoneNumberException $e) {
            $field = '' !== trim((string) $address->phone_mobile) ? 'phone_mobile' : 'phone';

            self::abortCheckout($e->getMessage(), sprintf('shipping_address.%s', $field));
        }
    }

    protected function addCheckoutAssets(Assets $assets): void
    {
        $assets->addJavaScript('js/front/super-checkout.js');
    }

    /**
     * @param array<string, mixed> $request request parameters
     */
    protected function getDeliveryAddress(\Cart $cart, array $request = []): \Address
    {
        if (null !== $address = $this->getAddressFromRequestData($request)) {
            return $address;
        }

        return parent::getDeliveryAddress($cart);
    }

    private static function getFormName(FormError $error): ?string
    {
        if (null === $form = $error->getOrigin()) {
            return null;
        }

        $name = $form->getName();
        while (null !== $form = $form->getParent()) {
            $name = sprintf('%s[%s]', $form->getName(), $name);
        }

        return $name;
    }

    /**
     * @return never-returns
     */
    private static function abortCheckout(string $error, string $path = 'checkout_option.shipping_method_error'): void
    {
        $parts = explode('.', $path, 2);

        $data = [
            'error' => [
                $parts[0] => [
                    [
                        'key' => $parts[1],
                        'error' => $error,
                    ],
                ],
            ],
        ];

        // successful status code is required for the response to be handled by JS
        throw AbortCheckoutException::json($data, Response::HTTP_OK);
    }

    /**
     * @param array<string, mixed> $request request parameters
     */
    private function getAddressFromRequestData(array $request): ?\Address
    {
        if (isset($request['shipping_address_value']) && !$request['shipping_address_value']) {
            return null;
        }

        if (!isset($request['shipping_address']) || !is_array($request['shipping_address'])) {
            return null;
        }

        $address = new \Address();

        foreach ($request['shipping_address'] as $name => $value) {
            if (!property_exists($address, $name)) {
                continue;
            }

            $address->{$name} = $value;
        }

        return $address;
    }
}
