<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\ZimbraBundle\Calendar\Connector;

use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Entity\CalendarRange;
use Chill\CalendarBundle\Entity\Invite;
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
use Chill\MainBundle\Entity\User;
use Chill\ZimbraBundle\Calendar\Connector\ZimbraConnector\CreateEvent;
use Chill\ZimbraBundle\Calendar\Connector\ZimbraConnector\DeleteEvent;
use Chill\ZimbraBundle\Calendar\Connector\ZimbraConnector\UpdateEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;

final readonly class ZimbraConnector implements RemoteCalendarConnectorInterface
{
    private const LOG_PREFIX = '[ZimbraConnector] ';

    public function __construct(
        private CreateEvent $createEvent,
        private UpdateEvent $updateEvent,
        private DeleteEvent $deleteEvent,
        private LoggerInterface $logger,
    ) {}

    public function countEventsForUser(User $user, \DateTimeImmutable $startDate, \DateTimeImmutable $endDate): int
    {
        return 0;
    }

    public function getMakeReadyResponse(string $returnPath): Response
    {
        throw new \BadMethodCallException('Zimbra connector is always ready');
    }

    public function isReady(): bool
    {
        return true;
    }

    public function listEventsForUser(User $user, \DateTimeImmutable $startDate, \DateTimeImmutable $endDate, ?int $offset = 0, ?int $limit = 50): array
    {
        return [];
    }

    public function removeCalendar(string $remoteId, array $remoteAttributes, User $user, ?CalendarRange $associatedCalendarRange = null): void
    {
        if ('' === $remoteId) {
            return;
        }

        ($this->deleteEvent)($user, $remoteId);

        if (null !== $associatedCalendarRange) {
            $this->logger->info(self::LOG_PREFIX.'Ask to re-create the previous calendar range', ['previous_calendar_range_id' => $associatedCalendarRange->getId()]);
            $this->createCalendarRange($associatedCalendarRange);
        }
    }

    public function removeCalendarRange(string $remoteId, array $remoteAttributes, User $user): void
    {
        if ('' === $remoteId) {
            return;
        }

        ($this->deleteEvent)($user, $remoteId);
    }

    public function syncCalendar(Calendar $calendar, string $action, ?CalendarRange $previousCalendarRange, ?User $previousMainUser, ?array $oldInvites, ?array $newInvites): void
    {
        if (null !== $previousMainUser && $previousMainUser !== $calendar->getMainUser()) {
            $this->removeCalendar($calendar->getRemoteId(), [], $previousMainUser);
            $calendar->setRemoteId('');
        }

        if (!$calendar->hasRemoteId()) {
            $calItemId = ($this->createEvent)($calendar);
            $this->logger->info(self::LOG_PREFIX.'Calendar synced with Zimbra', ['calendar_id' => $calendar->getId(), 'action' => $action, 'calItemId' => $calItemId]);

            $calendar->setRemoteId($calItemId);
        } else {
            ($this->updateEvent)($calendar);
            $this->logger->info(self::LOG_PREFIX.'Calendar updated against zimbra', ['old_cal_remote_id' => $calendar->getRemoteId(), 'calendar_id' => $calendar->getId()]);
        }

        if (null !== $calendar->getCalendarRange()) {
            $range = $calendar->getCalendarRange();
            $this->removeCalendarRange($range->getRemoteId(), [], $range->getUser());
            $range->setRemoteId('');
        }

        if (null !== $previousCalendarRange) {
            $this->syncCalendarRange($previousCalendarRange);
        }

        foreach ($calendar->getInvites() as $invite) {
            $this->syncInvite($invite);
        }
    }

    public function syncCalendarRange(CalendarRange $calendarRange): void
    {
        if (!$calendarRange->hasRemoteId()) {
            $this->createCalendarRange($calendarRange);
        } else {
            ($this->updateEvent)($calendarRange);
            $this->logger->info(self::LOG_PREFIX.'Calendar range updated against zimbra', ['old_cal_remote_id' => $calendarRange->getRemoteId(), 'calendar_range_id' => $calendarRange->getId()]);
        }
    }

    public function syncInvite(Invite $invite): void
    {
        if (Invite::ACCEPTED === $invite->getStatus()) {
            if ($invite->hasRemoteId()) {
                ($this->updateEvent)($invite);
                $this->logger->info(self::LOG_PREFIX.'Invite range updated against zimbra', ['invite_id' => $invite->getId(), 'invite_remote_id', $invite->getRemoteId()]);
            } else {
                $remoteId = ($this->createEvent)($invite);
                $invite->setRemoteId($remoteId);
                $this->logger->info(self::LOG_PREFIX.'Invite range updated against zimbra', ['invite_id' => $invite->getId(), 'invite_remote_id', $invite->getRemoteId()]);
            }
        } elseif ($invite->hasRemoteId()) {
            // case when the invite has been accepted in the past, and synchronized
            ($this->deleteEvent)($invite->getUser(), $invite->getRemoteId());
            $this->logger->info(self::LOG_PREFIX.'Invite range removed in zimbra', ['invite_id' => $invite->getId(), 'invite_remote_id', $invite->getRemoteId()]);
            $invite->setRemoteId('');
        }
    }

    private function createCalendarRange(CalendarRange $calendarRange): void
    {
        $calItemId = ($this->createEvent)($calendarRange);
        $this->logger->info(self::LOG_PREFIX.'Calendar range created with Zimbra', ['calendar_range_id' => $calendarRange->getId(), 'calItemId' => $calItemId]);

        $calendarRange->setRemoteId($calItemId);
    }
}
