<?php

declare(strict_types=1);

namespace App\Tests\Functional\Mcp\Directory;

use App\Module\Core\Domain\Entity\User;
use App\Module\Directory\Domain\Entity\Client;
use App\Module\Directory\Domain\Entity\Prestataire;
use App\Module\Directory\Domain\Entity\Prospect;
use App\Module\Directory\Domain\Enum\ReportType;
use App\Module\Directory\Domain\Repository\ClientRepositoryInterface;
use App\Module\Directory\Domain\Repository\CommercialReportRepositoryInterface;
use App\Module\Directory\Domain\Repository\PrestataireRepositoryInterface;
use App\Module\Directory\Domain\Repository\ProspectRepositoryInterface;
use App\Module\Directory\Infrastructure\Mcp\Tool\CreateCommercialReportTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\DeleteCommercialReportTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\GetCommercialReportTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\ListCommercialReportsTool;
use App\Module\Directory\Infrastructure\Mcp\Tool\UpdateCommercialReportTool;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;

/**
 * @internal
 */
class CommercialReportLifecycleTest extends KernelTestCase
{
    private EntityManagerInterface $em;
    private User $admin;
    private Client $client;
    private Prospect $prospect;
    private Prestataire $prestataire;

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

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

        $this->client = new Client();
        $this->client->setName('Test Client '.uniqid());
        $this->em->persist($this->client);

        $this->prospect = new Prospect();
        $this->prospect->setCompany('Test Prospect '.uniqid());
        $this->em->persist($this->prospect);

        $this->prestataire = new Prestataire();
        $this->prestataire->setName('Test Prestataire '.uniqid());
        $this->em->persist($this->prestataire);

