<?php

declare(strict_types=1);

namespace App\Tests\Api;

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

/**
 * Data integrity tests — verifies fixes that prevent data loss
 * in clone, slots, conversion, and custom fields.
 *
 * @internal
 */
class DataIntegrityTest extends AbstractApiTestCase
{
    // -----------------------------------------------------------------------
    // T1 — Clone: CustomFieldValue must reference cloned CustomField
    // -----------------------------------------------------------------------

    public function testCloneCustomFieldValuesReferenceClonedDefinitions(): void
    {
        $source = $this->createMachine('Source Machine');
        $cf     = $this->createCustomField('Puissance', 'text', $source);
        $this->createCustomFieldValue($cf, '15 kW', $source);

        $client = $this->createGestionnaireClient();
        $client->request('POST', '/api/machines/'.$source->getId().'/clone', [
            'headers' => ['Content-Type' => 'application/json'],
            'json'    => [
                'name'   => 'Cloned Machine',
                'siteId' => $source->getSite()->getId(),
            ],
        ]);

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

        $clonedMachineId = $data['machine']['id'] ?? null;
        $this->assertNotNull($clonedMachineId, 'Clone should return new machine ID');
        $this->assertNotSame($source->getId(), $clonedMachineId);

        // Verify cloned CustomFieldValues reference CustomFields owned by the clone
        $em            = $this->getEntityManager();
        $clonedValues  = $em->getRepository(CustomFieldValue::class)->findBy(['machine' => $clonedMachineId]);
        $clonedFields  = $em->getRepository(CustomField::class)->findBy(['machine' => $clonedMachineId]);

        $this->assertNotEmpty($clonedValues, 'Clone should have custom field values');
        $this->assertNotEmpty($clonedFields, 'Clone should have custom field definitions');

        $clonedFieldIds = array_map(static fn (CustomField $cf) => $cf->getId(), $clonedFields);

        foreach ($clonedValues as $cfv) {
            $this->assertContains(
                $cfv->getCustomField()->getId(),
                $clonedFieldIds,
                'Cloned CustomFieldValue must reference a CustomField owned by the cloned machine, not the source'
            );
        }
    }

    public function testCloneCustomFieldValuesPreserveValues(): void
    {
        $source = $this->createMachine('Source');
        $cf1    = $this->createCustomField('Tension', 'text', $source);
        $cf2    = $this->createCustomField('Vitesse', 'text', $source);
        $this->createCustomFieldValue($cf1, '400 V', $source);
        $this->createCustomFieldValue($cf2, '1500 tr/min', $source);

        $client = $this->createGestionnaireClient();
        $client->request('POST', '/api/machines/'.$source->getId().'/clone', [
            'headers' => ['Content-Type' => 'application/json'],
            'json'    => [
                'name'   => 'Clone with values',
                'siteId' => $source->getSite()->getId(),
            ],
        ]);

        $this->assertResponseIsSuccessful();
        $data            = $client->getResponse()->toArray();
        $clonedMachineId = $data['machine']['id'];

        $em           = $this->getEntityManager();
        $clonedValues = $em->getRepository(CustomFieldValue::class)->findBy(['machine' => $clonedMachineId]);

        $valueMap = [];
        foreach ($clonedValues as $cfv) {
            $valueMap[$cfv->getCustomField()->getName()] = $cfv->getValue();
        }

        $this->assertSame('400 V', $valueMap['Tension'] ?? null);
        $this->assertSame('1500 tr/min', $valueMap['Vitesse'] ?? null);
    }

    // -----------------------------------------------------------------------
    // T2 — Slot PATCH: type validation + 404 on missing piece
    // -----------------------------------------------------------------------

    public function testSlotPatchWithNonExistentPieceReturns404(): void
    {
        $composant = $this->createComposant('Comp slot 404');
        $pieceType = $this->createModelType('Roulement', 'ROUL-404', ModelCategory::PIECE);
        $slot      = $this->createComposantPieceSlot($composant, $pieceType, null, 1, 0);

        $client = $this->createGestionnaireClient();
        $client->request('PATCH', '/api/composant-piece-slots/'.$slot->getId(), [
            'headers' => ['Content-Type' => 'application/json'],
            'json'    => ['selectedPieceId' => 'cl_nonexistent_id_123'],
        ]);

        $this->assertResponseStatusCodeSame(404);
    }

    public function testSlotPatchWithWrongPieceTypeReturns422(): void
    {
        $typeA     = $this->createModelType('Roulement', 'ROUL-A', ModelCategory::PIECE);
        $typeB     = $this->createModelType('Joint', 'JOINT-B', ModelCategory::PIECE);
        $composant = $this->createComposant('Comp slot type');
        $slot      = $this->createComposantPieceSlot($composant, $typeA, null, 1, 0);
        $wrongPiece = $this->createPiece('Joint XY', 'REF-JXY', $typeB);

        $client = $this->createGestionnaireClient();
        $client->request('PATCH', '/api/composant-piece-slots/'.$slot->getId(), [
            'headers' => ['Content-Type' => 'application/json'],
            'json'    => ['selectedPieceId' => $wrongPiece->getId()],
        ]);

        $this->assertResponseStatusCodeSame(422);
        $data = $client->getResponse()->toArray(false);
        $this->assertStringContainsString('Roulement', $data['error']);
    }

