<?php

namespace AmeliaBooking\Application\Services\User;

use AmeliaBooking\Application\Commands\CommandResult;
use AmeliaBooking\Application\Common\Exceptions\AccessDeniedException;
use AmeliaBooking\Application\Services\Bookable\AbstractPackageApplicationService;
use AmeliaBooking\Application\Services\Helper\HelperService;
use AmeliaBooking\Domain\Collection\Collection;
use AmeliaBooking\Domain\Common\Exceptions\AuthorizationException;
use AmeliaBooking\Domain\Common\Exceptions\InvalidArgumentException;
use AmeliaBooking\Domain\Entity\Booking\Appointment\Appointment;
use AmeliaBooking\Domain\Entity\Booking\Appointment\CustomerBooking;
use AmeliaBooking\Domain\Entity\Entities;
use AmeliaBooking\Domain\Entity\User\AbstractUser;
use AmeliaBooking\Domain\Entity\User\Customer;
use AmeliaBooking\Domain\Entity\User\Provider;
use AmeliaBooking\Domain\Factory\User\UserFactory;
use AmeliaBooking\Domain\Services\DateTime\DateTimeService;
use AmeliaBooking\Domain\Services\Settings\SettingsService;
use AmeliaBooking\Domain\ValueObjects\Json;
use AmeliaBooking\Domain\ValueObjects\Number\Integer\Id;
use AmeliaBooking\Domain\ValueObjects\Number\Integer\LoginType;
use AmeliaBooking\Domain\ValueObjects\String\BookingStatus;
use AmeliaBooking\Infrastructure\Common\Container;
use AmeliaBooking\Infrastructure\Common\Exceptions\NotFoundException;
use AmeliaBooking\Infrastructure\Common\Exceptions\QueryExecutionException;
use AmeliaBooking\Infrastructure\Repository\Bookable\Service\PackageCustomerServiceRepository;
use AmeliaBooking\Infrastructure\Repository\Bookable\Service\ProviderServiceRepository;
use AmeliaBooking\Infrastructure\Repository\Booking\Appointment\AppointmentRepository;
use AmeliaBooking\Infrastructure\Repository\User\ProviderRepository;
use AmeliaBooking\Infrastructure\Repository\User\UserRepository;
use AmeliaBooking\Infrastructure\Services\Google\AbstractGoogleCalendarService;
use AmeliaBooking\Infrastructure\Services\Google\AbstractGoogleCalendarMiddlewareService;
use AmeliaBooking\Infrastructure\Services\Outlook\AbstractOutlookCalendarMiddlewareService;
use AmeliaBooking\Infrastructure\Services\Outlook\AbstractOutlookCalendarService;
use AmeliaBooking\Infrastructure\WP\HelperService\HelperService as WPHelperService;
use AmeliaBooking\Infrastructure\WP\UserService\CreateWPUser;
use AmeliaBooking\Infrastructure\WP\UserService\UserService;
use AmeliaBooking\Infrastructure\WP\UserRoles\UserRoles;
use AmeliaVendor\Firebase\JWT\Key;
use Exception;
use AmeliaVendor\Firebase\JWT\JWT;
use Slim\Exception\ContainerValueNotFoundException;

/**
 * Class UserApplicationService
 *
 * @package AmeliaBooking\Application\Services\User
 */
class UserApplicationService
{
    private Container $container;

    /**
     * @throws \InvalidArgumentException
     */
    public function __construct(Container $container)
    {
        $this->container = $container;
    }

