<?php

declare(strict_types=1);

namespace App\Tests\Functional\Mcp\Directory;

use App\Module\Core\Domain\Entity\User;
use App\Module\Directory\Domain\Repository\AddressRepositoryInterface;
use App\Module\Directory\Domain\Repository\CommercialReportRepositoryInterface;
use App\Module\Directory\Domain\Repository\ContactRepositoryInterface;
use App\Module\Directory\Domain\Repository\PrestataireRepositoryInterface;
use App\Module\Directory\Infrastructure\Mcp\Tool\CreatePrestataireTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\DeletePrestataireTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\GetPrestataireTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\ListPrestatairesTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\UpdatePrestataireTool;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

/**
 * @internal
 */
class PrestataireLifecycleTest extends KernelTestCase
{
    private EntityManagerInterface $em;
    private User $admin;

    protected function setUp(): void
    {
        self::bootKernel();
        $this->em = self::getContainer()->get(EntityManagerInterface::class);

        $this->admin = new User();
        $this->admin->setUsername('mcp-prest-admin-'.uniqid());
        $this->admin->setPassword('x');
        $this->admin->setRoles(['ROLE_ADMIN']);
        $this->em->persist($this->admin);
        $this->em->flush();
    }

    public function testCreatePersistsAllFields(): void
    {
        $json = ($this->createTool(admin: true))('ACME Cleaning', 'contact@acme.example', '+33100000000', 'https://acme.example');
        $data = json_decode($json, true);

        self::assertIsInt($data['id']);
        self::assertSame('ACME Cleaning', $data['name']);
        self::assertSame('contact@acme.example', $data['email']);
        self::assertSame('+33100000000', $data['phone']);
        self::assertSame('https://acme.example', $data['website']);
    }

    public function testCreateRequiresAdmin(): void
    {
        $this->expectException(AccessDeniedException::class);
        ($this->createTool(admin: false))('Should not pass');
    }

    public function testGetReturnsEmptyCollectionsWhenNoChildren(): void
    {
        $created = json_decode(($this->createTool(admin: true))('Lonely Prest'), true);

        $json = ($this->getTool(admin: true))((int) $created['id']);
        $data = json_decode($json, true);

        self::assertSame($created['id'], $data['id']);
        self::assertSame([], $data['contacts']);
        self::assertSame([], $data['addresses']);
        self::assertSame([], $data['reports']);
    }

    public function testGetUnknownIdThrows(): void
    {
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('Prestataire with ID 999999 not found.');
        ($this->getTool(admin: true))(999999);
    }

    public function testUpdateOnlyTouchesProvidedFields(): void
    {
        $created = json_decode(($this->createTool(admin: true))('Before', 'before@x.test', '+33000000000', 'https://before.test'), true);

        $json = ($this->updateTool(admin: true))((int) $created['id'], null, 'after@x.test', null, null);
        $data = json_decode($json, true);

        self::assertSame('Before', $data['name']);              // unchanged
        self::assertSame('after@x.test', $data['email']);       // changed
        self::assertSame('+33000000000', $data['phone']);       // unchanged
        self::assertSame('https://before.test', $data['website']); // unchanged
    }

    public function testListReturnsAllPrestatairesOrderedByName(): void
    {
        // Unique prefix isolates this test from data leaked by prior PHPUnit
        // runs (DAMA rollback is not active in this project).
        $prefix = 'list-test-'.uniqid().'-';
        ($this->createTool(admin: true))($prefix.'Zeta');
        ($this->createTool(admin: true))($prefix.'Alpha');
        ($this->createTool(admin: true))($prefix.'Mu');

        $data  = json_decode(($this->listTool(admin: true))(), true);
        $names = array_values(array_filter(
            array_column($data, 'name'),
            fn ($n) => str_starts_with((string) $n, $prefix),
        ));

        self::assertSame([$prefix.'Alpha', $prefix.'Mu', $prefix.'Zeta'], $names);
    }

    public function testDeleteRemovesPrestataire(): void
    {
        $created = json_decode(($this->createTool(admin: true))('To be removed'), true);
        $id      = (int) $created['id'];

        $json = ($this->deleteTool(admin: true))($id);
        $data = json_decode($json, true);

        self::assertTrue($data['success']);
        self::assertStringContainsString('"To be removed"', $data['message']);

        $this->em->clear();
        self::assertNull(self::getContainer()->get(PrestataireRepositoryInterface::class)->findById($id));
    }

    private function securityFor(bool $admin): Security
    {
        $security = $this->createMock(Security::class);
        $security->method('isGranted')->willReturn($admin);
        $security->method('getUser')->willReturn($admin ? $this->admin : null);

        return $security;
    }

    private function createTool(bool $admin): CreatePrestataireTool
    {
        return new CreatePrestataireTool(
            $this->em,
            $this->securityFor($admin),
        );
    }

    private function getTool(bool $admin): GetPrestataireTool
    {
        $c = self::getContainer();

        return new GetPrestataireTool(
            $c->get(PrestataireRepositoryInterface::class),
            $c->get(ContactRepositoryInterface::class),
            $c->get(AddressRepositoryInterface::class),
            $c->get(CommercialReportRepositoryInterface::class),
            $this->securityFor($admin),
        );
    }

    private function updateTool(bool $admin): UpdatePrestataireTool
    {
        return new UpdatePrestataireTool(
            self::getContainer()->get(PrestataireRepositoryInterface::class),
            $this->em,
            $this->securityFor($admin),
        );
    }

    private function listTool(bool $admin): ListPrestatairesTool
    {
        return new ListPrestatairesTool(
            self::getContainer()->get(PrestataireRepositoryInterface::class),
            $this->securityFor($admin),
        );
    }

    private function deleteTool(bool $admin): DeletePrestataireTool
    {
        return new DeletePrestataireTool(
            self::getContainer()->get(PrestataireRepositoryInterface::class),
            $this->em,
            $this->securityFor($admin),
        );
    }
}
