<?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\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoal;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkReferrerHistory;
use Chill\PersonBundle\Entity\AccompanyingPeriod\UserHistory;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Chill\PersonBundle\Entity\SocialWork\Goal;
use Chill\PersonBundle\Entity\SocialWork\Result;
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 Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Export\Helper\LabelThirdPartyHelper;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;

final readonly class ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod implements ListInterface, GroupedExportInterface
{
    private const FIELDS = [
        'id',
        'socialActionId',
        'socialAction',
        'socialIssue',
        'acp_id',
        'startDate',
        'endDate',
        'goalsId',
        'goalsTitle',
        'goalResultsId',
        'goalResultsTitle',
        'resultsId',
        'resultsTitle',
        'evaluationsId',
        'evaluationsTitle',
        'note',
        'personsId',
        'personsName',
        'thirdParties',
        'handlingThierParty',
        'acpwReferrers',
        'referrer',
        'createdAt',
        'createdBy',
        'updatedAt',
        'updatedBy',
    ];

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

    public function buildForm(FormBuilderInterface $builder)
    {
        $builder
            ->add('calc_date', PickRollingDateType::class, [
                'label' => 'export.list.acpw.Date of calculation for associated elements',
                'help' => 'export.list.acpw.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.acpw_associate_period.List description';
    }

    public function getGroup(): string
    {
        return 'Exports of social work actions';
    }

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

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

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

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

                return $this->socialIssueRender->renderString(
                    $this->socialIssueRepository->find($value),
                    []
                );
            },
            'createdBy', 'updatedBy' => $this->userHelper->getLabel($key, $values, 'export.list.acpw.'.$key),
            'referrer' => $this->userHelper->getLabel($key, $values, 'export.list.acpw.'.$key),
            'acpwReferrers' => $this->userHelper->getLabelMulti($key, $values, 'export.list.acpw.'.$key),
            'personsName' => $this->personHelper->getLabelMulti($key, $values, 'export.list.acpw.'.$key),
            'handlingThierParty' => $this->thirdPartyHelper->getLabel($key, $values, 'export.list.acpw.'.$key),
            'thirdParties' => $this->thirdPartyHelper->getLabelMulti($key, $values, 'export.list.acpw.'.$key),
            'personsId', 'goalsId', 'goalResultsId', 'resultsId', 'evaluationsId' => $this->aggregateStringHelper->getLabelMulti($key, $values, 'export.list.acpw.'.$key),
            'goalsTitle', 'goalResultsTitle', 'resultsTitle', 'evaluationsTitle' => $this->translatableStringExportLabelHelper->getLabelMulti($key, $values, 'export.list.acpw.'.$key),
            default => static function ($value) use ($key) {
                if ('_header' === $value) {
                    return 'export.list.acpw.'.$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.acpw_associate_period.List of accompanying period works';
    }

    public function getType(): string
    {
        return Declarations::SOCIAL_WORK_ACTION_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(AccompanyingPeriodWork::class, 'acpw')
            ->distinct()
            ->select('acpw.id AS id')
            ->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::SOCIAL_WORK_ACTION_TYPE,
            Declarations::ACP_TYPE,
            Declarations::PERSON_TYPE,
        ];
    }

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

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

        // join socialaction
        $qb
            ->join('acpw.socialAction', 'sa')
            ->addSelect('sa.id AS socialActionId')
            ->addSelect('sa.id AS socialAction')
            ->addSelect('IDENTITY(sa.issue) AS socialIssue');

        // join acp
        $qb
            ->addSelect('acp.id AS acp_id');

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

        // referrer => 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 <= :calcDate AND (history.endDate IS NULL OR history.endDate > :calcDate)) AS referrer');

        // acpwReferrer at date XXX
        $qb->addSelect('(SELECT AGGREGATE(IDENTITY(acpwrh.user)) FROM '.AccompanyingPeriodWorkReferrerHistory::class.' acpwrh
                        WHERE acpwrh.accompanyingPeriodWork = acpw
                        AND acpwrh.startDate <= :calcDate AND (acpwrh.endDate IS NULL or acpwrh.endDate > :calcDate)
                        ) AS acpwReferrers');
        $qb->setParameter('calcDate', $calcDate);

        // thirdparties
        $qb
            ->addSelect('(SELECT AGGREGATE(tp.id) FROM '.ThirdParty::class.' tp '
                .'WHERE tp MEMBER OF acpw.thirdParties) AS thirdParties');

        // goals
        $qb
            ->addSelect('(SELECT AGGREGATE(IDENTITY(goal.goal)) FROM '.AccompanyingPeriodWorkGoal::class.' goal '
                .'WHERE goal MEMBER OF acpw.goals) AS goalsId')
            ->addSelect('(SELECT AGGREGATE(g.title) FROM '.AccompanyingPeriodWorkGoal::class.' goal1 '
                .'LEFT JOIN '.Goal::class.' g WITH goal1.goal = g.id WHERE goal1 MEMBER OF acpw.goals) AS goalsTitle');

        // goals results
        $qb
            ->addSelect('(SELECT AGGREGATE(wr.id) FROM '.Result::class.' wr '
                .'JOIN '.AccompanyingPeriodWorkGoal::class.' wg WITH wr MEMBER OF wg.results '
                .'WHERE wg MEMBER OF acpw.goals) AS goalResultsId')
            ->addSelect('(SELECT AGGREGATE(wr1.title) FROM '.Result::class.' wr1 '
                .'JOIN '.AccompanyingPeriodWorkGoal::class.' wg1 WITH wr1 MEMBER OF wg1.results '
                .'WHERE wg1 MEMBER OF acpw.goals) AS goalResultsTitle');

        // results
        $qb
            ->addSelect('(SELECT AGGREGATE(result.id) FROM '.Result::class.' result '
                .'WHERE result MEMBER OF acpw.results ) AS resultsId ')
            ->addSelect('(SELECT AGGREGATE (result1.title) FROM '.Result::class.' result1 '
                .'WHERE result1 MEMBER OF acpw.results ) AS resultsTitle ');

        // evaluations
        $qb
            ->addSelect('(SELECT AGGREGATE(IDENTITY(we.evaluation)) FROM '.AccompanyingPeriodWorkEvaluation::class.' we '
                .'WHERE we MEMBER OF acpw.accompanyingPeriodWorkEvaluations ) AS evaluationsId ')
            ->addSelect('(SELECT AGGREGATE(ev.title) FROM '.AccompanyingPeriodWorkEvaluation::class.' we1 '
                .'LEFT JOIN '.Evaluation::class.' ev WITH we1.evaluation = ev.id '
                .'WHERE we1 MEMBER OF acpw.accompanyingPeriodWorkEvaluations ) AS evaluationsTitle ');

        $qb->setParameter('calcDate', $calcDate);
    }
}