    /**
     *
     * @param int $userId
     *
     * @return array
     *
     * @throws ContainerValueNotFoundException
     * @throws InvalidArgumentException
     * @throws QueryExecutionException
     * @throws NotFoundException
     */
    public function getAppointmentsCountForUser($userId)
    {
        /** @var UserRepository $userRepository */
        $userRepository = $this->container->get('domain.users.repository');

        /** @var AbstractUser $user */
        $user = $userRepository->getById($userId);

        /** @var AppointmentRepository $appointmentRepo */
        $appointmentRepo = $this->container->get('domain.booking.appointment.repository');

        /** @var PackageCustomerServiceRepository $packageCustomerServiceRepository */
        $packageCustomerServiceRepository = $this->container->get('domain.bookable.packageCustomerService.repository');

        /** @var AbstractPackageApplicationService $packageApplicationService */
        $packageApplicationService = $this->container->get('application.bookable.package');

        /** @var Collection $appointments */
        $appointments = new Collection();

        $packagePurchases = [];

        switch ($user->getType()) {
            case (AbstractUser::USER_ROLE_PROVIDER):
                $appointments = $appointmentRepo->getFiltered(['providerId' => $userId]);

                /** @var Collection $customerAppointments */
                $customerAppointments = $appointmentRepo->getFiltered(['customerId' => $userId]);

                /** @var Appointment $appointment */
                foreach ($customerAppointments->getItems() as $appointment) {
                    if (!$appointments->keyExists($appointment->getId()->getValue())) {
                        $appointments->addItem($appointment, $appointment->getId()->getValue());
                    }
                }

                break;
            case (AbstractUser::USER_ROLE_CUSTOMER):
                $appointments = $appointmentRepo->getFiltered(['customerId' => $userId]);

                /** @var Collection $packageCustomerServices */
                $packageCustomerServices = $packageCustomerServiceRepository->getByCriteria(['customers' => [$userId]]);

                $packagePurchases = $packageApplicationService->getPackageUnusedBookingsCount(
                    $packageCustomerServices,
                    $appointments
                );

                break;
        }

        $now = DateTimeService::getNowDateTimeObject();

        $futureAppointments = 0;

        $pastAppointments = 0;

        /** @var Appointment $appointment */
        foreach ($appointments->getItems() as $appointment) {
            if ($appointment->getBookingStart()->getValue() >= $now) {
                if ($appointment->getStatus()->getValue() === BookingStatus::APPROVED || $appointment->getStatus()->getValue() === BookingStatus::PENDING) {
                    $futureAppointments++;
                }
            } else {
                $pastAppointments++;
            }
        }

        return [
            'futureAppointments'  => $futureAppointments,
            'pastAppointments'    => $pastAppointments,
            'packageAppointments' => sizeof($packagePurchases)
        ];
    }

    /**
     * @param int          $userId
     * @param AbstractUser $user
     * @param string       $type
     * @param string|null  $password
     * @param bool         $sendNewUserNotification
     *
     * @return boolean
     * @throws ContainerValueNotFoundException
     * @throws InvalidArgumentException
     * @throws QueryExecutionException
     */
    public function setWpUserIdForNewUser($userId, $user, $type, $password = null, $sendNewUserNotification = true)
    {
        if (
            !$user->getEmail() ||
            !$user->getEmail()->getValue() ||
            !trim($user->getEmail()->getValue()) ||
            !$this->isRoleForEmailAllowed($user->getEmail()->getValue(), $type)
        ) {
            return false;
        }

        do_action('amelia_set_wp_user_for_new_' . $type, $user->toArray());

        /** @var CreateWPUser $createWPUserService */
        $createWPUserService = $this->container->get('user.create.wp.user');

        $externalId = $createWPUserService->create(
            $user->getEmail()->getValue(),
            $user->getFirstName() ? $user->getFirstName()->getValue() : '',
            $user->getLastName() ? $user->getLastName()->getValue() : '',
            'wpamelia-' . $user->getType(),
            $sendNewUserNotification
        );

        if ($password) {
            wp_set_password($password, $externalId);
        }

        /** @var UserRepository $userRepository */
        $userRepository = $this->container->get('domain.users.repository');

        if ($externalId && !$userRepository->findByExternalId($externalId)) {
            $user->setExternalId(new Id($externalId));
            $userRepository->updateFieldById($userId, $externalId, 'externalId');
        }

        return true;
    }

