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

use Chill\CustomFieldsBundle\Service\CustomFieldsHelper;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Timeline\TimelineProviderInterface;
use Chill\MainBundle\Timeline\TimelineSingleQuery;
use Chill\PersonBundle\Entity\Person;
use Chill\ReportBundle\Entity\Report;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Security\Core\Security;

/**
 * Provide report for inclusion in timeline.
 */
class TimelineReportProvider implements TimelineProviderInterface
{
    public function __construct(protected EntityManager $em, protected AuthorizationHelper $helper, private readonly Security $security, protected CustomFieldsHelper $customFieldsHelper, protected $showEmptyValues) {}

    public function fetchQuery($context, array $args)
    {
        $this->checkContext($context);

        $report = $this->em->getClassMetadata(Report::class);
        [$where, $parameters] = $this->getWhereClause($context, $args);

        return TimelineSingleQuery::fromArray([
            'id' => $report->getTableName()
                 .'.'.$report->getColumnName('id'),
            'type' => 'report',
            'date' => $report->getTableName()
                 .'.'.$report->getColumnName('date'),
            'FROM' => $this->getFromClause($context),
            'WHERE' => $where,
            'parameters' => $parameters,
        ]);
    }

    public function getEntities(array $ids)
    {
        $reports = $this->em->getRepository('ChillReportBundle:Report')
            ->findBy(['id' => $ids]);

        $result = [];

        foreach ($reports as $report) {
            $result[$report->getId()] = $report;
        }

        return $result;
    }

    public function getEntityTemplate($entity, $context, array $args)
    {
        $this->checkContext($context);

        return [
            'template' => '@ChillReport/Timeline/report.html.twig',
            'template_data' => [
                'report' => $entity,
                'context' => $context,
                'custom_fields_in_summary' => $this->getFieldsToRender($entity, $context),
            ],
        ];
    }

    public function supportsType($type)
    {
        return 'report' === $type;
    }

    protected function getFieldsToRender(Report $entity, $context, array $args = [])
    {
        // gather all custom fields which should appears in summary
        $gatheredFields = [];

        if (\array_key_exists('summary_fields', $entity->getCFGroup()->getOptions())) {
            // keep in memory title
            $title = null;
            $subtitle = null;

            foreach ($entity->getCFGroup()->getCustomFields() as $customField) {
                if (
                    \in_array(
                        $customField->getSlug(),
                        $entity->getCFGroup()->getOptions()['summary_fields'],
                        true
                    )
                ) {
                    // if we do not want to show empty values
                    if (false === $this->showEmptyValues) {
                        if ('title' === $customField->getType()) {
                            $options = $customField->getOptions();

                            switch ($options['type']) {
                                case 'title':
                                    $title = $customField;

                                    break;

                                case 'subtitle':
                                    $subtitle = $customField;

                                    break;
                            }
                        } else {
                            if (
                                false
                                === $this->customFieldsHelper->isEmptyValue($entity->getCFData(), $customField)
                            ) {
                                if (null !== $title) {
                                    $gatheredFields[] = $title;
                                    $title = null;
                                }

                                if (null !== $subtitle) {
                                    $gatheredFields[] = $subtitle;
                                    $subtitle = null;
                                }
                                $gatheredFields[] = $customField;
                            }
                        }
                    } else {
                        $gatheredFields[] = $customField;
                    }
                }
            }
        }

        return $gatheredFields;
    }

    /**
     * check if the context is supported.
     *
     * @param string $context
     *
     * @throws \LogicException if the context is not supported
     */
    private function checkContext($context)
    {
        if ('person' !== $context && 'center' !== $context) {
            throw new \LogicException("The context '{$context}' is not supported. Currently only 'person' and 'center' is supported");
        }
    }

    private function getFromClause(string $context): string
    {
        $report = $this->em->getClassMetadata(Report::class);
        $person = $this->em->getClassMetadata(Person::class);
        $reportPersonId = $report
            ->getAssociationMapping('person')['joinColumns'][0]['name'];
        $personId = $report
            ->getAssociationMapping('person')['joinColumns'][0]['referencedColumnName'];

        $clause = '{report} '.
            'JOIN {person} ON {report}.{person_id} = {person}.{id_person} ';

        return \strtr(
            $clause,
            [
                '{report}' => $report->getTableName(),
                '{person}' => $person->getTableName(),
                '{person_id}' => $reportPersonId,
                '{id_person}' => $personId,
            ]
        );
    }