    public function testSlotPatchWithCorrectPieceTypeSucceeds(): void
    {
        $type      = $this->createModelType('Filtre', 'FILT-OK', ModelCategory::PIECE);
        $composant = $this->createComposant('Comp slot OK');
        $slot      = $this->createComposantPieceSlot($composant, $type, null, 1, 0);
        $piece     = $this->createPiece('Filtre Parker', 'REF-FP', $type);

        $client = $this->createGestionnaireClient();
        $client->request('PATCH', '/api/composant-piece-slots/'.$slot->getId(), [
            'headers' => ['Content-Type' => 'application/json'],
            'json'    => ['selectedPieceId' => $piece->getId()],
        ]);

        $this->assertResponseIsSuccessful();
        $data = $client->getResponse()->toArray();
        $this->assertSame($piece->getId(), $data['selectedPieceId']);
    }

    public function testSlotPatchClearPieceSucceeds(): void
    {
        $type      = $this->createModelType('Joint', 'JOINT-CLR', ModelCategory::PIECE);
        $composant = $this->createComposant('Comp slot clear');
        $piece     = $this->createPiece('Joint A', 'REF-JA', $type);
        $slot      = $this->createComposantPieceSlot($composant, $type, $piece, 1, 0);

        $client = $this->createGestionnaireClient();
        $client->request('PATCH', '/api/composant-piece-slots/'.$slot->getId(), [
            'headers' => ['Content-Type' => 'application/json'],
            'json'    => ['selectedPieceId' => null],
        ]);

        $this->assertResponseIsSuccessful();
        $data = $client->getResponse()->toArray();
        $this->assertNull($data['selectedPieceId']);
    }

    // -----------------------------------------------------------------------
    // T3 — Conversion: blocks when slots have data
    // -----------------------------------------------------------------------

    public function testConversionBlockedWhenSlotsHaveData(): void
    {
        $compType  = $this->createModelType('Pompe', 'POMPE-CONV', ModelCategory::COMPONENT);
        $pieceType = $this->createModelType('Joint', 'JOINT-CONV', ModelCategory::PIECE);
        $composant = $this->createComposant('Pompe A', null, $compType);
        $piece     = $this->createPiece('Joint réel', 'REF-JR', $pieceType);
        $this->createComposantPieceSlot($composant, $pieceType, $piece, 1, 0);

        $client = $this->createGestionnaireClient();
        $client->request('GET', '/api/model_types/'.$compType->getId().'/conversion-check');

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

        $this->assertFalse($data['canConvert'], 'Conversion should be blocked when slots have data');
        $this->assertNotEmpty($data['blockers']);
    }

    public function testConversionAllowedWhenSlotsEmpty(): void
    {
        $compType  = $this->createModelType('Vanne', 'VANNE-CONV', ModelCategory::COMPONENT);
        $pieceType = $this->createModelType('Joint', 'JOINT-EMPTY', ModelCategory::PIECE);
        $composant = $this->createComposant('Vanne A', null, $compType);
        // Slot without selected piece (empty)
        $this->createComposantPieceSlot($composant, $pieceType, null, 1, 0);

        $client = $this->createGestionnaireClient();
        $client->request('GET', '/api/model_types/'.$compType->getId().'/conversion-check');

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

        // May still be blocked by machine links, but NOT by slots
        $slotBlocker = array_filter(
            $data['blockers'] ?? [],
            static fn (string $b) => str_contains($b, 'slot')
        );
        $this->assertEmpty($slotBlocker, 'Empty slots should not block conversion');
    }

    // -----------------------------------------------------------------------
    // T4 — CustomFieldValue: no orphan CustomField creation
    // -----------------------------------------------------------------------

    public function testCustomFieldValueRejectsOrphanCreation(): void
    {
        $machine = $this->createMachine('Machine CF');

        $client = $this->createGestionnaireClient();
        $client->request('POST', '/api/custom-fields/values', [
            'headers' => ['Content-Type' => 'application/json'],
            'json'    => [
                'customFieldName' => 'NonExistentField',
                'value'           => 'some value',
                'entityType'      => 'machine',
                'entityId'        => $machine->getId(),
            ],
        ]);

        $this->assertResponseStatusCodeSame(404);
    }

    public function testCustomFieldValueAcceptsExistingFieldById(): void
    {
        $machine = $this->createMachine('Machine CF ok');
        $cf      = $this->createCustomField('Existing Field', 'text', $machine);

        $client = $this->createGestionnaireClient();
        $client->request('POST', '/api/custom-fields/values', [
            'headers' => ['Content-Type' => 'application/json'],
            'json'    => [
                'customFieldId' => $cf->getId(),
                'value'         => 'test value',
                'entityType'    => 'machine',
                'entityId'      => $machine->getId(),
            ],
        ]);

        $this->assertResponseIsSuccessful();
    }
}
