<?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\BudgetBundle\Tests\Service\Summary;

use Chill\BudgetBundle\Entity\ChargeKind;
use Chill\BudgetBundle\Entity\ResourceKind;
use Chill\BudgetBundle\Repository\ChargeKindRepositoryInterface;
use Chill\BudgetBundle\Repository\ResourceKindRepositoryInterface;
use Chill\BudgetBundle\Service\Summary\SummaryBudget;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;

/**
 * @internal
 *
 * @coversNothing
 */
final class SummaryBudgetTest extends TestCase
{
    use ProphecyTrait;

    public function testGenerateSummaryForPerson(): void
    {
        $queryCharges = $this->prophesize(AbstractQuery::class);
        $queryCharges->getResult()->willReturn([
            [
                'sum' => 250.0,
                'comment' => '',
                'kind_id' => 1, // kind: rental
            ],
        ]);
        $queryCharges->setParameters(Argument::type('array'))
            ->will(static fn ($args, $query) => $query);

        $queryResources = $this->prophesize(AbstractQuery::class);
        $queryResources->getResult()->willReturn([
            [
                'sum' => 1500.0,
                'comment' => '',
                'kind_id' => 2, // kind: 'salary',
            ],
        ]);
        $queryResources->setParameters(Argument::type('array'))
            ->will(static fn ($args, $query) => $query);

        $em = $this->prophesize(EntityManagerInterface::class);
        $em->createNativeQuery(Argument::type('string'), Argument::type(Query\ResultSetMapping::class))
            ->will(static function ($args) use ($queryResources, $queryCharges) {
                if (str_contains((string) $args[0], 'chill_budget.resource')) {
                    return $queryResources->reveal();
                }

                if (str_contains((string) $args[0], 'chill_budget.charge')) {
                    return $queryCharges->reveal();
                }

                throw new \RuntimeException('this query does not have a stub counterpart: '.$args[0]);
            });

        $chargeRepository = $this->prophesize(ChargeKindRepositoryInterface::class);
        $chargeRepository->findAll()->willReturn([
            $rental = (new ChargeKind())->setKind('rental')->setName(['fr' => 'Rental']),
            $other = (new ChargeKind())->setKind('other')->setName(['fr' => 'Other']),
        ]);
        $chargeRepository->find(1)->willReturn($rental);
        $chargeRepository->findOneByKind('rental')->willReturn($rental);
        $chargeRepository->findOneByKind('other')->willReturn($other);

        $resourceRepository = $this->prophesize(ResourceKindRepositoryInterface::class);
        $resourceRepository->findAll()->willReturn([
            $salary = (new ResourceKind())->setKind('salary')->setName(['fr' => 'Salary']),
            $misc = (new ResourceKind())->setKind('misc')->setName(['fr' => 'Misc']),
        ]);
        $resourceRepository->find(2)->willReturn($salary);
        $resourceRepository->findOneByKind('salary')->willReturn($salary);
        $resourceRepository->findOneByKind('misc')->willReturn($misc);

        $translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class);
        $translatableStringHelper->localize(Argument::type('array'))->will(static fn ($arg) => $arg[0]['fr']);

        $person = new Person();
        $personReflection = new \ReflectionClass($person);
        $personIdReflection = $personReflection->getProperty('id');
        $personIdReflection->setAccessible(true);
        $personIdReflection->setValue($person, 1);

        $household = new Household();
        $householdReflection = new \ReflectionClass($household);
        $householdId = $householdReflection->getProperty('id');
        $householdId->setAccessible(true);
        $householdId->setValue($household, 1);
        $householdMember = (new HouseholdMember())->setPerson($person)
            ->setStartDate(new \DateTimeImmutable('1 month ago'));
        $household->addMember($householdMember);

        $summaryBudget = new SummaryBudget(
            $em->reveal(),
            $translatableStringHelper->reveal(),
            $resourceRepository->reveal(),
            $chargeRepository->reveal()
        );

        $summary = $summaryBudget->getSummaryForPerson($person);
        $summaryForHousehold = $summaryBudget->getSummaryForHousehold($household);

        // we check the structure for the summary. The structure is the same for household
        // and persons

        $expected = [
            'charges' => [
                'rental' => ['sum' => 250.0, 'comment' => '', 'label' => 'Rental'],
                'other' => ['sum' => 0.0, 'comment' => '', 'label' => 'Other'],
            ],
            'resources' => [
                'salary' => ['sum' => 1500.0, 'comment' => '', 'label' => 'Salary'],
                'misc' => ['sum' => 0.0, 'comment' => '', 'label' => 'Misc'],
            ],
        ];

        foreach ([$summaryForHousehold, $summary] as $summary) {
            $this->assertIsArray($summary);
            $this->assertEqualsCanonicalizing(['charges', 'resources'], array_keys($summary));
            $this->assertEqualsCanonicalizing(['rental', 'other'], array_keys($summary['charges']));
            $this->assertEqualsCanonicalizing(['salary', 'misc'], array_keys($summary['resources']));

            foreach ($expected as $resCha => $contains) {
                foreach ($contains as $kind => $row) {
                    $this->assertEqualsCanonicalizing($row, $summary[$resCha][$kind]);
                }
            }
        }
    }
}
