<?php

declare(strict_types=1);

namespace App\Tests;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use ApiPlatform\Symfony\Bundle\Test\Client;
use App\Entity\Composant;
use App\Entity\ComposantConstructeurLink;
use App\Entity\ComposantPieceSlot;
use App\Entity\ComposantProductSlot;
use App\Entity\ComposantSubcomponentSlot;
use App\Entity\Constructeur;
use App\Entity\ConstructeurCategorie;
use App\Entity\ConstructeurTelephone;
use App\Entity\CustomField;
use App\Entity\CustomFieldValue;
use App\Entity\Machine;
use App\Entity\MachineComponentLink;
use App\Entity\MachineConstructeurLink;
use App\Entity\MachinePieceLink;
use App\Entity\MachineProductLink;
use App\Entity\ModelType;
use App\Entity\Piece;
use App\Entity\PieceConstructeurLink;
use App\Entity\PieceProductSlot;
use App\Entity\Product;
use App\Entity\ProductConstructeurLink;
use App\Entity\Profile;
use App\Entity\Site;
use App\Enum\ModelCategory;
use Doctrine\ORM\EntityManagerInterface;
use stdClass;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

abstract class AbstractApiTestCase extends ApiTestCase
{
    private const DEFAULT_PASSWORD = 'test1234';

    protected function tearDown(): void
    {
        $this->getEntityManager()->clear();

        parent::tearDown();
    }

    protected function getEntityManager(): EntityManagerInterface
    {
        return static::getContainer()->get('doctrine')->getManager();
    }

    protected function getPasswordHasher(): UserPasswordHasherInterface
    {
        return static::getContainer()->get(UserPasswordHasherInterface::class);
    }

    // ── Auth helpers ────────────────────────────────────────────────

    protected function createAuthenticatedClient(string $role): Client
    {
        $profile = $this->createProfile(roles: [$role], password: self::DEFAULT_PASSWORD);

        $client = static::createClient();
        $client->request('POST', '/api/session/profile', [
            'json' => [
                'profileId' => $profile->getId(),
                'password'  => self::DEFAULT_PASSWORD,
            ],
        ]);

        return $client;
    }

    protected function createViewerClient(): Client
    {
        return $this->createAuthenticatedClient('ROLE_VIEWER');
    }

    protected function createGestionnaireClient(): Client
    {
        return $this->createAuthenticatedClient('ROLE_GESTIONNAIRE');
    }

    protected function createAdminClient(): Client
    {
        return $this->createAuthenticatedClient('ROLE_ADMIN');
    }

    protected function createUnauthenticatedClient(): Client
    {
        return static::createClient();
    }

    // ── MCP helpers ──────────────────────────────────────────────────

    /**
     * @return array{client: Client, sessionId: string}
     */
    protected function createMcpClient(string $role = 'ROLE_VIEWER'): array
    {
        $profile = $this->createProfile(roles: [$role], password: self::DEFAULT_PASSWORD);

        return $this->initMcpSession($profile->getId(), self::DEFAULT_PASSWORD);
    }

    /**
     * @return array{client: Client, sessionId: string}
     */
    protected function initMcpSession(string $profileId, string $password): array
    {
        $client = static::createClient();

        $response = $client->request('POST', '/_mcp', [
            'headers' => [
                'Content-Type'       => 'application/json',
                'Accept'             => 'application/json, text/event-stream',
                'X-Profile-Id'       => $profileId,
                'X-Profile-Password' => $password,
            ],
            'body' => json_encode([
                'jsonrpc' => '2.0',
                'method'  => 'initialize',
                'params'  => [
                    'protocolVersion' => '2025-03-26',
                    'capabilities'    => new stdClass(),
                    'clientInfo'      => ['name' => 'test', 'version' => '1.0'],
                ],
                'id' => 1,
            ]),
        ]);

        $sessionId = $response->getHeaders()['mcp-session-id'][0] ?? '';

        $client->request('POST', '/_mcp', [
            'headers' => [
                'Content-Type'       => 'application/json',
                'Accept'             => 'application/json, text/event-stream',
                'X-Profile-Id'       => $profileId,
                'X-Profile-Password' => $password,
                'Mcp-Session-Id'     => $sessionId,
            ],
            'body' => json_encode([
                'jsonrpc' => '2.0',
                'method'  => 'notifications/initialized',
            ]),
        ]);

        return ['client' => $client, 'sessionId' => $sessionId, 'profileId' => $profileId, 'password' => $password];
    }

