<?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\PersonBundle\Security\Authorization;

use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security;

class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{
    /**
     * all the roles that are linked to an accompanying period.
     */
    final public const ALL = [
        self::SEE,
        self::SEE_DETAILS,
        self::CREATE,
        self::EDIT,
        self::DELETE,
        self::FULL,
        self::TOGGLE_CONFIDENTIAL_ALL,
        self::TOGGLE_INTENSITY,
        self::RE_OPEN_COURSE,
    ];

    final public const CREATE = 'CHILL_PERSON_ACCOMPANYING_PERIOD_CREATE';

    /**
     * role to DELETE the course.
     *
     * Will be true only for the creator, and if the course is still at DRAFT step.
     */
    final public const DELETE = 'CHILL_PERSON_ACCOMPANYING_PERIOD_DELETE';

    /**
     * role to EDIT the course.
     *
     * If the course is closed, it will be always false.
     */
    final public const EDIT = 'CHILL_PERSON_ACCOMPANYING_PERIOD_UPDATE';

    /**
     * Give all the right above.
     */
    final public const FULL = 'CHILL_PERSON_ACCOMPANYING_PERIOD_FULL';

    /**
     * Reopen a closed course.
     *
     * This forward to the EDIT role, without taking into account that the course
     * is closed
     */
    final public const RE_OPEN_COURSE = 'CHILL_PERSON_ACCOMPANYING_PERIOD_REOPEN';

    /**
     * Allow user to bulk reassign the courses.
     */
    final public const REASSIGN_BULK = 'CHILL_PERSON_ACCOMPANYING_COURSE_REASSIGN_BULK';

    final public const SEE = 'CHILL_PERSON_ACCOMPANYING_PERIOD_SEE';

    /**
     * details are for seeing:.
     *
     * * SocialIssues
     */
    final public const SEE_DETAILS = 'CHILL_PERSON_ACCOMPANYING_PERIOD_SEE_DETAILS';

    /**
     * Give the ability to see statistics.
     */
    final public const STATS = 'CHILL_PERSON_ACCOMPANYING_PERIOD_STATS';

    /**
     * Right to toggle confidentiality.
     */
    final public const TOGGLE_CONFIDENTIAL = 'CHILL_PERSON_ACCOMPANYING_PERIOD_TOGGLE_CONFIDENTIAL';

    final public const TOGGLE_CONFIDENTIAL_ALL = 'CHILL_PERSON_ACCOMPANYING_PERIOD_TOGGLE_CONFIDENTIAL_ALL';

    /**
     * Right to toggle urgency of parcours.
     */
    final public const TOGGLE_INTENSITY = 'CHILL_PERSON_ACCOMPANYING_PERIOD_TOGGLE_INTENSITY';

    /**
     * Right to see confidential period even if not referrer.
     */
    final public const SEE_CONFIDENTIAL_ALL = 'CHILL_PERSON_ACCOMPANYING_PERIOD_SEE_CONFIDENTIAL';

    private readonly VoterHelperInterface $voterHelper;

    public function __construct(
        private readonly Security $security,
        VoterHelperFactoryInterface $voterHelperFactory,
    ) {
        $this->voterHelper = $voterHelperFactory
            ->generate(self::class)
            ->addCheckFor(null, [self::CREATE, self::REASSIGN_BULK])
            ->addCheckFor(AccompanyingPeriod::class, [self::TOGGLE_CONFIDENTIAL, ...self::ALL])
            ->addCheckFor(Household::class, [self::SEE])
            ->addCheckFor(Person::class, [self::SEE, self::CREATE])
            ->addCheckFor(Center::class, [self::STATS])
            ->build();
    }

    public function getRoles(): array
    {
        return [
            self::SEE,
            self::SEE_DETAILS,
            self::CREATE,
            self::EDIT,
            self::DELETE,
            self::FULL,
            self::TOGGLE_CONFIDENTIAL_ALL,
            self::REASSIGN_BULK,
            self::STATS,
            self::SEE_CONFIDENTIAL_ALL,
        ];
    }

    public function getRolesWithHierarchy(): array
    {
        return ['Accompanying period' => $this->getRoles()];
    }

    public function getRolesWithoutScope(): array
    {
        return [];
    }

    protected function supports($attribute, $subject): bool
    {
        return $this->voterHelper->supports($attribute, $subject);
    }

    protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
    {
        if (!$token->getUser() instanceof User) {
            return false;
        }

        if ($subject instanceof AccompanyingPeriod) {
            if (AccompanyingPeriod::STEP_CLOSED === $subject->getStep()) {
                if (\in_array($attribute, [self::EDIT, self::DELETE], true)) {
                    return false;
                }

                if (self::RE_OPEN_COURSE === $attribute) {
                    return $this->voterHelper->voteOnAttribute(self::EDIT, $subject, $token);
                }
            }

            if (AccompanyingPeriod::STEP_DRAFT === $subject->getStep()) {
                // only creator can see, edit, delete, etc.
                if (
                    $subject->getCreatedBy() === $token->getUser()
                    || null === $subject->getCreatedBy()
                ) {
                    return true;
                }

                return false;
            }

            if (\in_array($attribute, [
                self::SEE, self::SEE_DETAILS, self::EDIT,
            ], true)) {
                if ($subject->getUser() === $token->getUser()) {
                    return true;
                }
            }

            if (self::TOGGLE_CONFIDENTIAL === $attribute) {
                if (null !== $subject->getUser() && ($subject->getUser() === $token->getUser())) {
                    return true;
                }

                if ($this->voterHelper->voteOnAttribute(self::TOGGLE_CONFIDENTIAL_ALL, $subject, $token)) {
                    return true;
                }

                return false;
            }

            if (self::TOGGLE_INTENSITY === $attribute) {
                if (null !== $subject->getUser() && ($subject->getUser() === $token->getUser())) {
                    return true;
                }

                return false;
            }

            // if confidential, only the referent can see it
            if ($subject->isConfidential()) {
                if ($this->voterHelper->voteOnAttribute(self::SEE_CONFIDENTIAL_ALL, $subject, $token)) {
                    return true;
                }

                return $token->getUser() === $subject->getUser();
            }
        }

        return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);
    }
}