    /**
     * @param int          $userId
     * @param AbstractUser $user
     * @param string       $type
     *
     * @return boolean
     * @throws ContainerValueNotFoundException
     * @throws InvalidArgumentException
     * @throws QueryExecutionException
     */
    public function setWpUserIdForExistingUser($userId, $user, $type)
    {
        if (!$this->isRoleForEmailAllowed($user->getEmail()->getValue(), $type)) {
            return false;
        }

        /** @var CreateWPUser $createWPUserService */
        $createWPUserService = $this->container->get('user.create.wp.user');

        do_action('amelia_set_wp_user_for_existing_customer', $user ? $user->toArray() : null);

        $externalId = $user->getExternalId() ? $user->getExternalId()->getValue() : null;

        /** @var AbstractUser $wpUser */
        $wpUser = $this->container->get('logged.in.user');

        if (!$wpUser && $user->getExternalId()) {
            /** @var UserService $userService */
            $userService = $this->container->get('users.service');

            $wpUser = $userService->getWpUserById($user->getExternalId()->getValue());
        }

        if ($wpUser && $wpUser->getType() !== AbstractUser::USER_ROLE_ADMIN) {
            $createWPUserService->update(
                $externalId,
                'wpamelia-' . $user->getType()
            );
        }

        /** @var UserRepository $userRepository */
        $userRepository = $this->container->get('domain.users.repository');

        if ($externalId && !$userRepository->findByExternalId($externalId)) {
            $user->setExternalId(new Id($externalId));
            $userRepository->update($userId, $user);
        }

        return true;
    }

    /**
     * @param string $email
     * @param string $type
     *
     * @return boolean
     *
     */
    private function isRoleForEmailAllowed($email, $type)
    {
        $user = get_user_by('email', $email);

        if (
            $user &&
            (
                ($type === Entities::CUSTOMER && array_intersect(['administrator', 'wpamelia-manager', 'wpamelia-provider'], (array)$user->roles)) ||
                ($type === Entities::PROVIDER && array_intersect(['administrator', 'wpamelia-manager', 'wpamelia-customer'], (array)$user->roles))
            )
        ) {
            return false;
        }

        return true;
    }

    /**
     * Check if the external ID is allowed for the given type
     *
     * @param int    $externalId
     * @param string $type
     *
     * @return boolean
     *
     */
    public function isRoleForExternalIdAllowed($externalId, $type)
    {
        $user = get_user_by('ID', $externalId);

        return $user && array_intersect(["wpamelia-$type"], (array)$user->roles);
    }

    /**
     * @param AbstractUser $user
     *
     * @return boolean
     *
     */
    public function isCustomer($user)
    {
        return $user === null || $user->getType() === Entities::CUSTOMER;
    }

    /**
     * @param AbstractUser $user
     *
     * @return boolean
     *
     */
    public function isProvider($user)
    {
        return $user === null || $user->getType() === Entities::PROVIDER;
    }

