<?php

declare(strict_types=1);

namespace App\Tests\Api\Entity;

use App\Entity\CustomField;
use App\Entity\CustomFieldValue;
use App\Enum\ModelCategory;
use App\Tests\AbstractApiTestCase;

/**
 * @internal
 */
class ModelTypeTest extends AbstractApiTestCase
{
    public function testGetCollection(): void
    {
        $this->createModelType('Pompe', 'POMPE-001', ModelCategory::COMPONENT);
        $this->createModelType('Joint', 'JOINT-001', ModelCategory::PIECE);

        $client = $this->createViewerClient();
        $client->request('GET', '/api/model_types');

        $this->assertResponseIsSuccessful();
        $this->assertJsonContainsHydraCollection();
        $this->assertJsonContains(['totalItems' => 2]);
    }

    public function testGetItem(): void
    {
        $mt = $this->createModelType('Pompe', 'POMPE-001', ModelCategory::COMPONENT);

        $client = $this->createViewerClient();
        $client->request('GET', self::iri('model_types', $mt->getId()));

        $this->assertResponseIsSuccessful();
        $this->assertJsonContains([
            'name'     => 'Pompe',
            'code'     => 'POMPE-001',
            'category' => 'COMPONENT',
        ]);
    }

    public function testPost(): void
    {
        $client = $this->createGestionnaireClient();
        $client->request('POST', '/api/model_types', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'     => 'Roulement',
                'code'     => 'ROUL-001',
                'category' => 'PIECE',
            ],
        ]);

        $this->assertResponseStatusCodeSame(201);
        $this->assertJsonContains([
            'name'     => 'Roulement',
            'category' => 'PIECE',
        ]);
    }

    public function testPut(): void
    {
        $mt = $this->createModelType('Pompe', 'POMPE-001', ModelCategory::COMPONENT);

        $client = $this->createGestionnaireClient();
        $client->request('PUT', self::iri('model_types', $mt->getId()), [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'     => 'Pompe hydraulique',
                'code'     => 'POMPE-001',
                'category' => 'COMPONENT',
            ],
        ]);

        $this->assertResponseIsSuccessful();
        $this->assertJsonContains(['name' => 'Pompe hydraulique']);
    }

    public function testPatch(): void
    {
        $mt = $this->createModelType('Pompe', 'POMPE-001', ModelCategory::COMPONENT);

        $client = $this->createGestionnaireClient();
        $client->request('PATCH', self::iri('model_types', $mt->getId()), [
            'headers' => ['Content-Type' => 'application/merge-patch+json'],
            'json'    => ['notes' => 'Updated notes'],
        ]);

        $this->assertResponseIsSuccessful();
        $this->assertJsonContains(['notes' => 'Updated notes']);
    }

    public function testDelete(): void
    {
        $mt = $this->createModelType('ToDelete', 'DEL-001', ModelCategory::PRODUCT);

        $client = $this->createGestionnaireClient();
        $client->request('DELETE', self::iri('model_types', $mt->getId()));

        $this->assertResponseStatusCodeSame(204);
    }

    public function testUnauthenticatedAccess(): void
    {
        $client = $this->createUnauthenticatedClient();
        $client->request('GET', '/api/model_types');

        $this->assertResponseStatusCodeSame(401);
    }

    public function testViewerCannotWrite(): void
    {
        $client = $this->createViewerClient();
        $client->request('POST', '/api/model_types', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'     => 'Blocked',
                'code'     => 'BLK-001',
                'category' => 'COMPONENT',
            ],
        ]);

        $this->assertResponseStatusCodeSame(403);
    }

    public function testFilterByCategory(): void
    {
        $this->createModelType('Pompe', 'POMPE-001', ModelCategory::COMPONENT);
        $this->createModelType('Joint', 'JOINT-001', ModelCategory::PIECE);

        $client = $this->createViewerClient();
        $client->request('GET', '/api/model_types?category=COMPONENT');

        $this->assertResponseIsSuccessful();
        $this->assertJsonContains(['totalItems' => 1]);
    }

    public function testUniqueCodeConstraint(): void
    {
        $this->createModelType('Pompe', 'POMPE-001', ModelCategory::COMPONENT);

        $client = $this->createGestionnaireClient();
        $client->request('POST', '/api/model_types', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'     => 'Autre',
                'code'     => 'POMPE-001',
                'category' => 'PIECE',
            ],
        ]);

        $this->assertResponseStatusCodeSame(409);
    }

    public function testPostComponentWithSkeletonStructure(): void
    {
        $pieceType   = $this->createModelType('Joint', 'JOINT-001', ModelCategory::PIECE);
        $productType = $this->createModelType('Huile', 'HUILE-001', ModelCategory::PRODUCT);

        $client = $this->createGestionnaireClient();
        $client->request('POST', '/api/model_types', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'      => 'Pompe complète',
                'code'      => 'POMPE-FULL',
                'category'  => 'COMPONENT',
                'structure' => [
                    'pieces'        => [['typePieceId' => $pieceType->getId()]],
                    'products'      => [['typeProductId' => $productType->getId(), 'familyCode' => 'LUB']],
                    'subcomponents' => [['alias' => 'Sous-pompe', 'familyCode' => 'SP']],
                ],
            ],
        ]);

        $this->assertResponseStatusCodeSame(201);
        $data = $client->getResponse()->toArray();

        $this->assertArrayHasKey('structure', $data);
        $this->assertCount(1, $data['structure']['pieces']);
        $this->assertSame($pieceType->getId(), $data['structure']['pieces'][0]['typePieceId']);
        $this->assertCount(1, $data['structure']['products']);
        $this->assertSame($productType->getId(), $data['structure']['products'][0]['typeProductId']);
        $this->assertSame('LUB', $data['structure']['products'][0]['familyCode']);
        $this->assertCount(1, $data['structure']['subcomponents']);
        $this->assertSame('Sous-pompe', $data['structure']['subcomponents'][0]['alias']);
        $this->assertSame('SP', $data['structure']['subcomponents'][0]['familyCode']);
    }

    public function testGetItemReturnsStructureFromRelations(): void
    {
        $pieceType = $this->createModelType('Joint', 'JOINT-001', ModelCategory::PIECE);

        $client = $this->createGestionnaireClient();
        $client->request('POST', '/api/model_types', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'      => 'Pompe',
                'code'      => 'POMPE-STRUCT',
                'category'  => 'COMPONENT',
                'structure' => [
                    'pieces' => [['typePieceId' => $pieceType->getId()]],
                ],
            ],
        ]);
        $this->assertResponseStatusCodeSame(201);
        $createdData = $client->getResponse()->toArray();

        // GET the item and verify structure is returned from relations
        $client->request('GET', self::iri('model_types', $createdData['id']));
        $this->assertResponseIsSuccessful();
        $data = $client->getResponse()->toArray();

        $this->assertArrayHasKey('structure', $data);
        $this->assertCount(1, $data['structure']['pieces']);
        $this->assertSame($pieceType->getId(), $data['structure']['pieces'][0]['typePieceId']);
    }

    public function testPatchUpdatesSkeletonStructure(): void
    {
        $pieceType1 = $this->createModelType('Joint', 'JOINT-001', ModelCategory::PIECE);
        $pieceType2 = $this->createModelType('Roulement', 'ROUL-001', ModelCategory::PIECE);

        // Create with one piece requirement
        $client = $this->createGestionnaireClient();
        $client->request('POST', '/api/model_types', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'      => 'Pompe',
                'code'      => 'POMPE-UPD',
                'category'  => 'COMPONENT',
                'structure' => [
                    'pieces' => [['typePieceId' => $pieceType1->getId()]],
                ],
            ],
        ]);
        $this->assertResponseStatusCodeSame(201);
        $createdData = $client->getResponse()->toArray();

        // PATCH with two piece requirements (replaces the old one)
        $client->request('PATCH', self::iri('model_types', $createdData['id']), [
            'headers' => ['Content-Type' => 'application/merge-patch+json'],
            'json'    => [
                'structure' => [
                    'pieces' => [
                        ['typePieceId' => $pieceType1->getId()],
                        ['typePieceId' => $pieceType2->getId()],
                    ],
                ],
            ],
        ]);

        $this->assertResponseIsSuccessful();
        $data = $client->getResponse()->toArray();
        $this->assertCount(2, $data['structure']['pieces']);
    }

    public function testPostPieceTypeWithProductRequirements(): void
    {
        $productType = $this->createModelType('Graisse', 'GRAISSE-001', ModelCategory::PRODUCT);

        $client = $this->createGestionnaireClient();
        $client->request('POST', '/api/model_types', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'      => 'Roulement',
                'code'      => 'ROUL-PROD',
                'category'  => 'PIECE',
                'structure' => [
                    'products' => [['typeProductId' => $productType->getId(), 'familyCode' => 'GR']],
                ],
            ],
        ]);

        $this->assertResponseStatusCodeSame(201);
        $data = $client->getResponse()->toArray();

        $this->assertArrayHasKey('structure', $data);
        $this->assertCount(1, $data['structure']['products']);
        $this->assertSame($productType->getId(), $data['structure']['products'][0]['typeProductId']);
        $this->assertSame('GR', $data['structure']['products'][0]['familyCode']);
    }

    public function testPatchPieceStructureRenameKeepsCustomFieldValuesWhenIdIsMissing(): void
    {
        $type  = $this->createModelType('Arbre', 'ARB-001', ModelCategory::PIECE);
        $piece = $this->createPiece('Arbre de test', 'ARB-TEST', $type);
        $field = $this->createCustomField('diamètre', 'text', typePiece: $type, orderIndex: 0);
        $this->createCustomFieldValue($field, '35 mm', piece: $piece);

        $client = $this->createGestionnaireClient();
        $client->request('PATCH', self::iri('model_types', $type->getId()), [
            'headers' => ['Content-Type' => 'application/merge-patch+json'],
            'json'    => [
                'structure' => [
                    'customFields' => [[
                        'name'       => 'Diamètre',
                        'type'       => 'text',
                        'required'   => false,
                        'orderIndex' => 0,
                    ]],
                ],
            ],
        ]);

        $this->assertResponseIsSuccessful();

        $em = $this->getEntityManager();
        $em->clear();

        $fields = $em->getRepository(CustomField::class)->findBy(['typePiece' => $type], ['orderIndex' => 'ASC']);
        $this->assertCount(1, $fields);
        $this->assertSame($field->getId(), $fields[0]->getId(), 'Renaming must reuse the existing CustomField');
        $this->assertSame('Diamètre', $fields[0]->getName());

        $values = $em->getRepository(CustomFieldValue::class)->findBy(['piece' => $piece]);
        $this->assertCount(1, $values);
        $this->assertSame('35 mm', $values[0]->getValue(), 'Existing custom field value must be preserved');
        $this->assertSame($field->getId(), $values[0]->getCustomField()->getId());
    }
}
