<?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\TaskBundle\Timeline;

use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Timeline\TimelineProviderInterface;
use Chill\MainBundle\Timeline\TimelineSingleQuery;
use Chill\PersonBundle\Entity\Person;
use Chill\TaskBundle\Entity\SingleTask;
use Chill\TaskBundle\Entity\Task\SingleTaskPlaceEvent;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\Workflow;

/**
 * Provide element for timeline for 'person' and 'center' context.
 */
class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
{
    final public const TYPE = 'chill_task.transition';

    public function __construct(protected EntityManagerInterface $em, protected Registry $registry, protected AuthorizationHelper $authorizationHelper, protected Security $security) {}

    public function fetchQuery($context, $args)
    {
        $metadata = $this->em
            ->getClassMetadata(SingleTaskPlaceEvent::class);

        [$where, $parameters] = match ($context) {
            'person' => $this->getWhereClauseForPerson($args['person']),
            'center' => $this->getWhereClauseForCenter($args['centers']),
            default => throw new \UnexpectedValueException("context {$context} is not supported"),
        };

        return TimelineSingleQuery::fromArray([
            'id' => sprintf('%s.%s.%s', $metadata->getSchemaName(), $metadata->getTableName(), $metadata->getColumnName('id')),
            'type' => self::TYPE,
            'date' => $metadata->getColumnName('datetime'),
            'FROM' => $this->getFromClause($context),
            'WHERE' => $where,
            'parameters' => $parameters,
        ]);
    }

    public function getEntities(array $ids)
    {
        $events = $this->em
            ->getRepository(SingleTaskPlaceEvent::class)
            ->findBy(['id' => $ids]);

        return \array_combine(
            \array_map(static fn ($e) => $e->getId(), $events),
            $events
        );
    }

    public function getEntityTemplate($entity, $context, array $args)
    {
        $workflow = $this->registry->get(
            $entity->getTask(),
            $entity->getData()['workflow'] ?? null
        );
        // sf4 check: prevent error message:
        // `Notice: Undefined property: Chill\TaskBundle\Entity\Task\SingleTaskPlaceEvent::$getData`
        // * fix syntax error on $entity->getData['workflow']
        // * return null if not set

        $transition = $this->getTransitionByName($entity->getTransition(), $workflow);

        return [
            'template' => '@ChillTask/Timeline/single_task_transition.html.twig',
            'template_data' => [
                'context' => $context,
                'event' => $entity,
                'task' => $entity->getTask(),
                'transition' => $transition,
            ],
        ];
    }

    public function supportsType($type): bool
    {
        return self::TYPE === $type;
    }

    /**
     * @param string $name
     *
     * @return \Symfony\Component\Workflow\Transition
     */
    protected function getTransitionByName($name, Workflow $workflow)
    {
        foreach ($workflow->getDefinition()->getTransitions() as $transition) {
            if ($transition->getName() === $name) {
                return $transition;
            }
        }
    }

    private function getFromClause(string $context)
    {
        $taskEvent = $this->em->getClassMetadata(SingleTaskPlaceEvent::class);
        $singleTask = $this->em->getClassMetadata(SingleTask::class);
        $person = $this->em->getClassMetadata(Person::class);
        $eventFkTask = $taskEvent->getAssociationMapping('task')['joinColumns'][0]['name'];
        $taskFkPerson = $singleTask->getAssociationMapping('person')['joinColumns'][0]['name'];
        $personPk = $singleTask->getAssociationMapping('person')['joinColumns'][0]['referencedColumnName'];

        $from = '{single_task_event} '.
            'JOIN {single_task} ON {single_task}.{task_pk} = {single_task_event}.{event_fk_task} '.
            'JOIN {person} ON {single_task}.{task_person_fk} = {person}.{person_pk}';

        return \strtr(
            $from,
            [
                '{single_task}' => sprintf('%s.%s', $singleTask->getSchemaName(), $singleTask->getTableName()),
                '{single_task_event}' => sprintf('%s.%s', $taskEvent->getSchemaName(), $taskEvent->getTableName()),
                '{task_pk}' => $singleTask->getColumnName('id'),
                '{event_fk_task}' => $eventFkTask,
                '{person}' => $person->getTableName(),
                '{task_person_fk}' => $taskFkPerson,
                '{person_pk}' => $personPk,
            ]
        );
    }