    /**
     * @return array<string, mixed>
     */
    protected function callMcpTool(array $session, string $toolName, array $arguments = []): array
    {
        $response = $session['client']->request('POST', '/_mcp', [
            'headers' => [
                'Content-Type'       => 'application/json',
                'Accept'             => 'application/json, text/event-stream',
                'X-Profile-Id'       => $session['profileId'],
                'X-Profile-Password' => $session['password'],
                'Mcp-Session-Id'     => $session['sessionId'],
            ],
            'body' => json_encode([
                'jsonrpc' => '2.0',
                'method'  => 'tools/call',
                'params'  => [
                    'name'      => $toolName,
                    'arguments' => empty($arguments) ? new stdClass() : $arguments,
                ],
                'id' => random_int(10, 9999),
            ]),
        ]);

        $raw  = $response->getContent(false);
        $data = json_decode($raw, true);

        if (null === $data) {
            // SSE format: parse "data: {...}" lines
            foreach (explode("\n", $raw) as $line) {
                if (str_starts_with($line, 'data: ')) {
                    $parsed = json_decode(substr($line, 6), true);
                    if ($parsed && (isset($parsed['result']) || isset($parsed['error']))) {
                        $data = $parsed;

                        break;
                    }
                }
            }
        }

        $data ??= [];

        if (isset($data['result']['content'][0]['text'])) {
            $data['_parsed'] = json_decode($data['result']['content'][0]['text'], true);
        }

        return $data;
    }

    // ── Factory helpers ─────────────────────────────────────────────

    protected function createProfile(
        string $firstName = 'Test',
        string $lastName = 'User',
        ?string $email = null,
        array $roles = ['ROLE_USER'],
        ?string $password = null,
        bool $isActive = true,
    ): Profile {
        $profile = new Profile();
        $profile->setFirstName($firstName);
        $profile->setLastName($lastName);
        $profile->setEmail($email ?? uniqid('test-', true).'@test.local');
        $profile->setRoles($roles);
        $profile->setIsActive($isActive);

        if (null !== $password) {
            $hashed = $this->getPasswordHasher()->hashPassword($profile, $password);
            $profile->setPassword($hashed);
        }

        $em = $this->getEntityManager();
        $em->persist($profile);
        $em->flush();

        return $profile;
    }

    protected function createSite(string $name = 'Site Test', array $extra = []): Site
    {
        $site = new Site();
        $site->setName($name);

        if (isset($extra['contactName'])) {
            $site->setContactName($extra['contactName']);
        }
        if (isset($extra['contactCity'])) {
            $site->setContactCity($extra['contactCity']);
        }

        $em = $this->getEntityManager();
        $em->persist($site);
        $em->flush();

        return $site;
    }

    protected function createConstructeur(string $name = 'Constructeur Test', ?string $email = null, ?string $phone = null): Constructeur
    {
        $c = new Constructeur();
        $c->setName($name);
        $c->setEmail($email);

        if (null !== $phone) {
            $tel = new ConstructeurTelephone();
            $tel->setNumero($phone);
            $c->addTelephone($tel);
        }

        $em = $this->getEntityManager();
        $em->persist($c);
        $em->flush();

        return $c;
    }

    protected function createConstructeurCategorie(string $name = 'Catégorie Test'): ConstructeurCategorie
    {
        $categorie = new ConstructeurCategorie();
        $categorie->setName($name);

        $em = $this->getEntityManager();
        $em->persist($categorie);
        $em->flush();

        return $categorie;
    }

    protected function createConstructeurTelephone(Constructeur $constructeur, string $numero = '0102030405', ?string $label = null): ConstructeurTelephone
    {
        $tel = new ConstructeurTelephone();
        $tel->setConstructeur($constructeur);
        $tel->setNumero($numero);
        $tel->setLabel($label);

        $em = $this->getEntityManager();
        $em->persist($tel);
        $em->flush();

        return $tel;
    }

    protected function createMachineConstructeurLink(Machine $machine, Constructeur $constructeur, ?string $supplierReference = null): MachineConstructeurLink
    {
        $link = new MachineConstructeurLink();
        $link->setMachine($machine);
        $link->setConstructeur($constructeur);
        $link->setSupplierReference($supplierReference);
        $this->getEntityManager()->persist($link);
        $this->getEntityManager()->flush();

        return $link;
    }

    protected function createPieceConstructeurLink(Piece $piece, Constructeur $constructeur, ?string $supplierReference = null): PieceConstructeurLink
    {
        $link = new PieceConstructeurLink();
        $link->setPiece($piece);
        $link->setConstructeur($constructeur);
        $link->setSupplierReference($supplierReference);
        $this->getEntityManager()->persist($link);
        $this->getEntityManager()->flush();

        return $link;
    }

    protected function createComposantConstructeurLink(Composant $composant, Constructeur $constructeur, ?string $supplierReference = null): ComposantConstructeurLink
    {
        $link = new ComposantConstructeurLink();
        $link->setComposant($composant);
        $link->setConstructeur($constructeur);
        $link->setSupplierReference($supplierReference);
        $this->getEntityManager()->persist($link);
        $this->getEntityManager()->flush();

        return $link;
    }