        $this->em->flush();
    }

    public function testCreateRequiresExactlyOneParent(): void
    {
        try {
            ($this->createTool())('subject', null, null, null);
            self::fail('Expected error when no parent provided.');
        } catch (InvalidArgumentException $e) {
            self::assertStringContainsString('Exactly one of clientId, prospectId or prestataireId', $e->getMessage());
        }

        try {
            ($this->createTool())('subject', $this->client->getId(), null, $this->prestataire->getId());
            self::fail('Expected error when two parents provided.');
        } catch (InvalidArgumentException $e) {
            self::assertStringContainsString('Exactly one of clientId, prospectId or prestataireId', $e->getMessage());
        }
    }

    public function testCreateRejectsInvalidType(): void
    {
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('Invalid type "lunch". Allowed: note, call, meeting, email.');
        ($this->createTool())('Lunch at noon', $this->client->getId(), null, null, null, null, 'lunch');
    }

    public function testCreateAcceptsAllValidTypes(): void
    {
        foreach (['note', 'call', 'meeting', 'email'] as $type) {
            $data = json_decode(
                ($this->createTool())('subject', $this->client->getId(), null, null, null, '2026-01-15', $type),
                true,
            );
            self::assertSame($type, $data['type']);
        }
    }

    public function testCreateDefaultsTypeToNote(): void
    {
        $data = json_decode(($this->createTool())('subject', $this->client->getId(), null, null), true);

        self::assertSame(ReportType::Note->value, $data['type']);
    }

    public function testCreateRejectsInvalidDate(): void
    {
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('Invalid occurredAt "not-a-date"');
        ($this->createTool())('subject', $this->client->getId(), null, null, null, 'not-a-date');
    }

    public function testCreateDefaultsOccurredAtToToday(): void
    {
        $data = json_decode(($this->createTool())('subject', $this->client->getId(), null, null), true);

        self::assertSame(new DateTimeImmutable('today')->format('Y-m-d'), $data['occurredAt']);
    }

    public function testCreateAutoFillsAuthorFromCurrentUser(): void
    {
        $this->loginAdmin();

        $data = json_decode(($this->createTool())('subject', $this->client->getId(), null, null), true);

        self::assertNotNull($data['author']);
        self::assertSame($this->admin->getId(), $data['author']['id']);
        self::assertSame($this->admin->getUsername(), $data['author']['username']);
    }

    public function testGetReturnsReport(): void
    {
        $created = json_decode(
            ($this->createTool())('My subject', $this->prestataire->getId() ? null : null, null, $this->prestataire->getId(), 'body text', '2026-03-01', 'meeting'),
            true,
        );

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

        self::assertSame('My subject', $data['subject']);
        self::assertSame('body text', $data['body']);
        self::assertSame('2026-03-01', $data['occurredAt']);
        self::assertSame('meeting', $data['type']);
        self::assertSame($this->prestataire->getId(), $data['prestataireId']);
        self::assertSame([], $data['documents']);
    }

    public function testListOrderedByOccurredAtDesc(): void
    {
        ($this->createTool())('oldest', $this->client->getId(), null, null, null, '2026-01-01');
        ($this->createTool())('newest', $this->client->getId(), null, null, null, '2026-12-01');
        ($this->createTool())('middle', $this->client->getId(), null, null, null, '2026-06-15');

        $data = json_decode(($this->listTool())($this->client->getId(), null, null), true);

        self::assertCount(3, $data);
        self::assertSame('newest', $data[0]['subject']);
        self::assertSame('middle', $data[1]['subject']);
        self::assertSame('oldest', $data[2]['subject']);
    }

    public function testListRejectsMultipleFilters(): void
    {
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('At most one of clientId, prospectId or prestataireId');
        ($this->listTool())($this->client->getId(), $this->prospect->getId(), null);
    }

    public function testUpdateChangesTypeAndDate(): void
    {
        $created = json_decode(($this->createTool())('s', $this->client->getId(), null, null, null, '2026-01-01', 'note'), true);

        $data = json_decode(($this->updateTool())((int) $created['id'], 'new subject', null, '2026-02-02', 'call'), true);

        self::assertSame('new subject', $data['subject']);
        self::assertSame('2026-02-02', $data['occurredAt']);
        self::assertSame('call', $data['type']);
    }

    public function testUpdateRejectsInvalidType(): void
    {
        $created = json_decode(($this->createTool())('s', $this->client->getId(), null, null), true);

        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('Invalid type "lunch"');
        ($this->updateTool())((int) $created['id'], null, null, null, 'lunch');
    }

    public function testDeleteRemovesReport(): void
    {
        $created = json_decode(($this->createTool())('Bye', $this->client->getId(), null, null), true);
        $id      = (int) $created['id'];

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

        self::assertTrue($data['success']);

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

    private function loginAdmin(): void
    {
        $token = new UsernamePasswordToken($this->admin, 'main', $this->admin->getRoles());
        self::getContainer()->get(TokenStorageInterface::class)->setToken($token);
    }

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

        return $security;
    }

    private function createTool(): CreateCommercialReportTool
    {
        $c = self::getContainer();

        return new CreateCommercialReportTool(
            $this->em,
            $c->get(ClientRepositoryInterface::class),
            $c->get(ProspectRepositoryInterface::class),
            $c->get(PrestataireRepositoryInterface::class),
            $this->securityFor(),
        );
    }

    private function getTool(): GetCommercialReportTool
    {
        return new GetCommercialReportTool(
            self::getContainer()->get(CommercialReportRepositoryInterface::class),
            $this->securityFor(),
        );
    }

    private function listTool(): ListCommercialReportsTool
    {
        return new ListCommercialReportsTool(
            self::getContainer()->get(CommercialReportRepositoryInterface::class),
            $this->securityFor(),
        );
    }

    private function updateTool(): UpdateCommercialReportTool
    {
        return new UpdateCommercialReportTool(
            self::getContainer()->get(CommercialReportRepositoryInterface::class),
            $this->em,
            $this->securityFor(),
        );
    }

    private function deleteTool(): DeleteCommercialReportTool
    {
        return new DeleteCommercialReportTool(
            self::getContainer()->get(CommercialReportRepositoryInterface::class),
            $this->em,
            $this->securityFor(),
        );
    }
}