    private function getWhereClauseForCenter(array $centers): array
    {
        $taskEvent = $this->em->getClassMetadata(SingleTaskPlaceEvent::class);
        $singleTask = $this->em->getClassMetadata(SingleTask::class);
        $person = $this->em->getClassMetadata(Person::class);
        $personFkCenter = $person->getAssociationMapping('center')['joinColumns'][0]['name'];
        $taskFkCircle = $singleTask->getAssociationMapping('circle')['joinColumns'][0]['name'];

        // the parameters
        $parameters = [];

        // the clause that we will repeat for each center, joined by 'OR'
        $clause = '{person}.{center_id} = ? AND {task}.{circle} IN ({circle_ids})';

        // array to gather clauses
        $clauses = [];

        // loop over centers
        foreach (
            $this->authorizationHelper->getReachableCenters(
                $this->security->getUser(),
                ActivityVoter::SEE_DETAILS
            ) as $center
        ) {
            if (false === \in_array($center, $centers, true)) {
                continue;
            }

            // fill center parameter
            $parameters[] = $center->getId();

            // we loop over circles
            $circles = $this->authorizationHelper->getReachableCircles(
                $this->security->getUser(),
                ActivityVoter::SEE_DETAILS,
                $center
            );
            $circleIds = [];

            foreach ($circles as $circle) {
                $parameters[] = $circleIds[] = $circle->getId();
            }

            $clauses[] = \strtr(
                $clause,
                [
                    '{person}' => $person->getTableName(),
                    '{center_id}' => $personFkCenter,
                    '{task}' => $singleTask->getSchemaName().'.'.$singleTask->getTableName(),
                    '{circle}' => $taskFkCircle,
                    '{circle_ids}' => \implode(', ', \array_fill(0, \count($circleIds), '?')),
                ]
            );
        }

        if (0 === \count($clauses)) {
            return ['FALSE = TRUE', []];
        }

        return [
            \implode(' OR ', $clauses),
            $parameters,
        ];
    }

    private function getWhereClauseForPerson(Person $personArg): array
    {
        $taskEvent = $this->em->getClassMetadata(SingleTaskPlaceEvent::class);
        $singleTask = $this->em->getClassMetadata(SingleTask::class);
        $person = $this->em->getClassMetadata(Person::class);
        $eventFkTask = $taskEvent->getAssociationMapping('task')['joinColumns'][0]['name'];
        $taskFkPerson = $singleTask->getAssociationMapping('person')['joinColumns'][0]['name'];
        $personPk = $singleTask->getAssociationMapping('person')['joinColumns'][0]['referencedColumnName'];
        $taskFkCircle = $singleTask->getAssociationMapping('circle')['joinColumns'][0]['name'];

        // the parameters
        $parameters = $circleIds = [];

        // the clause that we will fill
        $clause = '{person}.{person_id} = ? AND {task}.{circle} IN ({circle_ids})';

        // person is the first parameter
        $parameters[] = $personArg->getId();

        // we loop over circles
        $circles = $this->authorizationHelper->getReachableCircles(
            $this->security->getUser(),
            ActivityVoter::SEE_DETAILS,
            $personArg->getCenter()
        );

        if (0 === \count($circles)) {
            // go fast to block access to every tasks
            return ['FALSE = TRUE', []];
        }

        foreach ($circles as $circle) {
            $parameters[] = $circleIds[] = $circle->getId();
        }

        return [
            \strtr(
                $clause,
                [
                    '{person}' => $person->getTableName(),
                    '{person_id}' => $person->getColumnName('id'),
                    '{task}' => $singleTask->getSchemaName().'.'.$singleTask->getTableName(),
                    '{circle}' => $taskFkCircle,
                    '{circle_ids}' => \implode(', ', \array_fill(0, \count($circleIds), '?')),
                ]
            ),
            $parameters,
        ];
    }
}