    /**
     * @param Provider|Customer $user
     * @param boolean           $sendToken
     * @param boolean           $checkIfSavedPassword
     * @param int               $loginType
     * @param string            $cabinetType
     *
     * @return CommandResult
     *
     * @throws Exception
     */
    public function getAuthenticatedUserResponse($user, $sendToken, $checkIfSavedPassword, $loginType, $cabinetType, $changePass = false)
    {
        $result = new CommandResult();

        do_action('amelia_login', $user ? $user->toArray() : null, $sendToken, $loginType, $cabinetType, $changePass);

        if (
            $user->getType() !== $cabinetType &&
            !(
                $cabinetType === AbstractUser::USER_ROLE_PROVIDER &&
                in_array($user->getType(), [AbstractUser::USER_ROLE_ADMIN, AbstractUser::USER_ROLE_MANAGER], true)
            )
        ) {
            $result->setResult(CommandResult::RESULT_ERROR);
            $result->setMessage('Could not retrieve user');
            $result->setData(['invalid_credentials' => true]);

            return $result;
        }

        /** @var HelperService $helperService */
        $helperService = $this->container->get('application.helper.service');

        /** @var SettingsService $settingsService */
        $settingsService = $this->container->get('domain.settings.service');

        /** @var array $cabinetSettings */
        $cabinetSettings = $settingsService->getSetting('roles', $cabinetType . 'Cabinet');

        /** @var ProviderApplicationService $providerService */
        $providerService = $this->container->get('application.user.provider.service');

        /** @var AbstractGoogleCalendarService $googleCalendarService */
        $googleCalendarService = $this->container->get('infrastructure.google.calendar.service');

        /** @var AbstractOutlookCalendarService $outlookCalendarService */
        $outlookCalendarService = $this->container->get('infrastructure.outlook.calendar.service');

        /** @var ProviderRepository $providerRepository */
        $providerRepository = $this->container->get('domain.users.providers.repository');

        /** @var ProviderServiceRepository $providerServiceRepository */
        $providerServiceRepository = $this->container->get('domain.bookable.service.providerService.repository');

        $provider = null;
        // If cabinet is for provider, return provider with services and schedule
        if (
            $cabinetType === AbstractUser::USER_ROLE_PROVIDER &&
            $user->getType() === AbstractUser::USER_ROLE_PROVIDER
        ) {
            $password = $user->getPassword();

            /** @var Provider $user */
            $user = $providerService->getProviderWithServicesAndSchedule($user->getId()->getValue());

            $providerService->modifyPeriodsWithSingleLocationAfterFetch($user->getWeekDayList());
            $providerService->modifyPeriodsWithSingleLocationAfterFetch($user->getSpecialDayList());

            /** @var Provider $provider */
            $provider = $providerRepository->getById($user->getId()->getValue());

            if ($provider->getGoogleCalendar()) {
                $user->setGoogleCalendar($provider->getGoogleCalendar());
            }

            if ($provider->getOutlookCalendar()) {
                $user->setOutlookCalendar($provider->getOutlookCalendar());
            }

            $user->setPassword($password);
        }

        /** @var array $userArray */
        $userArray = $user->toArray();

        // Set Time Zone to null if feature is disabled
        if ($settingsService->isFeatureEnabled('timezones') === false) {
            $userArray['timeZone'] = null;
        }

        // Set activity if it is employee cabinet
        if (
            $cabinetType === AbstractUser::USER_ROLE_PROVIDER &&
            $user->getType() === AbstractUser::USER_ROLE_PROVIDER
        ) {
            $companyDaysOff = $settingsService->getCategorySettings('daysOff');

            $companyDayOff = $providerService->checkIfTodayIsCompanyDayOff($companyDaysOff);

            $userArray = $providerService->manageProvidersActivity(
                [$userArray],
                $companyDayOff
            )[0];

            $userArray['mandatoryServicesIds'] = $providerServiceRepository->getMandatoryServicesIdsForProvider($user->getId()->getValue());

            $userArray['googleCalendar']['calendarList'] = [];
            $userArray['googleCalendar']['calendarId'] = null;

            if ($settingsService->isFeatureEnabled('googleCalendar')) {
                $googleCalendarAccounts = $providerRepository->getGoogleCalendarAccounts($user->getId()->getValue());

                $googleCalendarIdFromAccounts = null;
                foreach ($googleCalendarAccounts as $account) {
                    if (!empty($account['calendarId'])) {
                        $googleCalendarIdFromAccounts = $account['calendarId'];
                        break;
                    }
                }

                $userArray['googleCalendar']['calendarId'] = $googleCalendarIdFromAccounts;
                $userArray['googleCalendar']['accounts'] = $googleCalendarAccounts;

                $googleCalendarGlobalSettings = $settingsService->getCategorySettings('googleCalendar');
                $userArray['googleCalendar']['title'] = $userArray['googleCalendar']['title'] ??
                    ($googleCalendarGlobalSettings['title'] ?? ['appointment' => '%service_name%', 'event' => '%event_name%']);
                $userArray['googleCalendar']['description'] = $userArray['googleCalendar']['description'] ??
                    ($googleCalendarGlobalSettings['description'] ?? ['appointment' => '', 'event' => '']);

                $blockedCalendars = [];
                foreach ($googleCalendarAccounts as $account) {
                    if (!empty($account['blockedCalendars'])) {
                        foreach ($account['blockedCalendars'] as $calendarId) {
                            if (!in_array($calendarId, $blockedCalendars)) {
                                $blockedCalendars[] = $calendarId;
                            }
                        }
                    }
                }
                $userArray['googleCalendar']['blockedCalendars'] = $blockedCalendars;

                try {
                    $googleCalendar = $settingsService->getCategorySettings('googleCalendar');

                    if (!$googleCalendar['accessToken']) {
                        $userArray['googleCalendar']['calendarList'] = $googleCalendarService->listCalendarList($user);
                        $userArray['googleCalendar']['calendarId'] = $googleCalendarService->getProviderGoogleCalendarId($user);

                        // Fetch calendar lists for all accounts
                        if (!empty($userArray['googleCalendar']['accounts'])) {
                            $userArray['googleCalendar']['accounts'] = $googleCalendarService->getCalendarListsForAccounts(
                                $userArray['googleCalendar']['accounts'],
                                $user
                            );
                        }
                    } else {
                        /** @var AbstractGoogleCalendarMiddlewareService $googleCalendarMiddlewareService */
                        $googleCalendarMiddlewareService = $this->container->get(
                            'infrastructure.google.calendar.middleware.service'
                        );
                        $userArray['googleCalendar']['calendarList'] = $googleCalendarMiddlewareService->getCalendarList($userArray['googleCalendar']);
                        $userArray['googleCalendar']['calendarId'] = $googleCalendarIdFromAccounts;

                        // Fetch calendar lists for all accounts
                        if (!empty($userArray['googleCalendar']['accounts'])) {
                            $userArray['googleCalendar']['accounts'] = $googleCalendarMiddlewareService->getCalendarListsForAccounts(
                                $userArray['googleCalendar']['accounts']
                            );
                        }
                    }
                } catch (\Exception $e) {
                    $providerRepository->updateErrorColumn($user->getId()->getValue(), $e->getMessage());
                }

                // Ensure the top-level calendarId reflects the correct account row.
                if ($googleCalendarIdFromAccounts) {
                    $userArray['googleCalendar']['calendarId'] = $googleCalendarIdFromAccounts;
                }
            }

            $userArray['outlookCalendar']['calendarList'] = [];
            $userArray['outlookCalendar']['calendarId'] = null;

            if ($settingsService->isFeatureEnabled('outlookCalendar')) {
                $outlookCalendarAccounts = $providerRepository->getOutlookCalendarAccounts($user->getId()->getValue());

                $outlookCalendarIdFromAccounts = null;
                foreach ($outlookCalendarAccounts as $account) {
                    if (!empty($account['calendarId'])) {
                        $outlookCalendarIdFromAccounts = $account['calendarId'];
                        break;
                    }
                }

                $userArray['outlookCalendar']['calendarId'] = $outlookCalendarIdFromAccounts;
                $userArray['outlookCalendar']['accounts'] = $outlookCalendarAccounts;

                $outlookCalendarGlobalSettings = $settingsService->getCategorySettings('outlookCalendar');
                $userArray['outlookCalendar']['title'] = $userArray['outlookCalendar']['title'] ??
                    ($outlookCalendarGlobalSettings['title'] ?? ['appointment' => '%service_name%', 'event' => '%event_name%']);
                $userArray['outlookCalendar']['description'] = $userArray['outlookCalendar']['description'] ??
                    ($outlookCalendarGlobalSettings['description'] ?? ['appointment' => '', 'event' => '']);

                $blockedCalendars = [];
                foreach ($outlookCalendarAccounts as $account) {
                    if (!empty($account['blockedCalendars'])) {
                        foreach ($account['blockedCalendars'] as $calendarId) {
                            if (!in_array($calendarId, $blockedCalendars)) {
                                $blockedCalendars[] = $calendarId;
                            }
                        }
                    }
                }
                $userArray['outlookCalendar']['blockedCalendars'] = $blockedCalendars;

                try {
                    $outlookCalendar = $settingsService->getCategorySettings('outlookCalendar');
                    if (!$outlookCalendar['accessToken']) {
                        $userArray['outlookCalendar']['calendarList'] = $outlookCalendarService->listCalendarList($user);
                        $userArray['outlookCalendar']['calendarId'] = $outlookCalendarService->getProviderOutlookCalendarId($user);

                        // Fetch calendar lists for all accounts
                        if (!empty($userArray['outlookCalendar']['accounts'])) {
                            $userArray['outlookCalendar']['accounts'] = $outlookCalendarService->getCalendarListsForAccounts(
                                $userArray['outlookCalendar']['accounts'],
                                $user
                            );
                        }
                    } else {
                        /** @var AbstractOutlookCalendarMiddlewareService $outlookCalendarMiddlewareService */
                        $outlookCalendarMiddlewareService = $this->container->get(
                            'infrastructure.outlook.calendar.middleware.service'
                        );
                        $userArray['outlookCalendar']['calendarList'] = $outlookCalendarMiddlewareService->getCalendarList($userArray['outlookCalendar']);
                        $userArray['outlookCalendar']['calendarId'] = $outlookCalendarIdFromAccounts;

                        // Fetch calendar lists for all accounts
                        if (!empty($userArray['outlookCalendar']['accounts'])) {
                            $userArray['outlookCalendar']['accounts'] = $outlookCalendarMiddlewareService->getCalendarListsForAccounts(
                                $userArray['outlookCalendar']['accounts']
                            );
                        }
                    }
                } catch (\Exception $e) {
                    $providerRepository->updateErrorColumn($user->getId()->getValue(), $e->getMessage());
                }

                if ($outlookCalendarIdFromAccounts) {
                    $userArray['outlookCalendar']['calendarId'] = $outlookCalendarIdFromAccounts;
                }
            }
        }

        $responseData = [
            Entities::USER => $userArray,
            'is_wp_user'   => $loginType === LoginType::WP_USER
        ];

        if (
            ($loginType === LoginType::AMELIA_URL_TOKEN || $loginType === LoginType::AMELIA_CREDENTIALS) &&
            $checkIfSavedPassword &&
            $cabinetSettings['loginEnabled']
        ) {
            if ($user->getPassword() === null || $user->getPassword()->getValue() === null) {
                $responseData['set_password'] = true;
            } elseif ($changePass) {
                $responseData['change_password'] = true;
            }
        }

        if ($sendToken) {
            $secureCookie = WPHelperService::isSSL();

            $token = $helperService->getGeneratedJWT(
                $user->getEmail()->getValue(),
                $cabinetSettings['headerJwtSecret'],
                DateTimeService::getNowDateTimeObject()->getTimestamp() + $cabinetSettings['tokenValidTime'],
                $loginType
            );

            setcookie('ameliaToken', $token, [
                'path' => '/',
                'secure' => $secureCookie,
                'httponly' => true,
                'expires' => DateTimeService::getNowDateTimeObject()->getTimestamp() + $cabinetSettings['tokenValidTime']
            ]);
            setcookie('ameliaUserEmail', $userArray['email'], [
                'path' => '/',
                'secure' => $secureCookie,
                'httponly' => true,
                'expires' => DateTimeService::getNowDateTimeObject()->getTimestamp() + $cabinetSettings['tokenValidTime']
            ]);
        }

        $result->setResult(CommandResult::RESULT_SUCCESS);
        $result->setMessage('Successfully');
        $result->setData($responseData);

        return $result;
    }

