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

use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\ExportGenerationContext;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\Helper\AggregateStringHelper;
use Chill\MainBundle\Export\Helper\DateTimeHelper;
use Chill\MainBundle\Export\Helper\TranslatableStringExportLabelHelper;
use Chill\MainBundle\Export\Helper\UserHelper;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
// use Chill\MainBundle\Service\RollingDate\RollingDateConverter;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\AccompanyingPeriod\UserHistory;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Export\Helper\FilterListAccompanyingPeriodHelperInterface;
use Chill\PersonBundle\Export\Helper\LabelPersonHelper;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;

final readonly class ListEvaluation implements ListInterface, GroupedExportInterface
{
    private const FIELDS = [
        'id',
        'startDate',
        'endDate',
        'maxDate',
        'warningInterval',
        'timeSpent',
        'acpw_id',
        'acpw_startDate',
        'acpw_endDate',
        'acpw_socialaction_id',
        'acpw_socialaction',
        'acpw_socialissue',
        'acpw_referrers',
        'acpw_note',
        'acpw_acp_id',
        'acpw_acp_user',
        'acpw_persons_id',
        'acpw_persons',
        'comment',
        'eval_title',
        'createdAt',
        'updatedAt',
        'createdBy',
        'updatedBy',
    ];

    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly SocialIssueRender $socialIssueRender,
        private readonly SocialIssueRepository $socialIssueRepository,
        private readonly SocialActionRender $socialActionRender,
        private readonly SocialActionRepository $socialActionRepository,
        private readonly UserHelper $userHelper,
        private readonly LabelPersonHelper $personHelper,
        private readonly DateTimeHelper $dateTimeHelper,
        private readonly TranslatableStringExportLabelHelper $translatableStringExportLabelHelper,
        private readonly AggregateStringHelper $aggregateStringHelper,
        private readonly RollingDateConverterInterface $rollingDateConverter,
        private FilterListAccompanyingPeriodHelperInterface $filterListAccompanyingPeriodHelper,
    ) {}

    public function buildForm(FormBuilderInterface $builder)
    {
        $builder
            ->add('calc_date', PickRollingDateType::class, [
                'label' => 'export.list.eval.Date of calculation for associated elements',
                'help' => 'export.list.eval.help_description',
                'required' => true,
            ]);
    }

    public function getNormalizationVersion(): int
    {
        return 1;
    }

    public function normalizeFormData(array $formData): array
    {
        return ['calc_date' => $formData['calc_date']->normalize()];
    }

    public function denormalizeFormData(array $formData, int $fromVersion): array
    {
        return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])];
    }

    public function getFormDefaultData(): array
    {
        return ['calc_date' => new RollingDate(RollingDate::T_TODAY)];
    }

    public function getAllowedFormattersTypes()
    {
        return [FormatterInterface::TYPE_LIST];
    }

    public function getDescription(): string
    {
        return 'export.list.eval.Generate a list of evaluations, filtered on different parameters';
    }

    public function getGroup(): string
    {
        return 'Exports of evaluations';
    }

    public function getLabels($key, array $values, $data)
    {
        return match ($key) {
            'startDate', 'endDate', 'maxDate', 'acpw_startDate', 'acpw_endDate', 'createdAt', 'updatedAt' => $this->dateTimeHelper->getLabel('export.list.eval.'.$key),
            'acpw_socialaction' => function ($value) use ($key) {
                if ('_header' === $value) {
                    return 'export.list.eval.'.$key;
                }

                if (null === $value || '' === $value) {
                    return '';
                }

                return $this->socialActionRender->renderString(
                    $this->socialActionRepository->find($value),
                    []
                );
            },
            'acpw_socialissue' => function ($value) use ($key) {
                if ('_header' === $value) {
                    return 'export.list.eval.'.$key;
                }

                if (null === $value || '' === $value) {
                    return '';
                }

                return $this->socialIssueRender->renderString(
                    $this->socialIssueRepository->find($value),
                    []
                );
            },
            'createdBy', 'updatedBy', 'acpw_acp_user' => $this->userHelper->getLabel($key, $values, 'export.list.eval.'.$key),
            'acpw_referrers' => $this->userHelper->getLabelMulti($key, $values, 'export.list.eval.'.$key),
            'acpw_persons_id' => $this->aggregateStringHelper->getLabelMulti($key, $values, 'export.list.eval.'.$key),
            'acpw_persons' => $this->personHelper->getLabelMulti($key, $values, 'export.list.eval.'.$key),
            'eval_title' => $this->translatableStringExportLabelHelper
                ->getLabel($key, $values, 'export.list.eval.'.$key),
            default => static function ($value) use ($key) {
                if ('_header' === $value) {
                    return 'export.list.eval.'.$key;
                }

                if (null === $value) {
                    return '';
                }

                return $value;
            },
        };
    }

    public function getQueryKeys($data): array
    {
        return self::FIELDS;
    }

    public function getResult($query, $data, ExportGenerationContext $context): array
    {
        return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR);
    }

    public function getTitle(): string
    {
        return 'export.list.eval.List of evaluations';
    }

    public function getType(): string
    {
        return Declarations::EVAL_TYPE;
    }

    public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder
    {
        $centers = array_map(static fn ($el) => $el['center'], $acl);
        $calcDate = $data['calc_date'] ?? new RollingDate(RollingDate::T_TODAY);

        $qb = $this->entityManager->createQueryBuilder();

        $qb
            ->from(AccompanyingPeriodWorkEvaluation::class, 'workeval')
            ->distinct()
            ->select('workeval.id AS id')
            ->join('workeval.accompanyingPeriodWork', 'acpw')
            ->join('acpw.accompanyingPeriod', 'acp')
            ->join('acp.participations', 'acppart')
            ->join('acppart.person', 'person')
            // ignore participation which didn't last one day, at least
            ->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL')
            // get participants at the given date
            ->andWhere('acppart.startDate <= :calc_date AND (acppart.endDate > :calc_date OR acppart.endDate IS NULL)')
            ->setParameter('calc_date', $this->rollingDateConverter->convert($calcDate));

        $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $context->byUser, $data);

        AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);

        $this->addSelectClauses($qb, $this->rollingDateConverter->convert($calcDate));

        return $qb;
    }

    public function requiredRole(): string
    {
        return AccompanyingPeriodVoter::STATS;
    }

    public function supportsModifiers(): array
    {
        return [
            Declarations::EVAL_TYPE,
            Declarations::SOCIAL_WORK_ACTION_TYPE,
            Declarations::ACP_TYPE,
            Declarations::PERSON_TYPE,
        ];
    }

    private function addSelectClauses(QueryBuilder $qb, \DateTimeImmutable $calc_date): void
    {
        // add the regular fields
        foreach (['startDate', 'endDate', 'maxDate', 'warningInterval', 'comment', 'createdAt', 'updatedAt'] as $field) {
            $qb->addSelect(sprintf('workeval.%s AS %s', $field, $field));
        }

        // add the time spent field
        $qb->addSelect('(workeval.timeSpent / 60) AS timeSpent');

        // those with identity
        foreach (['createdBy', 'updatedBy'] as $field) {
            $qb->addSelect(sprintf('IDENTITY(workeval.%s) AS %s', $field, $field));
        }

        foreach (['id', 'startDate', 'endDate', 'note'] as $field) {
            $qb->addSelect(sprintf('acpw.%s AS %s', $field, 'acpw_'.$field));
        }

        // join socialaction
        $qb
            ->leftJoin('acpw.socialAction', 'sa')
            ->addSelect('sa.id AS acpw_socialaction_id')
            ->addSelect('sa.id AS acpw_socialaction')
            ->addSelect('IDENTITY(sa.issue) AS acpw_socialissue');

        // join acp
        $qb
            ->addSelect('acp.id AS acpw_acp_id')
            ->addSelect('IDENTITY(acp.user) AS acpw_acp_user');

        // referrers => at date XXXX
        $qb
            ->addSelect('(SELECT JSON_BUILD_OBJECT(\'uid\', IDENTITY(history.user), \'d\', history.startDate) FROM '.UserHistory::class.' history '.
                'WHERE history.accompanyingPeriod = acp AND history.startDate <= :calc_date AND (history.endDate IS NULL OR history.endDate > :calc_date)) AS acpw_referrers');

        // persons
        $qb
            ->addSelect('(SELECT AGGREGATE(person_acpw_member.id) FROM '.Person::class.' person_acpw_member '
                .'WHERE person_acpw_member MEMBER OF acpw.persons) AS acpw_persons_id')
            ->addSelect('(SELECT AGGREGATE(person1_acpw_member.id) FROM '.Person::class.' person1_acpw_member '
                .'WHERE person1_acpw_member MEMBER OF acpw.persons) AS acpw_persons');

        // join evaluation
        $qb
            ->leftJoin('workeval.evaluation', 'eval')
            ->addSelect('eval.title AS eval_title');
    }
}
