<?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\PickupAddress\MessageHandler;

use Doctrine\ORM\EntityManagerInterface;
use InPost\International\Entity\Address;
use InPost\International\Entity\ContactPerson;
use InPost\International\Entity\PickupAddress;
use InPost\International\PickupAddress\Exception\PickupAddressNotFoundException;
use InPost\International\PickupAddress\Message\EditPickupAddressCommand;
use InPost\International\PickupAddress\PickupAddressMapper;
use InPost\International\PickupAddress\PickupAddressRepositoryInterface;

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

final class EditPickupAddressHandler
{
    /**
     * @var PickupAddressRepositoryInterface
     */
    private $repository;

    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    /**
     * @var PickupAddressMapper
     */
    private $mapper;

    public function __construct(PickupAddressRepositoryInterface $repository, EntityManagerInterface $entityManager, PickupAddressMapper $mapper)
    {
        $this->repository = $repository;
        $this->entityManager = $entityManager;
        $this->mapper = $mapper;
    }

    public function __invoke(EditPickupAddressCommand $command): void
    {
        $addressId = $command->getAddressId();

        if (null === $pickupAddress = $this->repository->find($addressId)) {
            throw new PickupAddressNotFoundException('Pickup address does not exist.');
        }

        if ($pickupAddress->isDeleted()) {
            throw new PickupAddressNotFoundException('Deleted pickup address cannot be edited.');
        }

        if (false === $command->isDefault() && $pickupAddress->isDefault()) {
            throw new PickupAddressNotFoundException('Cannot unset default pickup address.');
        }

        $newContactPerson = $this->getNewContactPerson($pickupAddress, $command);
        $newAddress = $this->getNewAddress($pickupAddress, $command);

        $isModified = null !== $newContactPerson || null !== $newAddress;

        if ($isModified && $pickupAddress->wasUsed()) {
            $pickupAddress = $this->copyAddress($pickupAddress);
        }

        if (null !== $newContactPerson) {
            $pickupAddress->setContactPerson($newContactPerson);
        }

        if (null !== $newAddress) {
            $pickupAddress->setAddress($newAddress);
        }

        if (null !== $name = $command->getName()) {
            $pickupAddress->setName($name);
        }

        if ($command->isDefault()) {
            $this->markAsDefaultAddress($pickupAddress);
        }

        $this->entityManager->flush();
    }

    public function handle(EditPickupAddressCommand $command): void
    {
        ($this)($command);
    }

    private function getNewContactPerson(PickupAddress $pickupAddress, EditPickupAddressCommand $command): ?ContactPerson
    {
        if (null === $command->getContactPerson()) {
            return null;
        }

        $contactPerson = $this->mapper->mapDtoToContactPerson($command->getContactPerson());

        if ($contactPerson == $pickupAddress->getContactPerson()) {
            return null;
        }

        return $contactPerson;
    }

    private function getNewAddress(PickupAddress $pickupAddress, EditPickupAddressCommand $command): ?Address
    {
        if (null === $command->getAddress()) {
            return null;
        }

        $address = $this->mapper->mapDtoToAddress($command->getAddress());

        if ($address == $pickupAddress->getAddress()) {
            return null;
        }

        return $address;
    }

    private function copyAddress(PickupAddress $previousAddress): PickupAddress
    {
        $pickupAddress = clone $previousAddress;

        if ($previousAddress->isDefault()) {
            $pickupAddress->setDefault(true);
        }

        $previousAddress->delete();

        return $pickupAddress;
    }

    private function markAsDefaultAddress(PickupAddress $pickupAddress): void
    {
        if ($pickupAddress->isDefault()) {
            return;
        }

        if (null !== $previousDefault = $this->repository->getDefault()) {
            $previousDefault->setDefault(false);
        }

        $pickupAddress->setDefault(true);
    }
}