    /**
     * @param $token
     * @param $isUrlToken
     * @param $jwtType
     *
     * @return AbstractUser|null
     *
     * @throws AccessDeniedException
     * @throws InvalidArgumentException
     * @throws QueryExecutionException
     */
    public function getAuthenticatedUser($token, $isUrlToken, $jwtType)
    {
        /** @var SettingsService $settingsService */
        $settingsService = $this->container->get('domain.settings.service');

        /** @var array $jwtSettings */
        $jwtSettings = $settingsService->getSetting('roles', $jwtType);

        $secretKey = $jwtSettings[$isUrlToken ? 'urlJwtSecret' : 'headerJwtSecret'];
        try {
            $jwtObject = JWT::decode(
                $token,
                new Key($secretKey, 'HS256')
            );
        } catch (Exception $e) {
            return null;
        }

        /** @var UserRepository $userRepository */
        $userRepository = $this->container->get('domain.users.repository');

        $wpUser = get_user_by('email', $jwtObject->email);
        $wpUserAmeliaRole = $wpUser ? UserRoles::getUserAmeliaRole($wpUser) : null;

        if (
            !$isUrlToken &&
            in_array((int)$jwtObject->wp, [LoginType::WP_CREDENTIALS, LoginType::WP_USER], true) &&
            $wpUser &&
            in_array($wpUserAmeliaRole, [AbstractUser::USER_ROLE_ADMIN, AbstractUser::USER_ROLE_MANAGER], true)
        ) {
            $user = UserFactory::create(
                [
                    'type'       => $wpUserAmeliaRole,
                    'firstName'  => $wpUser->get('first_name') !== ''
                        ? $wpUser->get('first_name')
                        : $wpUser->get('user_nicename'),
                    'lastName'   => $wpUser->get('last_name') !== ''
                        ? $wpUser->get('last_name')
                        : $wpUser->get('user_nicename'),
                    'email'      => $wpUser->get('user_email') ?: 'guest@example.com',
                    'externalId' => $wpUser->ID,
                ]
            );
        } else {
            /** @var Customer $user */
            $user = $userRepository->getByEmail($jwtObject->email, true, true);

            if (!($user instanceof AbstractUser)) {
                return null;
            }
        }

        $user->setLoginType($jwtObject->wp);

        if ($isUrlToken) {
            $usedTokens = $user->getUsedTokens() && $user->getUsedTokens()->getValue() ?
                json_decode($user->getUsedTokens()->getValue(), true) : [];

            if (
                in_array($token, $usedTokens, true) &&
                ($usedTokensCount = array_count_values($usedTokens)) &&
                $usedTokensCount[$token] > 4
            ) {
                return null;
            }

            $currentTimeStamp = DateTimeService::getNowDateTimeObject()->getTimestamp();

            foreach ($usedTokens as $tokenKey => $usedToken) {
                if ($tokenKey < $currentTimeStamp) {
                    unset($usedTokens[$tokenKey]);
                }
            }

            $usedTokens[$jwtSettings['tokenValidTime'] + $currentTimeStamp] = $token;

            $newUsedTokens = new Json(json_encode($usedTokens));

            $userRepository->updateFieldById($user->getId()->getValue(), $newUsedTokens->getValue(), 'usedTokens');
        }

        return $user;
    }