    protected function createProductConstructeurLink(Product $product, Constructeur $constructeur, ?string $supplierReference = null): ProductConstructeurLink
    {
        $link = new ProductConstructeurLink();
        $link->setProduct($product);
        $link->setConstructeur($constructeur);
        $link->setSupplierReference($supplierReference);
        $this->getEntityManager()->persist($link);
        $this->getEntityManager()->flush();

        return $link;
    }

    protected function createModelType(
        string $name = 'ModelType Test',
        string $code = 'MT-001',
        ModelCategory $category = ModelCategory::COMPONENT,
    ): ModelType {
        $mt = new ModelType();
        $mt->setName($name);
        $mt->setCode($code);
        $mt->setCategory($category);

        $em = $this->getEntityManager();
        $em->persist($mt);
        $em->flush();

        return $mt;
    }

    protected function createMachine(string $name = 'Machine Test', ?Site $site = null, ?string $reference = null): Machine
    {
        $site ??= $this->createSite();

        $machine = new Machine();
        $machine->setName($name);
        $machine->setSite($site);
        if (null !== $reference) {
            $machine->setReference($reference);
        }

        $em = $this->getEntityManager();
        $em->persist($machine);
        $em->flush();

        return $machine;
    }

    protected function createComposant(string $name = 'Composant Test', ?string $reference = null, ?ModelType $type = null): Composant
    {
        $c = new Composant();
        $c->setName($name);
        if (null !== $reference) {
            $c->setReference($reference);
        }
        if (null !== $type) {
            $c->setTypeComposant($type);
        }

        $em = $this->getEntityManager();
        $em->persist($c);
        $em->flush();

        return $c;
    }

    protected function createPiece(string $name = 'Piece Test', ?string $reference = null, ?ModelType $type = null): Piece
    {
        $p = new Piece();
        $p->setName($name);
        if (null !== $reference) {
            $p->setReference($reference);
        }
        if (null !== $type) {
            $p->setTypePiece($type);
        }

        $em = $this->getEntityManager();
        $em->persist($p);
        $em->flush();

        return $p;
    }

    protected function createProduct(string $name = 'Product Test', ?string $reference = null, ?ModelType $type = null): Product
    {
        $p = new Product();
        $p->setName($name);
        if (null !== $reference) {
            $p->setReference($reference);
        }
        if (null !== $type) {
            $p->setTypeProduct($type);
        }

        $em = $this->getEntityManager();
        $em->persist($p);
        $em->flush();

        return $p;
    }

    protected function createCustomField(
        string $name = 'Custom Field',
        string $type = 'text',
        ?Machine $machine = null,
        ?ModelType $typeComposant = null,
        ?ModelType $typePiece = null,
        ?ModelType $typeProduct = null,
        int $orderIndex = 0,
        bool $machineContextOnly = false,
    ): CustomField {
        $cf = new CustomField();
        $cf->setName($name);
        $cf->setType($type);
        $cf->setOrderIndex($orderIndex);
        $cf->setMachineContextOnly($machineContextOnly);
        if (null !== $machine) {
            $cf->setMachine($machine);
        }
        if (null !== $typeComposant) {
            $cf->setTypeComposant($typeComposant);
        }
        if (null !== $typePiece) {
            $cf->setTypePiece($typePiece);
        }
        if (null !== $typeProduct) {
            $cf->setTypeProduct($typeProduct);
        }

        $em = $this->getEntityManager();
        $em->persist($cf);
        $em->flush();

        return $cf;
    }

    protected function createCustomFieldValue(
        CustomField $customField,
        string $value = 'test value',
        ?Machine $machine = null,
        ?Composant $composant = null,
        ?Piece $piece = null,
        ?Product $product = null,
        ?MachineComponentLink $machineComponentLink = null,
        ?MachinePieceLink $machinePieceLink = null,
    ): CustomFieldValue {
        $cfv = new CustomFieldValue();
        $cfv->setValue($value);
        $cfv->setCustomField($customField);
        if (null !== $machine) {
            $cfv->setMachine($machine);
        }
        if (null !== $composant) {
            $cfv->setComposant($composant);
        }
        if (null !== $piece) {
            $cfv->setPiece($piece);
        }
        if (null !== $product) {
            $cfv->setProduct($product);
        }
        if (null !== $machineComponentLink) {
            $cfv->setMachineComponentLink($machineComponentLink);
        }
        if (null !== $machinePieceLink) {
            $cfv->setMachinePieceLink($machinePieceLink);
        }

        $em = $this->getEntityManager();
        $em->persist($cfv);
        $em->flush();

        // Keep inverse-side collections in sync so identity-mapped entities reflect the new CFV.
        if (null !== $machineComponentLink && !$machineComponentLink->getContextFieldValues()->contains($cfv)) {
            $machineComponentLink->getContextFieldValues()->add($cfv);
        }
        if (null !== $machinePieceLink && !$machinePieceLink->getContextFieldValues()->contains($cfv)) {
            $machinePieceLink->getContextFieldValues()->add($cfv);
        }

        return $cfv;
    }

