<?php

declare(strict_types=1);

namespace App\Tests\Api\Entity;

use App\Enum\ModelCategory;
use App\Tests\AbstractApiTestCase;

/**
 * @internal
 */
class MachineContextCustomFieldTest extends AbstractApiTestCase
{
    public function testStructureReturnsContextFieldsOnComponentLink(): void
    {
        $client = $this->createGestionnaireClient();

        $site      = $this->createSite('Site A');
        $modelType = $this->createModelType('Motor', 'MOT', ModelCategory::COMPONENT);

        $contextField = $this->createCustomField(
            name: 'Voltage',
            type: 'number',
            typeComposant: $modelType,
            machineContextOnly: true,
        );
        $normalField = $this->createCustomField(
            name: 'Serial',
            type: 'text',
            typeComposant: $modelType,
        );

        $machine   = $this->createMachine('Machine A', $site);
        $composant = $this->createComposant('Motor 1', 'MOT-001', $modelType);
        $link      = $this->createMachineComponentLink($machine, $composant);

        $this->createCustomFieldValue(
            customField: $contextField,
            value: '220',
            composant: $composant,
            machineComponentLink: $link,
        );

        $response = $client->request('GET', '/api/machines/'.$machine->getId().'/structure');
        $this->assertResponseIsSuccessful();

        $data          = $response->toArray();
        $componentLink = $data['componentLinks'][0];

        $this->assertArrayHasKey('contextCustomFields', $componentLink);
        $this->assertCount(1, $componentLink['contextCustomFields']);
        $this->assertSame('Voltage', $componentLink['contextCustomFields'][0]['name']);
        $this->assertTrue($componentLink['contextCustomFields'][0]['machineContextOnly']);

        $this->assertArrayHasKey('contextCustomFieldValues', $componentLink);
        $this->assertCount(1, $componentLink['contextCustomFieldValues']);
        $this->assertSame('220', $componentLink['contextCustomFieldValues'][0]['value']);

        $normalFields = array_filter(
            $componentLink['composant']['customFields'],
            fn (array $f) => 'Serial' === $f['name'],
        );
        $this->assertCount(1, $normalFields);
    }

    public function testStructureReturnsContextFieldsOnPieceLink(): void
    {
        $client = $this->createGestionnaireClient();

        $site         = $this->createSite('Site B');
        $modelType    = $this->createModelType('Bearing', 'BRG', ModelCategory::PIECE);
        $contextField = $this->createCustomField(
            name: 'Wear Level',
            type: 'select',
            typePiece: $modelType,
            machineContextOnly: true,
        );
        $contextField->setOptions(['Good', 'Fair', 'Replace']);
        $this->getEntityManager()->flush();

        $machine = $this->createMachine('Machine B', $site);
        $piece   = $this->createPiece('Bearing 1', 'BRG-001', $modelType);
        $link    = $this->createMachinePieceLink($machine, $piece);

        $this->createCustomFieldValue(
            customField: $contextField,
            value: 'Fair',
            piece: $piece,
            machinePieceLink: $link,
        );

        $response = $client->request('GET', '/api/machines/'.$machine->getId().'/structure');
        $data     = $response->toArray();

        $pieceLink = $data['pieceLinks'][0];
        $this->assertCount(1, $pieceLink['contextCustomFields']);
        $this->assertSame('Wear Level', $pieceLink['contextCustomFields'][0]['name']);
        $this->assertCount(1, $pieceLink['contextCustomFieldValues']);
        $this->assertSame('Fair', $pieceLink['contextCustomFieldValues'][0]['value']);
    }

    public function testUpsertContextFieldValueViaComponentLink(): void
    {
        $client = $this->createGestionnaireClient();

        $site         = $this->createSite('Site C');
        $modelType    = $this->createModelType('Pump', 'PMP', ModelCategory::COMPONENT);
        $contextField = $this->createCustomField(
            name: 'Flow Rate',
            type: 'number',
            typeComposant: $modelType,
            machineContextOnly: true,
        );

        $machine   = $this->createMachine('Machine C', $site);
        $composant = $this->createComposant('Pump 1', 'PMP-001', $modelType);
        $link      = $this->createMachineComponentLink($machine, $composant);

        $response = $client->request('POST', '/api/custom-fields/values/upsert', [
            'json' => [
                'customFieldId'          => $contextField->getId(),
                'machineComponentLinkId' => $link->getId(),
                'value'                  => '380',
            ],
        ]);

        $this->assertResponseIsSuccessful();
        $data = $response->toArray();
        $this->assertSame('380', $data['value']);
    }

