<?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\ActivityBundle\Service\DocGenerator;

use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityPresence;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Service\DocGenerator\AccompanyingPeriodContext;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use libphonenumber\PhoneNumber;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

/**
 * @implements DocGeneratorContextWithPublicFormInterface<AccompanyingPeriod>
 * @implements DocGeneratorContextWithAdminFormInterface<AccompanyingPeriod>
 */
class ListActivitiesByAccompanyingPeriodContext implements
    DocGeneratorContextWithAdminFormInterface,
    DocGeneratorContextWithPublicFormInterface
{
    public function __construct(
        private readonly AccompanyingPeriodContext $accompanyingPeriodContext,
        private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
        private readonly NormalizerInterface $normalizer,
        private readonly PersonRepository $personRepository,
        private readonly SocialActionRepository $socialActionRepository,
        private readonly SocialIssueRepository $socialIssueRepository,
        private readonly ThirdPartyRepository $thirdPartyRepository,
        private readonly TranslatableStringHelperInterface $translatableStringHelper,
        private readonly UserRepository $userRepository,
    ) {}

    public function adminFormReverseTransform(array $data): array
    {
        return $this->accompanyingPeriodContext->adminFormReverseTransform($data);
    }

    public function adminFormTransform(array $data): array
    {
        return $this->accompanyingPeriodContext->adminFormTransform($data);
    }

    public function buildAdminForm(FormBuilderInterface $builder): void
    {
        $this->accompanyingPeriodContext->buildAdminForm($builder);
    }

    public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
    {
        $this->accompanyingPeriodContext->buildPublicForm($builder, $template, $entity);

        $builder
            ->add('myActivitiesOnly', CheckboxType::class, [
                'required' => false,
                'label' => 'docgen.myActivitiesOnly',
            ])
            ->add('myWorksOnly', CheckboxType::class, [
                'required' => false,
                'label' => 'docgen.myWorksOnly',
            ]);
    }

    public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array
    {
        $denormalized = $this->accompanyingPeriodContext->contextGenerationDataDenormalize($template, $entity, $data);

        foreach (['myActivitiesOnly', 'myWorksOnly'] as $k) {
            $denormalized[$k] = $data[$k];
        }

        return $denormalized;
    }

    public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
    {
        $normalized = $this->accompanyingPeriodContext->contextGenerationDataNormalize($template, $entity, $data);

        foreach (['myActivitiesOnly', 'myWorksOnly'] as $k) {
            $normalized[$k] = $data[$k] ?? false;
        }

        return $normalized;
    }

    /**
     * @return list<Activity>
     */
    private function filterActivitiesByUser(array $activities, User $user): array
    {
        return array_values(
            array_filter(
                $activities,
                function ($activity) use ($user) {
                    $u = $activity['user'];

                    if (null !== $u && $u['username'] === $user->getUserIdentifier()) {
                        return true;
                    }

                    $activityUsernames = array_map(static fn ($user) => $user['username'], $activity['users'] ?? []);

                    return \in_array($user->getUserIdentifier(), $activityUsernames, true);
                }
            )
        );
    }

    /**
     * @return list<AccompanyingPeriod\AccompanyingPeriodWork>
     */
    private function filterWorksByUser(array $works, User $user): array
    {
        return array_values(
            array_filter(
                $works,
                function ($work) use ($user) {
                    $workUsernames = [];
                    foreach ($work['referrers'] as $referrer) {
                        $workUsernames[] = $referrer['username'];
                    }

                    return \in_array($user->getUserIdentifier(), $workUsernames, true);
                }
            )
        );
    }

    public function getData(DocGeneratorTemplate $template, object $entity, array $contextGenerationData = []): array
    {
        $data = $this->accompanyingPeriodContext->getData($template, $entity, $contextGenerationData);

        $activities = $this->getActivitiesSimplified($entity);
        $myActivitiesOnly = $contextGenerationData['myActivitiesOnly'];

        if ($myActivitiesOnly && isset($contextGenerationData['creator'])) {
            $activities = $this->filterActivitiesByUser($activities, $contextGenerationData['creator']);
        }

        $data['activities'] = $activities;

        $myWorksOnly = $contextGenerationData['myWorksOnly'];

        if ($myWorksOnly && isset($contextGenerationData['creator'])) {
            $data['course']['works'] = $this->filterWorksByUser($data['course']['works'], $contextGenerationData['creator']);
        }

        return $data;
    }

    public function getDescription(): string
    {
        return 'docgen.Accompanying period with a list of activities description';
    }

    public function getEntityClass(): string
    {
        return AccompanyingPeriod::class;
    }

    public function getFormData(DocGeneratorTemplate $template, $entity): array
    {
        return $this->accompanyingPeriodContext->getFormData($template, $entity);
    }

    public static function getKey(): string
    {
        return self::class;
    }

    public function getName(): string
    {
        return 'docgen.Accompanying period with a list of activities';
    }

    public function hasAdminForm(): bool
    {
        return $this->accompanyingPeriodContext->hasAdminForm();
    }

    public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
    {
        return true;
    }

    public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
    {
        $this->accompanyingPeriodContext->storeGenerated($template, $storedObject, $entity, $contextGenerationData);
    }

    private function getActivitiesSimplified(AccompanyingPeriod $period)
    {
        $activities =
            $this->activityACLAwareRepository->findByAccompanyingPeriodSimplified($period);
        $results = [];

        foreach ($activities as $row) {
            $activity = $row[0];

            $user = match (null === $row['userId']) {
                false => $this->userRepository->find($row['userId']),
                true => null,
            };

            $activity['user'] = $this->normalizer->normalize($user, 'docgen', [
                AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => User::class,
            ]);

            $activity['date'] = $this->normalizer->normalize($activity['date'], 'docgen', [
                AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => \DateTime::class,
            ]);

            if (null === $activity['location']) {
                $activity['location'] = $this->normalizer->normalize(null, 'docgen', [
                    AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Location::class,
                ]);
                $activity['location']['type'] = 'location';
            } else {
                $activity['location']['isNull'] = false;
                $activity['location']['type'] = 'location';

                foreach (['1', '2'] as $key) {
                    $activity['location']['phonenumber'.$key] = $this->normalizer->normalize(
                        $activity['location']['phonenumber'.$key],
                        'docgen',
                        [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => PhoneNumber::class]
                    );
                }
            }

            if (is_numeric($activity['location']['locationType']['id'])) {
                $activity['location']['locationType']['title'] = $this->translatableStringHelper->localize(
                    $activity['location']['locationType']['title']
                );
                $activity['location']['locationType']['isNull'] = false;
                $activity['location']['locationType']['type'] = 'locationType';
            }

            if (null !== $activity['activityType']) {
                $activity['activityType']['name'] = $this->translatableStringHelper->localize(
                    $activity['activityType']['name']
                );
                $activity['activityType']['isNull'] = false;
                $activity['activityType']['type'] = 'activityType';
            } else {
                $activity['activityType'] = $this->normalizer->normalize(null, 'docgen', [
                    AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityType::class,
                ]);
            }

            if (null !== $activity['attendee']) {
                $activity['attendee']['name'] = $this->translatableStringHelper->localize(
                    $activity['attendee']['name']
                );
                $activity['attendee']['isNull'] = false;
                $activity['attendee']['type'] = 'activityPresence';
            } else {
                $activity['attendee'] = $this->normalizer->normalize(null, 'docgen', [
                    AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ActivityPresence::class,
                ]);
            }

            $activity['comment'] = (string) $row['comment'];
            $activity['travelTimeMinute'] = $row['travelTimeMinute'];
            $activity['durationTimeMinute'] = $row['durationTimeMinute'];

            if (null !== $row['userIds']) {
                foreach ($row['userIds'] as $id) {
                    $activity['users'][] = $this->normalizer->normalize(
                        $this->userRepository->find($id),
                        'docgen',
                        [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => User::class]
                    );
                }
            } else {
                $activity['users'] = [];
            }

            if (null !== $row['personIds']) {
                foreach ($row['personIds'] as $id) {
                    $activity['persons'][] = $this->normalizer->normalize(
                        $this->personRepository->find($id),
                        'docgen',
                        [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Person::class]
                    );
                }
            } else {
                $activity['persons'] = [];
            }

            if (null !== $row['thirdPartyIds']) {
                foreach ($row['thirdPartyIds'] as $id) {
                    $activity['thirdParties'][] = $this->normalizer->normalize(
                        $this->thirdPartyRepository->find($id),
                        'docgen',
                        [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => ThirdParty::class]
                    );
                }
            } else {
                $activity['thirdParties'] = [];
            }

            if (null !== $row['socialActionIds']) {
                foreach ($row['socialActionIds'] as $id) {
                    $activity['socialActions'][] = $this->normalizer->normalize(
                        $this->socialActionRepository->find($id),
                        'docgen',
                        [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => SocialAction::class]
                    );
                }
            } else {
                $activity['socialActions'] = [];
            }

            if (null !== $row['socialIssueIds']) {
                foreach ($row['socialIssueIds'] as $id) {
                    $activity['socialIssues'][] = $this->normalizer->normalize(
                        $this->socialIssueRepository->find($id),
                        'docgen',
                        [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => SocialIssue::class]
                    );
                }
            } else {
                $activity['socialIssues'] = [];
            }

            $results[] = $activity;
        }

        return $results;
    }
}
