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

use Chill\MainBundle\Entity\User;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
 * Write metadata to user, which allow to find his default calendar.
 */
class MapCalendarToUser
{
    final public const EXPIRATION_SUBSCRIPTION_EVENT = 'subscription_events_expiration';

    final public const ID_SUBSCRIPTION_EVENT = 'subscription_events_id';

    final public const METADATA_KEY = 'msgraph';

    final public const SECRET_SUBSCRIPTION_EVENT = 'subscription_events_secret';

    public function __construct(private readonly HttpClientInterface $machineHttpClient, private readonly LoggerInterface $logger) {}

    public function getActiveSubscriptionId(User $user): string
    {
        if (!\array_key_exists(self::METADATA_KEY, $user->getAttributes())) {
            throw new \LogicException('do not contains msgraph metadata');
        }

        if (!\array_key_exists(self::ID_SUBSCRIPTION_EVENT, $user->getAttributes()[self::METADATA_KEY])) {
            throw new \LogicException('do not contains metadata for subscription id');
        }

        return $user->getAttributes()[self::METADATA_KEY][self::ID_SUBSCRIPTION_EVENT];
    }

    public function getCalendarId(User $user): ?string
    {
        if (null === $msKey = ($user->getAttributes()[self::METADATA_KEY] ?? null)) {
            return null;
        }

        return $msKey['defaultCalendarId'] ?? null;
    }

    public function getDefaultUserCalendar(string $idOrUserPrincipalName): ?array
    {
        try {
            $value = $this->machineHttpClient->request('GET', "users/{$idOrUserPrincipalName}/calendars", [
                'query' => ['$filter' => 'isDefaultCalendar eq true'],
            ])->toArray()['value'];
        } catch (ClientExceptionInterface $e) {
            $this->logger->error('[MapCalendarToUser] Error while listing calendars for a user', [
                'http_status_code' => $e->getResponse()->getStatusCode(),
                'id_user' => $idOrUserPrincipalName,
            ]);

            return null;
        }

        return $value[0] ?? null;
    }

    public function getSubscriptionSecret(User $user): string
    {
        if (!\array_key_exists(self::METADATA_KEY, $user->getAttributes())) {
            throw new \LogicException('do not contains msgraph metadata');
        }

        if (!\array_key_exists(self::SECRET_SUBSCRIPTION_EVENT, $user->getAttributes()[self::METADATA_KEY])) {
            throw new \LogicException('do not contains secret in msgraph');
        }

        return $user->getAttributes()[self::METADATA_KEY][self::SECRET_SUBSCRIPTION_EVENT];
    }

    public function getUserByEmail(string $email): ?array
    {
        $value = $this->machineHttpClient->request('GET', 'users', [
            'query' => ['$filter' => "mail eq '{$email}'"],
        ])->toArray()['value'];

        return $value[0] ?? null;
    }

    public function getUserId(User $user): ?string
    {
        if (null === $msKey = ($user->getAttributes()[self::METADATA_KEY] ?? null)) {
            return null;
        }

        return $msKey['id'] ?? null;
    }

    public function hasActiveSubscription(User $user): bool
    {
        if (!\array_key_exists(self::METADATA_KEY, $user->getAttributes())) {
            return false;
        }

        if (!\array_key_exists(self::EXPIRATION_SUBSCRIPTION_EVENT, $user->getAttributes()[self::METADATA_KEY])) {
            return false;
        }

        return $user->getAttributes()[self::METADATA_KEY][self::EXPIRATION_SUBSCRIPTION_EVENT]
            >= (new \DateTimeImmutable('now'))->getTimestamp();
    }

    public function hasSubscriptionSecret(User $user): bool
    {
        if (!\array_key_exists(self::METADATA_KEY, $user->getAttributes())) {
            return false;
        }

        return \array_key_exists(self::SECRET_SUBSCRIPTION_EVENT, $user->getAttributes()[self::METADATA_KEY]);
    }

    public function hasUserId(User $user): bool
    {
        if (null === $user->getEmail() || '' === $user->getEmail()) {
            return false;
        }

        if (!\array_key_exists(self::METADATA_KEY, $user->getAttributes())) {
            return false;
        }

        return \array_key_exists('id', $user->getAttributes()[self::METADATA_KEY]);
    }

    public function writeMetadata(User $user): User
    {
        if (null === $user->getEmail() || '' === $user->getEmail()) {
            return $user;
        }

        if (null === $userData = $this->getUserByEmail($user->getEmailCanonical())) {
            $this->logger->warning('[MapCalendarToUser] could not find user on msgraph', ['userId' => $user->getId(), 'email' => $user->getEmailCanonical()]);

            return $this->writeNullData($user);
        }

        if (null === $defaultCalendar = $this->getDefaultUserCalendar($userData['id'])) {
            $this->logger->warning('[MapCalendarToUser] could not find default calendar', ['userId' => $user->getId(), 'email' => $user->getEmailCanonical()]);

            return $this->writeNullData($user);
        }

        return $user->setAttributes([self::METADATA_KEY => [
            'id' => $userData['id'],
            'userPrincipalName' => $userData['userPrincipalName'],
            'defaultCalendarId' => $defaultCalendar['id'],
        ]]);
    }

    /**
     * @param int $expiration the expiration time as unix timestamp
     */
    public function writeSubscriptionMetadata(
        User $user,
        int $expiration,
        ?string $id = null,
        ?string $secret = null,
    ): void {
        $user->setAttributeByDomain(self::METADATA_KEY, self::EXPIRATION_SUBSCRIPTION_EVENT, $expiration);

        if (null !== $id) {
            $user->setAttributeByDomain(self::METADATA_KEY, self::ID_SUBSCRIPTION_EVENT, $id);
        }

        if (null !== $secret) {
            $user->setAttributeByDomain(self::METADATA_KEY, self::SECRET_SUBSCRIPTION_EVENT, $secret);
        }
    }

    private function writeNullData(User $user): User
    {
        return $user->unsetAttribute(self::METADATA_KEY);
    }
}