    public function testSameComposantDifferentValuesPerMachine(): void
    {
        $client = $this->createGestionnaireClient();

        $site         = $this->createSite('Site D');
        $modelType    = $this->createModelType('Valve', 'VLV', ModelCategory::COMPONENT);
        $contextField = $this->createCustomField(
            name: 'Pressure',
            type: 'number',
            typeComposant: $modelType,
            machineContextOnly: true,
        );

        $machineA  = $this->createMachine('Machine A', $site);
        $machineB  = $this->createMachine('Machine B', $site);
        $composant = $this->createComposant('Valve 1', 'VLV-001', $modelType);

        $linkA = $this->createMachineComponentLink($machineA, $composant);
        $linkB = $this->createMachineComponentLink($machineB, $composant);

        $this->createCustomFieldValue(
            customField: $contextField,
            value: '100',
            composant: $composant,
            machineComponentLink: $linkA,
        );
        $this->createCustomFieldValue(
            customField: $contextField,
            value: '200',
            composant: $composant,
            machineComponentLink: $linkB,
        );

        $dataA = $client->request('GET', '/api/machines/'.$machineA->getId().'/structure')->toArray();
        $this->assertSame('100', $dataA['componentLinks'][0]['contextCustomFieldValues'][0]['value']);

        $dataB = $client->request('GET', '/api/machines/'.$machineB->getId().'/structure')->toArray();
        $this->assertSame('200', $dataB['componentLinks'][0]['contextCustomFieldValues'][0]['value']);
    }

    public function testMachineContextOnlyFieldSerialization(): void
    {
        $client = $this->createGestionnaireClient();

        $site         = $this->createSite('Site E');
        $modelType    = $this->createModelType('Sensor', 'SNS', ModelCategory::COMPONENT);
        $contextField = $this->createCustomField(
            name: 'Calibration Date',
            type: 'date',
            typeComposant: $modelType,
            machineContextOnly: true,
        );

        $response = $client->request('GET', '/api/custom_fields/'.$contextField->getId());
        $this->assertResponseIsSuccessful();
        $data = $response->toArray();
        $this->assertTrue($data['machineContextOnly']);
    }

    public function testCloneMachineCopiesContextFieldValues(): void
    {
        $client = $this->createGestionnaireClient();

        $site         = $this->createSite('Site F');
        $modelType    = $this->createModelType('Motor Clone', 'MOTC', ModelCategory::COMPONENT);
        $contextField = $this->createCustomField(
            name: 'RPM Setting',
            type: 'number',
            typeComposant: $modelType,
            machineContextOnly: true,
        );

        $source    = $this->createMachine('Source Machine', $site);
        $composant = $this->createComposant('Motor C', 'MOTC-001', $modelType);
        $link      = $this->createMachineComponentLink($source, $composant);

        $this->createCustomFieldValue(
            customField: $contextField,
            value: '3000',
            composant: $composant,
            machineComponentLink: $link,
        );

        $response = $client->request('POST', '/api/machines/'.$source->getId().'/clone', [
            'json' => [
                'name'   => 'Cloned Machine',
                'siteId' => $site->getId(),
            ],
        ]);

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

        $clonedLink = $data['componentLinks'][0] ?? null;
        $this->assertNotNull($clonedLink);
        $this->assertCount(1, $clonedLink['contextCustomFieldValues']);
        $this->assertSame('3000', $clonedLink['contextCustomFieldValues'][0]['value']);
    }

    public function testCloneMachineCopiesPieceContextFieldValues(): void
    {
        $client = $this->createGestionnaireClient();

        $site         = $this->createSite('Site G');
        $modelType    = $this->createModelType('Bearing Clone', 'BRGC', ModelCategory::PIECE);
        $contextField = $this->createCustomField(
            name: 'Wear Level',
            type: 'text',
            typePiece: $modelType,
            machineContextOnly: true,
        );

        $source = $this->createMachine('Source Piece Machine', $site);
        $piece  = $this->createPiece('Bearing C', 'BRGC-001', $modelType);
        $link   = $this->createMachinePieceLink($source, $piece);

        $this->createCustomFieldValue(
            customField: $contextField,
            value: 'Fair',
            piece: $piece,
            machinePieceLink: $link,
        );

        $response = $client->request('POST', '/api/machines/'.$source->getId().'/clone', [
            'json' => [
                'name'   => 'Cloned Piece Machine',
                'siteId' => $site->getId(),
            ],
        ]);

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

        $clonedLink = $data['pieceLinks'][0] ?? null;
        $this->assertNotNull($clonedLink, 'Clone should expose at least one pieceLink');
        $this->assertCount(1, $clonedLink['contextCustomFieldValues']);
        $this->assertSame('Fair', $clonedLink['contextCustomFieldValues'][0]['value']);
    }