    protected function createMachineComponentLink(Machine $machine, Composant $composant): MachineComponentLink
    {
        $link = new MachineComponentLink();
        $link->setMachine($machine);
        $link->setComposant($composant);

        $em = $this->getEntityManager();
        $em->persist($link);
        $em->flush();

        return $link;
    }

    protected function createMachinePieceLink(Machine $machine, Piece $piece, ?MachineComponentLink $parentLink = null, int $quantity = 1): MachinePieceLink
    {
        $link = new MachinePieceLink();
        $link->setMachine($machine);
        $link->setPiece($piece);
        $link->setQuantity($quantity);
        if (null !== $parentLink) {
            $link->setParentLink($parentLink);
        }

        $em = $this->getEntityManager();
        $em->persist($link);
        $em->flush();

        return $link;
    }

    protected function createMachineProductLink(Machine $machine, Product $product): MachineProductLink
    {
        $link = new MachineProductLink();
        $link->setMachine($machine);
        $link->setProduct($product);

        $em = $this->getEntityManager();
        $em->persist($link);
        $em->flush();

        return $link;
    }

    protected function createComposantPieceSlot(
        Composant $composant,
        ?ModelType $typePiece = null,
        ?Piece $selectedPiece = null,
        int $quantity = 1,
        int $position = 0,
    ): ComposantPieceSlot {
        $slot = new ComposantPieceSlot();
        $slot->setComposant($composant);
        $slot->setQuantity($quantity);
        $slot->setPosition($position);
        if (null !== $typePiece) {
            $slot->setTypePiece($typePiece);
        }
        if (null !== $selectedPiece) {
            $slot->setSelectedPiece($selectedPiece);
        }

        $em = $this->getEntityManager();
        $em->persist($slot);
        $em->flush();

        return $slot;
    }

    protected function createComposantSubcomponentSlot(
        Composant $composant,
        ?string $alias = null,
        ?string $familyCode = null,
        ?ModelType $typeComposant = null,
        ?Composant $selectedComposant = null,
        int $position = 0,
    ): ComposantSubcomponentSlot {
        $slot = new ComposantSubcomponentSlot();
        $slot->setComposant($composant);
        $slot->setAlias($alias);
        $slot->setFamilyCode($familyCode);
        $slot->setPosition($position);
        if (null !== $typeComposant) {
            $slot->setTypeComposant($typeComposant);
        }
        if (null !== $selectedComposant) {
            $slot->setSelectedComposant($selectedComposant);
        }

        $em = $this->getEntityManager();
        $em->persist($slot);
        $em->flush();

        return $slot;
    }

    protected function createComposantProductSlot(
        Composant $composant,
        ?ModelType $typeProduct = null,
        ?Product $selectedProduct = null,
        ?string $familyCode = null,
        int $position = 0,
    ): ComposantProductSlot {
        $slot = new ComposantProductSlot();
        $slot->setComposant($composant);
        $slot->setFamilyCode($familyCode);
        $slot->setPosition($position);
        if (null !== $typeProduct) {
            $slot->setTypeProduct($typeProduct);
        }
        if (null !== $selectedProduct) {
            $slot->setSelectedProduct($selectedProduct);
        }

        $em = $this->getEntityManager();
        $em->persist($slot);
        $em->flush();

        return $slot;
    }

    protected function createPieceProductSlot(
        Piece $piece,
        ?ModelType $typeProduct = null,
        ?Product $selectedProduct = null,
        ?string $familyCode = null,
        int $position = 0,
    ): PieceProductSlot {
        $slot = new PieceProductSlot();
        $slot->setPiece($piece);
        $slot->setFamilyCode($familyCode);
        $slot->setPosition($position);
        if (null !== $typeProduct) {
            $slot->setTypeProduct($typeProduct);
        }
        if (null !== $selectedProduct) {
            $slot->setSelectedProduct($selectedProduct);
        }

        $em = $this->getEntityManager();
        $em->persist($slot);
        $em->flush();

        return $slot;
    }

    // ── Assertion helpers ───────────────────────────────────────────

    protected function assertJsonContainsHydraCollection(): void
    {
        $this->assertJsonContains(['@type' => 'Collection']);
    }

    protected static function iri(string $resource, string $id): string
    {
        return sprintf('/api/%s/%s', $resource, $id);
    }
}
