<?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\DataFixtures\ORM;

use Chill\MainBundle\DataFixtures\ORM\LoadCenters;
use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\PostalCode;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\Position;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Household\MembersEditorFactory;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ObjectManager;
use Nelmio\Alice\Loader\NativeLoader;

class LoadHousehold extends Fixture implements DependentFixtureInterface
{
    private const NUMBER_OF_HOUSEHOLD = 10;

    private readonly NativeLoader $loader;

    private array $personIds;

    public function __construct(private readonly MembersEditorFactory $editorFactory, private readonly EntityManagerInterface $em)
    {
        $this->loader = new NativeLoader();
    }

    public function getDependencies(): array
    {
        return [
            LoadPeople::class,
            LoadHouseholdPosition::class,
        ];
    }

    public function load(ObjectManager $manager): void
    {
        // generate two times the participation. This will lead to
        // some movement in participation (same people in two differents
        // households)

        $this->preparePersonIds();

        $this->generateHousehold(
            $manager,
            \DateTimeImmutable::createFromFormat('Y-m-d', '2010-01-01')
        );

        $this->preparePersonIds();

        $this->generateHousehold(
            $manager,
            \DateTimeImmutable::createFromFormat('Y-m-d', '2015-01-01')
        );

        $manager->flush();
    }

    private function addAddressToHousehold(Household $household, \DateTimeImmutable $date, ObjectManager $manager)
    {
        if (\random_int(0, 10) > 8) {
            //  20% of household without address
            return;
        }

        $nb = \random_int(1, 6);

        $i = 0;

        while ($i < $nb) {
            $address = $this->createAddress();
            $address->setValidFrom(\DateTime::createFromImmutable($date));

            if (\random_int(0, 20) < 1) {
                $date = $date->add(new \DateInterval('P'.\random_int(8, 52).'W'));
                $address->setValidTo(\DateTime::createFromImmutable($date));
            }

            $household->addAddress($address);
            $manager->persist($address);

            $date = $date->add(new \DateInterval('P'.\random_int(8, 52).'W'));
            ++$i;
        }
    }

    private function createAddress(): Address
    {
        $objectSet = $this->loader->loadData([
            Address::class => [
                'address1' => [
                    'street' => '<fr_FR:streetName()>',
                    'streetNumber' => '<fr_FR:buildingNumber()>',
                    'postCode' => $this->getPostalCode(),
                ],
            ],
        ]);

        return $objectSet->getObjects()['address1'];
    }

    private function generateHousehold(ObjectManager $manager, \DateTimeImmutable $startDate)
    {
        for ($i = 0; self::NUMBER_OF_HOUSEHOLD > $i; ++$i) {
            $household = new Household();
            $manager->persist($household);

            $this->addAddressToHousehold($household, clone $startDate, $manager);

            $movement = $this->editorFactory->createEditor($household);

            // load adults
            $k = 0;

            foreach ($this->getRandomPersons(1, 3) as $person) {
                $date = $startDate->add(new \DateInterval('P'.\random_int(1, 200).'W'));
                $position = $this->getReference(LoadHouseholdPosition::ADULT, Position::class);

                $movement->addMovement($date, $person, $position, 0 === $k, 'self generated');
                ++$k;
            }

            // load children
            foreach ($this->getRandomPersons(0, 3) as $person) {
                $date = $startDate->add(new \DateInterval('P'.\random_int(1, 200).'W'));
                $position = $this->getReference(LoadHouseholdPosition::CHILD, Position::class);

                $movement->addMovement($date, $person, $position, 0 === $k, 'self generated');
                ++$k;
            }

            // load children out
            foreach ($this->getRandomPersons(0, 2) as $person) {
                $date = $startDate->add(new \DateInterval('P'.\random_int(1, 200).'W'));
                $position = $this->getReference(LoadHouseholdPosition::CHILD_OUT, Position::class);

                $movement->addMovement($date, $person, $position, 0 === $k, 'self generated');
                ++$k;
            }

            foreach ($movement->getPersistable() as $obj) {
                $manager->persist($obj);
            }
        }
    }

    private function getPostalCode(): PostalCode
    {
        $ref = LoadPostalCodes::$refs[\array_rand(LoadPostalCodes::$refs)];

        return $this->getReference($ref, PostalCode::class);
    }

    private function getRandomPersons(int $min, int $max): array
    {
        $persons = [];

        $nb = \random_int($min, $max);

        for ($i = 0; $i < $nb; ++$i) {
            $personId = \array_pop($this->personIds)['id'];
            $persons[] = $this->em->getRepository(Person::class)->find($personId);
        }

        return $persons;
    }

    private function preparePersonIds()
    {
        $centers = LoadCenters::$centers;

        // @TODO: Remove this and make this service stateless
        $this->personIds = $this->em
            ->createQuery(
                'SELECT p.id FROM '.Person::class.' p '.
                'WHERE EXISTS( '.
                    'SELECT 1 FROM '.PersonCenterHistory::class.' pch '.
                    'JOIN pch.center c '.
                    'WHERE pch.person = p.id '.
                    'AND c.name IN (:authorized_centers)'.
                ')'
            )
            ->setParameter('authorized_centers', $centers)
            ->getScalarResult();

        \shuffle($this->personIds);
    }
}