    public function testCloneMachineLeavesSourceContextFieldValuesIntact(): void
    {
        $client = $this->createGestionnaireClient();

        $site         = $this->createSite('Site H');
        $modelType    = $this->createModelType('Motor Source', 'MOTS', ModelCategory::COMPONENT);
        $contextField = $this->createCustomField(
            name: 'RPM',
            type: 'number',
            typeComposant: $modelType,
            machineContextOnly: true,
        );

        $source    = $this->createMachine('Original Machine', $site);
        $composant = $this->createComposant('Motor S', 'MOTS-001', $modelType);
        $link      = $this->createMachineComponentLink($source, $composant);

        $this->createCustomFieldValue(
            customField: $contextField,
            value: '1500',
            composant: $composant,
            machineComponentLink: $link,
        );

        $client->request('POST', '/api/machines/'.$source->getId().'/clone', [
            'json' => [
                'name'   => 'Clone Machine',
                'siteId' => $site->getId(),
            ],
        ]);
        $this->assertResponseStatusCodeSame(201);

        // Source must still expose its original context field value
        $sourceData = $client->request('GET', '/api/machines/'.$source->getId().'/structure')->toArray();
        $sourceLink = $sourceData['componentLinks'][0] ?? null;
        $this->assertNotNull($sourceLink, 'Source machine should still expose its component link');
        $this->assertCount(1, $sourceLink['contextCustomFieldValues']);
        $this->assertSame('1500', $sourceLink['contextCustomFieldValues'][0]['value']);
    }

    public function testCloneMachineStructureModeKeepsCategoriesWithoutConcreteEntities(): void
    {
        $client = $this->createGestionnaireClient();

        $site          = $this->createSite('Site Structure');
        $compType      = $this->createModelType('Motor Struct', 'MOTST', ModelCategory::COMPONENT);
        $pieceType     = $this->createModelType('Bearing Struct', 'BRGST', ModelCategory::PIECE);
        $contextField  = $this->createCustomField(
            name: 'RPM Struct',
            type: 'number',
            typeComposant: $compType,
            machineContextOnly: true,
        );

        $source        = $this->createMachine('Source Struct Machine', $site);
        $composant     = $this->createComposant('Motor ST', 'MOTST-001', $compType);
        $componentLink = $this->createMachineComponentLink($source, $composant);
        $piece         = $this->createPiece('Bearing ST', 'BRGST-001', $pieceType);
        $this->createMachinePieceLink($source, $piece, $componentLink);

        $this->createCustomFieldValue(
            customField: $contextField,
            value: '4200',
            composant: $composant,
            machineComponentLink: $componentLink,
        );

        $response = $client->request('POST', '/api/machines/'.$source->getId().'/clone', [
            'json' => [
                'name'   => 'Cloned Struct Machine',
                'siteId' => $site->getId(),
                'mode'   => 'structure',
            ],
        ]);

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

        // Component slot: category preserved, concrete component dropped, no context values.
        $clonedComponent = $data['componentLinks'][0] ?? null;
        $this->assertNotNull($clonedComponent, 'Structure clone should expose the component slot');
        $this->assertTrue($clonedComponent['pendingEntity']);
        $this->assertNull($clonedComponent['composantId']);
        $this->assertSame($compType->getId(), $clonedComponent['modelTypeId']);
        $this->assertCount(0, $clonedComponent['contextCustomFieldValues']);

        // Piece slot: category preserved, concrete piece dropped.
        $clonedPiece = $data['pieceLinks'][0] ?? null;
        $this->assertNotNull($clonedPiece, 'Structure clone should expose the piece slot');
        $this->assertTrue($clonedPiece['pendingEntity']);
        $this->assertNull($clonedPiece['pieceId']);
        $this->assertSame($pieceType->getId(), $clonedPiece['modelTypeId']);

        // Source machine stays intact (still has its concrete component).
        $sourceData = $client->request('GET', '/api/machines/'.$source->getId().'/structure')->toArray();
        $this->assertFalse($sourceData['componentLinks'][0]['pendingEntity']);
        $this->assertSame($composant->getId(), $sourceData['componentLinks'][0]['composantId']);
    }

    public function testCloneMachineFullModeStillCopiesConcreteEntities(): void
    {
        $client = $this->createGestionnaireClient();

        $site      = $this->createSite('Site Full');
        $compType  = $this->createModelType('Motor Full', 'MOTFL', ModelCategory::COMPONENT);
        $source    = $this->createMachine('Source Full Machine', $site);
        $composant = $this->createComposant('Motor FL', 'MOTFL-001', $compType);
        $this->createMachineComponentLink($source, $composant);

        $response = $client->request('POST', '/api/machines/'.$source->getId().'/clone', [
            'json' => [
                'name'   => 'Cloned Full Machine',
                'siteId' => $site->getId(),
                'mode'   => 'full',
            ],
        ]);

        $this->assertResponseStatusCodeSame(201);
        $clonedComponent = $response->toArray()['componentLinks'][0] ?? null;
        $this->assertNotNull($clonedComponent);
        $this->assertFalse($clonedComponent['pendingEntity']);
        $this->assertSame($composant->getId(), $clonedComponent['composantId']);
    }
}
