<?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\Serializer\Normalizer;

use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdComposition;
use Chill\PersonBundle\Entity\Household\HouseholdCompositionType;
use Chill\PersonBundle\Entity\Household\Position;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Household\MembersEditor;
use Chill\PersonBundle\Household\MembersEditorFactory;
use Symfony\Component\Serializer\Exception;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

class MembersEditorNormalizer implements DenormalizerAwareInterface, DenormalizerInterface
{
    use DenormalizerAwareTrait;

    public function __construct(private readonly MembersEditorFactory $factory) {}

    public function denormalize($data, $type, $format = null, array $context = [])
    {
        // some test about schema first...
        $this->performChecks($data);

        // route to "leave movement" (all concerned leave household)
        // or "move to another household" (all concerned go to another
        // household)
        if (null === $data['destination']) {
            return $this->denormalizeLeave($data, $type, $format, $context);
        }

        return $this->denormalizeMove($data, $type, $format, $context);
    }

    public function supportsDenormalization($data, $type, $format = null)
    {
        return MembersEditor::class === $type;
    }

    protected function denormalizeLeave($data, string $type, string $format, array $context = [])
    {
        $editor = $this->factory->createEditor(null);

        foreach ($data['concerned'] as $key => $concerned) {
            $person = $this->denormalizer->denormalize(
                $concerned['person'] ?? null,
                Person::class,
                $format,
                $context
            );
            $startDate = $this->denormalizer->denormalize(
                $concerned['start_date'] ?? null,
                \DateTimeImmutable::class,
                $format,
                $context
            );

            if (
                null === $person
                && null === $startDate
            ) {
                throw new Exception\InvalidArgumentException('position with '."key {$key} could not be denormalized: missing ".'person or start_date.');
            }

            $editor->leaveMovement($startDate, $person);
        }

        return $editor;
    }

    protected function denormalizeMove($data, string $type, string $format, array $context = [])
    {
        $householdContext = $context;
        $householdContext['groups'][] = 'create';

        $household = $this->denormalizer->denormalize(
            $data['destination'],
            Household::class,
            $format,
            $householdContext
        );

        if (null === $household) {
            throw new Exception\InvalidArgumentException('household could not be denormalized. Impossible to process');
        }

        $editor = $this->factory->createEditor($household);

        foreach ($data['concerned'] as $key => $concerned) {
            $person = $this->denormalizer->denormalize(
                $concerned['person'] ?? null,
                Person::class,
                $format,
                $context
            );

            if (\array_key_exists('position', $concerned)) {
                $position = $this->denormalizer->denormalize(
                    $concerned['position'] ?? null,
                    Position::class,
                    $format,
                    $context
                );
            } else {
                $position = null;
            }

            $startDate = $this->denormalizer->denormalize(
                $concerned['start_date'] ?? null,
                \DateTimeImmutable::class,
                $format,
                $context
            );

            $holder = (bool) $concerned['holder'] ?? false;
            $comment = (string) $concerned['comment'] ?? false;

            if (
                null === $person
                && null === $position
                && null === $startDate
            ) {
                throw new Exception\InvalidArgumentException('position with '."key {$key} could not be denormalized: missing ".'person, position or start_date.');
            }

            $editor->addMovement(
                $startDate,
                $person,
                $position,
                $holder,
                $comment
            );
        }

        if (null !== $data['composition']) {
            $compositionType = $this->denormalizer->denormalize($data['composition']['household_composition_type'], HouseholdCompositionType::class, $format, $context);
            $numberOfChildren = $data['composition']['number_of_children'];
            $numberOfDependents = $data['composition']['number_of_dependents'];
            $numberOfDependentsWithDisabilities = $data['composition']['number_of_dependents_with_disabilities'];
            $startDate = $this->denormalizer->denormalize($data['composition']['start_date'], \DateTimeImmutable::class, $format, $context);

            if (null === $compositionType) {
                throw new \UnexpectedValueException('composition type cannot be null');
            }

            $householdComposition = (new HouseholdComposition())
                ->setHouseholdCompositionType($compositionType)
                ->setNumberOfChildren($numberOfChildren)
                ->setNumberOfDependents($numberOfDependents)
                ->setNumberOfDependentsWithDisabilities($numberOfDependentsWithDisabilities)
                ->setStartDate($startDate);

            $household->addComposition($householdComposition);
        }

        return $editor;
    }

    private function performChecks($data): void
    {
        if (
            null === $data['concerned'] ?? null
            && false === \is_array('concerned')
        ) {
            throw new Exception\UnexpectedValueException("The schema does not have any key 'concerned'");
        }

        if (false === \array_key_exists('destination', $data)) {
            throw new Exception\UnexpectedValueException("The schema does not have any key 'destination'");
        }
    }
}
