<?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\Controller\Admin;

use InPost\International\Api\Exception\ApiClientExceptionInterface;
use InPost\International\Api\Exception\ApiExceptionInterface;
use InPost\International\Api\Exception\ApiProblemException;
use InPost\International\Api\Exception\PayerNotFoundException;
use InPost\International\Api\Exception\ValidationException;
use InPost\International\Api\Pickup\PickupsApiClientInterface;
use InPost\International\Controller\AbstractController;
use InPost\International\Http\Exception\HttpExceptionInterface;
use InPost\International\PickupOrder\Exception\NoShipmentWithoutPickupOrderFoundException;
use InPost\International\PickupOrder\Exception\PickupOrderException;
use InPost\International\PickupOrder\Form\CutoffTimeRequestType;
use InPost\International\PickupOrder\Form\PickupOrderType;
use InPost\International\PickupOrder\Grid\PickupOrderFilters;
use InPost\International\PickupOrder\PickupOrderCommandFactory;
use PrestaShop\PrestaShop\Core\Grid\Definition\Factory\FilterableGridDefinitionFactoryInterface;
use PrestaShop\PrestaShop\Core\Grid\GridFactoryInterface;
use PrestaShop\PrestaShop\Core\Grid\Presenter\GridPresenterInterface;
use PrestaShopBundle\Security\Annotation\AdminSecurity;
use PrestaShopBundle\Service\Grid\ResponseBuilder;
use Psr\Clock\ClockInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;

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

/**
 * @Route("/pickup-orders", name="admin_inpost_intl_pickup_orders_", defaults={"_legacy_controller"=PickupOrderController::TAB_NAME})
 */
final class PickupOrderController extends AbstractController
{
    public const TAB_NAME = 'AdminInPostInternationalPickupOrders';

    /**
     * @Route(name="index", methods={"GET"})
     *
     * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))")
     */
    public function index(PickupOrderFilters $filters, GridFactoryInterface $gridFactory, GridPresenterInterface $gridPresenter): Response
    {
        $grid = $gridFactory->getGrid($filters);

        return $this->render('@Modules/inpostinternational/views/templates/admin/pickup_order/index.html.twig', [
            'grid' => $gridPresenter->present($grid),
            'layoutTitle' => $this->trans('Pickup orders', [], 'Modules.Inpostinternational.Pickup'),
        ]);
    }

    /**
     * @Route(name="filter", methods={"POST"})
     *
     * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))")
     */
    public function filter(Request $request, FilterableGridDefinitionFactoryInterface $gridDefinitionFactory, ResponseBuilder $responseBuilder): Response
    {
        return $responseBuilder
            ->buildSearchResponse(
                $gridDefinitionFactory,
                $request,
                $gridDefinitionFactory->getFilterId(),
                'admin_inpost_intl_pickup_orders_index'
            )
            ->setStatusCode(Response::HTTP_SEE_OTHER);
    }

    /**
     * @Route(path="/cutoff-time", name="check_cutoff_time", methods={"GET"})
     */
    public function checkCutoffTime(Request $request, PickupsApiClientInterface $client, ClockInterface $clock): Response
    {
        $form = $this->createForm(CutoffTimeRequestType::class, null, ['method' => 'GET']);
        $form->handleRequest($request);

        if (!$form->isSubmitted()) {
            throw new BadRequestHttpException('Form was not submitted.');
        }

        if (!$form->isValid()) {
            throw new BadRequestHttpException('Form is not valid.');
        }

        try {
            $time = $client->getCutoffPickupTime($form->getData())->getCutoffTime();
            if (false !== $timezone = $clock->now()->getTimezone()) {
                $time = $time->setTimezone($timezone);
            }

            $this->addFlash('info', $this->trans('Cutoff pickup time for the selected postal code is {time}.', [
                '{time}' => $time->format('H:i'),
            ], 'Modules.Inpostinternational.Pickup'));
        } catch (ValidationException $e) {
            foreach ($e->getErrors() as $field => $error) {
                $this->addFlash('error', sprintf('%s: %s', $field, $error));
            }
        } catch (ApiClientExceptionInterface $e) {
            $this->addFlash('error', $this->trans('Could not retrieve cutoff pickup time from the API.', [], 'Modules.Inpostinternational.Pickup'));
        }

        return $this->redirectToRoute('admin_inpost_intl_shipments_index', [], Response::HTTP_SEE_OTHER);
    }

