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

use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Export\Helper\DateTimeHelper;
use Chill\MainBundle\Export\Helper\ExportAddressHelper;
use Chill\MainBundle\Export\Helper\UserHelper;
use Chill\MainBundle\Repository\CenterRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Contracts\Translation\TranslatorInterface;

final readonly class ListAccompanyingPeriodHelper
{
    public const FIELDS = [
        'acpId',
        'step',
        'stepSince',
        'openingDate',
        'closingDate',
        'duration',
        'centers',
        'referrer',
        'referrerSince',
        'acpParticipantPersons',
        'acpParticipantPersonsIds',
        'administrativeLocation',
        'locationIsPerson',
        'locationIsTemp',
        'locationPersonName',
        'locationPersonId',
        'origin',
        'closingMotive',
        'confidential',
        'emergency',
        'intensity',
        'job',
        'isRequestorPerson',
        'isRequestorThirdParty',
        'requestorPerson',
        'requestorPersonId',
        'requestorThirdParty',
        'requestorThirdPartyId',
        'scopes',
        'socialIssues',
        'acpCreatedAt',
        'acpCreatedBy_id',
        'acpCreatedBy',
        'acpUpdatedAt',
        'acpUpdatedBy_id',
        'acpUpdatedBy',
    ];

    public function __construct(
        private ExportAddressHelper $addressHelper,
        private DateTimeHelper $dateTimeHelper,
        private PersonRenderInterface $personRender,
        private PersonRepository $personRepository,
        private ThirdPartyRepository $thirdPartyRepository,
        private ThirdPartyRender $thirdPartyRender,
        private SocialIssueRepository $socialIssueRepository,
        private SocialIssueRender $socialIssueRender,
        private TranslatableStringHelperInterface $translatableStringHelper,
        private TranslatorInterface $translator,
        private UserHelper $userHelper,
        private LabelPersonHelper $labelPersonHelper,
        private CenterRepository $centerRepository,
    ) {}

    public function getQueryKeys($data)
    {
        return array_merge(
            ListAccompanyingPeriodHelper::FIELDS,
            $this->addressHelper->getKeys(ExportAddressHelper::F_ALL, 'acp_address_fields')
        );
    }

    public function getLabels($key, array $values, $data)
    {
        if (str_starts_with((string) $key, 'acp_address_fields')) {
            return $this->addressHelper->getLabel($key, $values, $data, 'acp_address_fields');
        }

        return match ($key) {
            'stepSince', 'openingDate', 'closingDate', 'referrerSince', 'acpCreatedAt', 'acpUpdatedAt' => $this->dateTimeHelper->getLabel('export.list.acp.'.$key),
            'origin', 'closingMotive', 'job' => function ($value) use ($key) {
                if ('_header' === $value) {
                    return 'export.list.acp.'.$key;
                }

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

                return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR));
            },
            'acpCreatedBy', 'acpUpdatedBy', 'referrer' => $this->userHelper->getLabel($key, $values, 'export.list.acp.'.$key),

            'acpParticipantPersons' => $this->labelPersonHelper->getLabelMulti($key, $values, 'export.list.acp.'.$key),
            'acpParticipantPersonsIds' => function ($value) use ($key): string {
                if ('_header' === $value) {
                    return 'export.list.acp.'.$key;
                }

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

                $keys = json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR);

                return implode('|', $keys);
            },
            'locationPersonName', 'requestorPerson' => function ($value) use ($key) {
                if ('_header' === $value) {
                    return 'export.list.acp.'.$key;
                }

                if (null === $value || null === $person = $this->personRepository->find($value)) {
                    return '';
                }

                return $this->personRender->renderString($person, []);
            },
            'requestorThirdParty' => function ($value) use ($key) {
                if ('_header' === $value) {
                    return 'export.list.acp.'.$key;
                }

                if (null === $value || null === $thirdparty = $this->thirdPartyRepository->find($value)) {
                    return '';
                }

                return $this->thirdPartyRender->renderString($thirdparty, []);
            },
            'scopes' => function ($value) use ($key) {
                if ('_header' === $value) {
                    return 'export.list.acp.'.$key;
                }

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

                return implode(
                    '|',
                    array_map(
                        fn ($s) => $this->translatableStringHelper->localize($s),
                        json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR)
                    )
                );
            },
            'socialIssues' => function ($value) use ($key) {
                if ('_header' === $value) {
                    return 'export.list.acp.'.$key;
                }

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

                return implode(
                    '|',
                    array_map(
                        fn ($s) => $this->socialIssueRender->renderString($this->socialIssueRepository->find($s), []),
                        json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR)
                    )
                );
            },
            'step' => fn ($value) => match ($value) {
                '_header' => 'export.list.acp.step',
                null => '',
                AccompanyingPeriod::STEP_DRAFT => $this->translator->trans('course.draft'),
                AccompanyingPeriod::STEP_CONFIRMED => $this->translator->trans('course.confirmed'),
                AccompanyingPeriod::STEP_CLOSED => $this->translator->trans('course.closed'),
                AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT => $this->translator->trans('course.inactive_short'),
                AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG => $this->translator->trans('course.inactive_long'),
                default => $value,
            },
            'intensity' => fn ($value) => match ($value) {
                '_header' => 'export.list.acp.intensity',
                null => '',
                AccompanyingPeriod::INTENSITY_OCCASIONAL => $this->translator->trans('occasional'),
                AccompanyingPeriod::INTENSITY_REGULAR => $this->translator->trans('regular'),
                default => $value,
            },
            'centers' => function ($value) use ($key) {
                if ('_header' === $value) {
                    return 'export.list.acp.'.$key;
                }

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

                return implode(
                    '|',
                    array_map(
                        fn ($cid) => $this->centerRepository->find($cid)->getName(),
                        array_unique(
                            json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR)
                        )
                    )
                );
            },
            default => static function ($value) use ($key) {
                if ('_header' === $value) {
                    return 'export.list.acp.'.$key;
                }

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

                return $value;
            },
        };
    }

    public function addSelectClauses(QueryBuilder $qb, \DateTimeImmutable $calcDate): void
    {
        $qb->addSelect('acp.id AS acpId');
        $qb->addSelect('acp.createdAt AS acpCreatedAt');
        $qb->addSelect('acp.updatedAt AS acpUpdatedAt');

        // add the regular fields
        foreach (['openingDate', 'closingDate', 'confidential', 'emergency', 'intensity'] as $field) {
            $qb->addSelect(sprintf('acp.%s AS %s', $field, $field));
        }

        // add the field which are simple association
        $qb
            ->addSelect('IDENTITY(acp.createdBy) AS acpCreatedBy_id')
            ->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(acp.createdBy), \'d\', acp.createdAt) AS acpCreatedBy');
        $qb
            ->addSelect('IDENTITY(acp.updatedBy) AS acpUpdatedBy_id')
            ->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(acp.updatedBy), \'d\', acp.updatedAt) AS acpUpdatedBy');

        foreach (['origin' => 'label', 'closingMotive' => 'name', 'job' => 'label', 'administrativeLocation' => 'name'] as $entity => $field) {
            $qb
                ->leftJoin(sprintf('acp.%s', $entity), "{$entity}_t")
                ->addSelect(sprintf('%s_t.%s AS %s', $entity, $field, $entity));
        }

        // persons
        $qb
            ->addSelect(sprintf('(SELECT AGGREGATE(IDENTITY(participant_persons_n.person)) FROM %s participant_persons_n WHERE participant_persons_n.accompanyingPeriod = acp) AS acpParticipantPersons', AccompanyingPeriodParticipation::class))
            ->addSelect(sprintf('(SELECT AGGREGATE(IDENTITY(participant_persons_ids.person)) FROM %s participant_persons_ids WHERE participant_persons_ids.accompanyingPeriod = acp) AS acpParticipantPersonsIds', AccompanyingPeriodParticipation::class));

        // step at date
        $qb
            ->addSelect('stepHistory.step AS step')
            ->addSelect('stepHistory.startDate AS stepSince')
            ->leftJoin('acp.stepHistories', 'stepHistory')
            ->andWhere(
                $qb->expr()->andX(
                    $qb->expr()->lte('stepHistory.startDate', ':calcDate'),
                    $qb->expr()->orX($qb->expr()->isNull('stepHistory.endDate'), $qb->expr()->gt('stepHistory.endDate', ':calcDate'))
                )
            );

        // referree at date
        $qb
            ->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(userHistory.user), \'d\', userHistory.startDate) AS referrer')
            ->addSelect('userHistory.startDate AS referrerSince')
            ->leftJoin('acp.userHistories', 'userHistory')
            ->leftJoin('userHistory.user', 'referrer_t')
            ->andWhere(
                $qb->expr()->orX(
                    $qb->expr()->isNull('userHistory'),
                    $qb->expr()->andX(
                        $qb->expr()->lte('userHistory.startDate', ':calcDate'),
                        $qb->expr()->orX($qb->expr()->isNull('userHistory.endDate'), $qb->expr()->gt('userHistory.endDate', ':calcDate'))
                    )
                )
            );

        // location of the acp
        $qb
            ->addSelect('CASE WHEN locationHistory.personLocation IS NOT NULL THEN 1 ELSE 0 END AS locationIsPerson')
            ->addSelect('CASE WHEN locationHistory.personLocation IS NOT NULL THEN 0 ELSE 1 END AS locationIsTemp')
            ->addSelect('IDENTITY(locationHistory.personLocation) AS locationPersonName')
            ->addSelect('IDENTITY(locationHistory.personLocation) AS locationPersonId')
            ->leftJoin('acp.locationHistories', 'locationHistory')
            ->andWhere(
                $qb->expr()->orX(
                    $qb->expr()->isNull('locationHistory'),
                    $qb->expr()->andX(
                        $qb->expr()->lte('locationHistory.startDate', ':calcDate'),
                        $qb->expr()->orX($qb->expr()->isNull('locationHistory.endDate'), $qb->expr()->gt('locationHistory.endDate', ':calcDate'))
                    )
                )
            )
            ->leftJoin(
                PersonHouseholdAddress::class,
                'acpPersonAddress',
                Join::WITH,
                'locationHistory.personLocation = acpPersonAddress.person AND (acpPersonAddress.validFrom <= :calcDate AND (acpPersonAddress.validTo IS NULL OR acpPersonAddress.validTo > :calcDate))'
            )
            ->leftJoin(Address::class, 'acp_address', Join::WITH, 'COALESCE(IDENTITY(locationHistory.addressLocation), IDENTITY(acpPersonAddress.address)) = acp_address.id');

        $this->addressHelper->addSelectClauses(ExportAddressHelper::F_ALL, $qb, 'acp_address', 'acp_address_fields');

        // requestor
        $qb
            ->addSelect('CASE WHEN acp.requestorPerson IS NULL THEN 1 ELSE 0 END AS isRequestorPerson')
            ->addSelect('CASE WHEN acp.requestorPerson IS NULL THEN 0 ELSE 1 END AS isRequestorThirdParty')
            ->addSelect('IDENTITY(acp.requestorPerson) AS requestorPersonId')
            ->addSelect('IDENTITY(acp.requestorThirdParty) AS requestorThirdPartyId')
            ->addSelect('IDENTITY(acp.requestorPerson) AS requestorPerson')
            ->addSelect('IDENTITY(acp.requestorThirdParty) AS requestorThirdParty');

        $qb
            // scopes
            ->addSelect('(SELECT AGGREGATE(scope.name) FROM '.Scope::class.' scope WHERE scope MEMBER OF acp.scopes) AS scopes')
            // social issues
            ->addSelect('(SELECT AGGREGATE(socialIssue.id) FROM '.SocialIssue::class.' socialIssue WHERE socialIssue MEMBER OF acp.socialIssues) AS socialIssues');

        // duration
        $qb->addSelect('DATE_DIFF(COALESCE(acp.closingDate, :calcDate), acp.openingDate) AS duration');

        // centers
        $qb->addSelect(
            '(SELECT AGGREGATE(IDENTITY(cppch.center))
                        FROM '.AccompanyingPeriodParticipation::class.' part
                        JOIN '.PersonCenterHistory::class." cppch
                            WITH IDENTITY(cppch.person) = IDENTITY(part.person)
                            AND OVERLAPSI (cppch.startDate, cppch.endDate), (part.startDate, part.endDate) = 'TRUE'
                        WHERE part.accompanyingPeriod = acp
                    ) AS centers"
        );
        // add parameter
        $qb->setParameter('calcDate', $calcDate);
    }
}