    /**
     * @param string $token
     * @param string $cabinetType
     *
     * @return AbstractUser
     *
     * @throws AccessDeniedException
     * @throws AuthorizationException
     * @throws InvalidArgumentException
     * @throws QueryExecutionException
     */
    public function authorization($token, $cabinetType)
    {
        /** @var SettingsService $settingsService */
        $settingsService = $this->container->get('domain.settings.service');

        if ($cabinetType !== 'urlAttachment') {
            $cabinetType .= 'Cabinet';
        }
        /** @var array $cabinetSettings */
        $cabinetSettings = $settingsService->getSetting('roles', $cabinetType);

        /** @var AbstractUser $user */
        $user = $this->container->get('logged.in.user');

        $isAmeliaWPUser = $this->isAmeliaUser($user);

        // check if token exist and user is not logged in as Word Press User and token is valid
        if ($token && !$isAmeliaWPUser && ($user = $this->getAuthenticatedUser($token, false, $cabinetType)) === null) {
            throw new AuthorizationException('Authorization Exception.');
        }

        if ($user && !$isAmeliaWPUser && $user->getLoginType() === LoginType::WP_USER) {
            throw new AuthorizationException('Authorization Exception.');
        }

        // if user is not logged in as Word Press User or token not exist/valid
        if (!$this->isAmeliaUser($user)) {
            throw new AuthorizationException('Authorization Exception.');
        }

        $userType = $user->getType();

        // check if user is not logged in as Word Press User and password is required and password is not created
        if (
            !$isAmeliaWPUser &&
            $userType !== AbstractUser::USER_ROLE_ADMIN &&
            $userType !== AbstractUser::USER_ROLE_MANAGER &&
            $cabinetSettings['loginEnabled'] &&
            $this->isAmeliaUser($user) &&
            ($user->getLoginType() === LoginType::AMELIA_URL_TOKEN ||
                $user->getLoginType() === LoginType::AMELIA_CREDENTIALS
            ) && (!$user->getPassword() || !$user->getPassword()->getValue())
        ) {
            throw new AuthorizationException('Authorization Exception.');
        }

        return $user;
    }

