<?php

/**
 * 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.
 */

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\CalendarBundle\RemoteCalendar\Connector\MSGraph\RemoteToLocalSync;

use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Entity\Invite;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\RemoteEventConverter;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class CalendarSyncer
{
    public function __construct(private readonly LoggerInterface $logger, private readonly HttpClientInterface $machineHttpClient, private readonly UserRepositoryInterface $userRepository) {}

    public function handleCalendarSync(Calendar $calendar, array $notification, User $user): void
    {
        match ($notification['changeType']) {
            'deleted' => $this->handleDeleteCalendar($calendar, $notification, $user),
            'updated' => $this->handleUpdateCalendar($calendar, $notification, $user),
            default => throw new \RuntimeException('this change type is not supported: '.$notification['changeType']),
        };
    }

    private function handleDeleteCalendar(Calendar $calendar, array $notification, User $user): void
    {
        $calendar
            ->setStatus(Calendar::STATUS_CANCELED)
            ->setCalendarRange(null);
        $calendar->preventEnqueueChanges = true;
    }

    private function handleUpdateCalendar(Calendar $calendar, array $notification, User $user): void
    {
        try {
            $new = $this->machineHttpClient->request(
                'GET',
                $notification['resource']
            )->toArray();
        } catch (ClientExceptionInterface $clientException) {
            $this->logger->warning(self::class.' could not retrieve event from ms graph. Already deleted ?', [
                'calendarId' => $calendar->getId(),
                'remoteEventId' => $notification['resource'],
            ]);

            throw $clientException;
        }

        if (false === $new['isOrganizer']) {
            return;
        }

        $lastModified = RemoteEventConverter::convertStringDateWithTimezone(
            $new['lastModifiedDateTime']
        );

        if ($calendar->getRemoteAttributes()['lastModifiedDateTime'] === $lastModified->getTimestamp()) {
            $this->logger->info(self::class.' change key is equals. Source is probably a local update', [
                'calendarRangeId' => $calendar->getId(),
                'remoteEventId' => $notification['resource'],
            ]);

            return;
        }

        $this->syncAttendees($calendar, $new['attendees']);

        $startDate = RemoteEventConverter::convertStringDateWithoutTimezone($new['start']['dateTime']);
        $endDate = RemoteEventConverter::convertStringDateWithoutTimezone($new['end']['dateTime']);

        if ($startDate->getTimestamp() !== $calendar->getStartDate()->getTimestamp()) {
            $calendar->setStartDate($startDate);
        }

        if ($endDate->getTimestamp() !== $calendar->getEndDate()->getTimestamp()) {
            $calendar->setEndDate($endDate);
        }

        $calendar
            ->addRemoteAttributes([
                'lastModifiedDateTime' => $lastModified->getTimestamp(),
                'changeKey' => $new['changeKey'],
            ])
            ->preventEnqueueChanges = true;
    }

    private function syncAttendees(Calendar $calendar, array $attendees): void
    {
        $emails = [];

        foreach ($attendees as $attendee) {
            $status = $attendee['status']['response'];

            if ('organizer' === $status) {
                continue;
            }

            $email = $attendee['emailAddress']['address'];
            $emails[] = strtolower((string) $email);
            $user = $this->userRepository->findOneByUsernameOrEmail($email);

            if (null === $user) {
                continue;
            }

            if (!$calendar->isInvited($user)) {
                $calendar->addUser($user);
            }

            $invite = $calendar->getInviteForUser($user);

            match ($status) {
                'none', 'notResponded' => $invite->setStatus(Invite::PENDING),
                'tentativelyAccepted' => $invite->setStatus(Invite::TENTATIVELY_ACCEPTED),
                'accepted' => $invite->setStatus(Invite::ACCEPTED),
                'declined' => $invite->setStatus(Invite::DECLINED),
                default => throw new \LogicException('should not happens, not implemented: '.$status),
            };
        }

        foreach ($calendar->getUsers() as $user) {
            if (!\in_array(strtolower($user->getEmailCanonical()), $emails, true)) {
                $calendar->removeUser($user);
            }
        }
    }
}