    private function getWhereClause(string $context, array $args): array
    {
        return match ($context) {
            'person' => $this->getWhereClauseForPerson($context, $args),
            'center' => $this->getWhereClauseForCenter($context, $args),
            default => throw new \UnexpectedValueException("This context {$context} is not implemented"),
        };
    }

    private function getWhereClauseForCenter(string $context, array $args): array
    {
        $role = 'CHILL_REPORT_SEE';

        $report = $this->em->getClassMetadata(Report::class);
        $person = $this->em->getClassMetadata(Person::class);

        $reachableCenters = $this->helper->getReachableCenters($this->security->getUser(), $role);
        $reportPersonId = $report->getAssociationMapping('person')['joinColumns'][0]['name'];
        $reportScopeId = $report->getAssociationMapping('scope')['joinColumns'][0]['name'];
        $personCenterId = $person->getAssociationMapping('center')['joinColumns'][0]['name'];
        // parameters for the query, will be filled later
        $parameters = [];

        // the clause, that will be joined with an "OR"
        $centerScopesClause = '({person}.{center_id} = ? '.
            'AND {report}.{scopes_id} IN ({scopes_ids}))';
        // container for formatted clauses
        $formattedClauses = [];

        $askedCenters = $args['centers'];

        foreach ($reachableCenters as $center) {
            if (false === \in_array($center, $askedCenters, true)) {
                continue;
            }

            // add the center id to the parameters
            $parameters[] = $center->getId();
            // loop over scopes
            $scopeIds = [];

            foreach ($this->helper->getReachableScopes($this->security->getUser(), $role, $center) as $scope) {
                if (\in_array($scope->getId(), $scopeIds, true)) {
                    continue;
                }
                $scopeIds[] = $scope->getId();
            }

            $formattedClauses[] = \strtr($centerScopesClause, [
                '{scopes_ids}' => \implode(', ', \array_fill(0, \count($scopeIds), '?')),
            ]);
            // append $scopeIds to parameters
            $parameters = [...$parameters, ...$scopeIds];
        }

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

        return [
            \strtr(
                \implode(' OR ', $formattedClauses),
                [
                    '{person}' => $person->getTableName(),
                    '{center_id}' => $personCenterId,
                    '{report}' => $report->getTableName(),
                    '{scopes_id}' => $reportScopeId,
                ]
            ),
            $parameters,
        ];
    }

    private function getWhereClauseForPerson(string $context, array $args): array
    {
        $role = 'CHILL_REPORT_SEE';
        $report = $this->em->getClassMetadata(Report::class);
        $person = $this->em->getClassMetadata(Person::class);
        $reportPersonId = $report->getAssociationMapping('person')['joinColumns'][0]['name'];
        $reportScopeId = $report->getAssociationMapping('scope')['joinColumns'][0]['name'];
        $personCenterId = $person->getAssociationMapping('center')['joinColumns'][0]['name'];
        // parameters for the query, will be filled later
        $parameters = [$args['person']->getId()];

        // this is the final clause  that we are going to fill
        $clause = '{report}.{person_id} = ? AND {report}.{scopes_id} IN ({scopes_ids})';
        // iterate over reachable scopes
        $scopes = $this->helper->getReachableScopes($this->security->getUser(), $role, $args['person']->getCenter());

        foreach ($scopes as $scope) {
            if (\in_array($scope->getId(), $parameters, true)) {
                continue;
            }

            $parameters[] = $scope->getId();
        }

        if (1 === \count($parameters)) {
            // nothing change, we simplify the clause
            $clause = '{report}.{person_id} = ? AND FALSE = TRUE';
        }

        return [
            \strtr(
                $clause,
                [
                    '{report}' => $report->getTableName(),
                    '{person_id}' => $reportPersonId,
                    '{scopes_id}' => $reportScopeId,
                    '{scopes_ids}' => \implode(
                        ', ',
                        \array_fill(0, \count($parameters) - 1, '?')
                    ),
                ]
            ),
            $parameters,
        ];
    }
}