    /**
     * @Route("/create", name="create", methods={"GET", "POST"})
     *
     * @AdminSecurity("is_granted('create', request.get('_legacy_controller'))", redirectRoute="admin_inpost_intl_shipments_index")
     */
    public function create(Request $request, PickupOrderCommandFactory $commandFactory): Response
    {
        $command = $commandFactory->createCommand();

        $form = $this->createForm(PickupOrderType::class, $command, [
            'action' => $this->generateUrl('admin_inpost_intl_pickup_orders_create'),
        ]);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            try {
                $this->handle($form->getData());
                $this->addFlash('success', $this->trans('Successful creation', [], 'Admin.Notifications.Success'));

                return new Response(null, Response::HTTP_CREATED);
            } catch (ValidationException $e) {
                $this->mapApiValidationErrors($form, $e);
            } catch (\Throwable $e) {
                $message = $this->getErrorMessageForException($e);

                return new JsonResponse([
                    'message' => $message,
                ], $this->getStatusCodeForException($e));
            }
        }

        return $this->render('@Modules/inpostinternational/views/templates/admin/pickup_order/_form.html.twig', [
            'form' => $form->createView(),
        ], $this->createResponseForForm($form));
    }

    private function mapApiValidationErrors(FormInterface $form, ValidationException $e): void
    {
        foreach ($e->getErrors() as $path => $message) {
            $this->addFormError($form, $path, $message);
        }
    }

    private function addFormError(FormInterface $form, string $path, string $message): void
    {
        if (str_starts_with($path, 'validation')) {
            $form->addError(new FormError($message));

            return;
        }

        $originalPath = $path;

        while (str_contains($path, '.')) {
            [$name, $path] = explode('.', $path, 2);

            if ($form->has($name)) {
                $this->addFormError($form->get($name), $path, $message);

                return;
            }
        }

        if ($form->has($path)) {
            $form->get($path)->addError(new FormError($message));
        } else {
            $message = sprintf('%s: %s', $originalPath, $message);
            $form->addError(new FormError($message));
        }
    }

    private function getErrorMessageForException(\Throwable $e): string
    {
        switch (true) {
            case $e instanceof NoShipmentWithoutPickupOrderFoundException:
                return $this->trans('No shipment without pickup order was found among the selected items.', [], 'Modules.Inpostinternational.Errors');
            case $e instanceof PayerNotFoundException:
                $messages = $e->getMessages();

                return $messages[0]['message'] ?? $e->getProblem()->getDetail() ?? $e->getMessage();
            case $e instanceof ApiProblemException:
                return $e->getProblem()->getDetail() ?? $e->getMessage();
            case $e instanceof ApiExceptionInterface:
                return $this->trans('InPost API error: {message}', [
                    '{message}' => $e->getMessage(),
                ], 'Modules.Inpostinternational.Errors');
            case $e instanceof HttpExceptionInterface:
                return $this->trans('Unsuccessful InPost API response. Status code: {status}.', [
                    '{status}' => $e->getResponse()->getStatusCode(),
                ], 'Modules.Inpostinternational.Errors');
            case $e instanceof NetworkExceptionInterface:
                return $this->trans('Could not connect to the InPost API.', [], 'Modules.Inpostinternational.Errors');
            case $e instanceof PickupOrderException:
            case $e instanceof ApiClientExceptionInterface:
                return $e->getMessage();
            default:
                $this->getLogger()->critical('An error occurred while creating pickup order: {exception}', [
                    'exception' => $e,
                ]);

                if ($this->debug) {
                    throw $e;
                }

                return $this->trans('An unexpected error occurred.', [], 'Modules.Inpostinternational.Errors');
        }
    }

    private function getStatusCodeForException(\Throwable $e): int
    {
        switch (true) {
            case $e instanceof PickupOrderException:
                return Response::HTTP_CONFLICT;
            case $e instanceof ApiClientExceptionInterface:
                return Response::HTTP_BAD_GATEWAY;
            default:
                return Response::HTTP_INTERNAL_SERVER_ERROR;
        }
    }
}