    /**
     * @param AbstractUser $user
     *
     * @return boolean
     *
     */
    public function isAmeliaUser($user)
    {
        return $user &&
            (
                $user->getId() !== null ||
                $user->getType() === AbstractUser::USER_ROLE_ADMIN ||
                $user->getType() === AbstractUser::USER_ROLE_MANAGER
            );
    }

    /**
     * @return boolean
     */
    public function isAdminAndAllowedToBookAtAnyTime()
    {
        /** @var SettingsService $settingsDomainService */
        $settingsDomainService = $this->container->get('domain.settings.service');

        return $settingsDomainService->getSetting('roles', 'allowAdminBookAtAnyTime') &&
            ($loggedInUser = $this->container->get('logged.in.user')) &&
            $loggedInUser->getType() === AbstractUser::USER_ROLE_ADMIN;
    }

    /**
     * @return boolean
     */
    public function isAdminAndAllowedToBookOver()
    {
        /** @var SettingsService $settingsDomainService */
        $settingsDomainService = $this->container->get('domain.settings.service');

        return $settingsDomainService->getSetting('roles', 'allowAdminBookOverApp') &&
            ($loggedInUser = $this->container->get('logged.in.user')) &&
            $loggedInUser->getType() === AbstractUser::USER_ROLE_ADMIN;
    }

    /**
     * @param CustomerBooking $booking
     * @param AbstractUser    $user
     * @param string          $bookingToken
     *
     * @return boolean
     */
    public function isCustomerBooking($booking, $user, $bookingToken)
    {
        $isValidToken = $booking && $bookingToken !== null && $bookingToken === $booking->getToken()->getValue();

        $isValidUser = $user &&
            $booking &&
            $user->getId() &&
            $booking->getCustomerId() &&
            $booking->getCustomerId()->getValue() &&
            $user->getId()->getValue() === $booking->getCustomerId()->getValue();

        if (!($isValidToken || $isValidUser)) {
            return false;
        }

        return true;
    }

    /**
     * @param AbstractUser $currentUser
     * @param string $token
     *
     * @return boolean
     */
    public function checkProviderPermissions($currentUser, $token)
    {
        return ($currentUser === null && $token) || $currentUser->getType() === AbstractUser::USER_ROLE_PROVIDER;
    }
}
