<?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\Command;

use Chill\CalendarBundle\Exception\UserAbsenceSyncException;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\EventsOnUserSubscriptionCreator;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MapCalendarToUser;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

final class MapAndSubscribeUserCalendarCommand extends Command
{
    protected static $defaultDescription = 'MSGraph: collect user metadata and create subscription on events for users, and sync the user absence-presence';

    public function __construct(
        private readonly EntityManagerInterface $em,
        private readonly EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator,
        private readonly LoggerInterface $logger,
        private readonly MapCalendarToUser $mapCalendarToUser,
        private readonly UserRepositoryInterface $userRepository,
        private readonly MSUserAbsenceSync $userAbsenceSync,
    ) {
        parent::__construct('chill:calendar:msgraph-user-map-subscribe');
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->logger->info(self::class.' execute command');

        $limit = 50;
        $offset = 0;
        $expiration = (new \DateTimeImmutable('now'))->add(new \DateInterval($input->getOption('subscription-duration')));
        $users = $this->userRepository->findAllAsArray('fr');
        $created = 0;
        $renewed = 0;

        $this->logger->info(self::class.' start user to get - renew', [
            'expiration' => $expiration->format(\DateTimeImmutable::ATOM),
        ]);

        foreach ($users as $u) {
            ++$offset;

            if (false === $u['enabled']) {
                continue;
            }

            $user = $this->userRepository->find($u['id']);

            if (null === $user) {
                $this->logger->error('could not find user by id', ['uid' => $u['id']]);
                $output->writeln('could not find user by id : '.$u['id']);
                continue;
            }

            if (!$this->mapCalendarToUser->hasUserId($user)) {
                $user = $this->mapCalendarToUser->writeMetadata($user);

                // if user still does not have userid, continue
                if (!$this->mapCalendarToUser->hasUserId($user)) {
                    $this->logger->warning('user does not have a counterpart on ms api', ['userId' => $user->getId(), 'email' => $user->getEmail()]);
                    $output->writeln(sprintf('giving up for user with email %s and id %s', $user->getEmail(), $user->getId()));

                    continue;
                }
            }

            // sync user absence
            try {
                $this->userAbsenceSync->syncUserAbsence($user);
            } catch (UserAbsenceSyncException $e) {
                $this->logger->error('could not sync user absence', ['userId' => $user->getId(), 'email' => $user->getEmail(), 'exception' => $e->getTraceAsString(), 'message' => $e->getMessage()]);
                $output->writeln(sprintf('Could not sync user absence: id: %s and email: %s', $user->getId(), $user->getEmail()));
            }

            // we first try to renew an existing subscription, if any.
            // if not, or if it fails, we try to create a new one
            if ($this->mapCalendarToUser->hasActiveSubscription($user)) {
                $this->logger->debug(self::class.' renew a subscription for', [
                    'userId' => $user->getId(),
                    'username' => $user->getUsernameCanonical(),
                ]);

                ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
                    = $this->eventsOnUserSubscriptionCreator->renewSubscriptionForUser($user, $expiration);
                $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);

                if (0 !== $expirationTs) {
                    ++$renewed;
                } else {
                    $this->logger->warning(self::class.' could not renew subscription for a user', [
                        'userId' => $user->getId(),
                        'username' => $user->getUsernameCanonical(),
                    ]);
                }
            }

            if (!$this->mapCalendarToUser->hasActiveSubscription($user)) {
                $this->logger->debug(self::class.' create a subscription for', [
                    'userId' => $user->getId(),
                    'username' => $user->getUsernameCanonical(),
                ]);

                ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
                    = $this->eventsOnUserSubscriptionCreator->createSubscriptionForUser($user, $expiration);
                $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);

                if (0 !== $expirationTs) {
                    ++$created;
                } else {
                    $this->logger->warning(self::class.' could not create subscription for a user', [
                        'userId' => $user->getId(),
                        'username' => $user->getUsernameCanonical(),
                    ]);
                }
            }

            if (0 === $offset % $limit) {
                $this->em->flush();
                $this->em->clear();
            }
        }

        $this->em->flush();
        $this->em->clear();

        $this->logger->warning(self::class.' process executed', [
            'created' => $created,
            'renewed' => $renewed,
        ]);

        $output->writeln('users synchronized');

        return Command::SUCCESS;
    }

    protected function configure()
    {
        parent::configure();

        $this
            ->addOption(
                'renew-before-end-interval',
                'r',
                InputOption::VALUE_OPTIONAL,
                'delay before renewing subscription',
                'P1D'
            )
            ->addOption(
                'subscription-duration',
                's',
                InputOption::VALUE_OPTIONAL,
                'duration for the subscription',
                'PT4230M'
            );
    }
}
