<?php

declare(strict_types=1);

namespace App\Controller;

use App\Entity\CustomField;
use App\Entity\CustomFieldValue;
use App\Entity\Piece;
use App\Repository\ComposantRepository;
use App\Repository\CustomFieldRepository;
use App\Repository\CustomFieldValueRepository;
use App\Repository\MachineComponentLinkRepository;
use App\Repository\MachinePieceLinkRepository;
use App\Repository\MachineRepository;
use App\Repository\PieceRepository;
use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityNotFoundException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/api/custom-fields/values')]
class CustomFieldValueController extends AbstractController
{
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly CustomFieldRepository $customFieldRepository,
        private readonly CustomFieldValueRepository $customFieldValueRepository,
        private readonly MachineRepository $machineRepository,
        private readonly ComposantRepository $composantRepository,
        private readonly PieceRepository $pieceRepository,
        private readonly ProductRepository $productRepository,
        private readonly MachineComponentLinkRepository $machineComponentLinkRepository,
        private readonly MachinePieceLinkRepository $machinePieceLinkRepository,
    ) {}

    #[Route('', name: 'custom_field_values_create', methods: ['POST'])]
    public function create(Request $request): JsonResponse
    {
        $this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');

        $payload = $this->decodePayload($request);
        if ($payload instanceof JsonResponse) {
            return $payload;
        }

        $customField = $this->resolveCustomField($payload);
        if ($customField instanceof JsonResponse) {
            return $customField;
        }

        $target = $this->resolveTarget($payload);
        if ($target instanceof JsonResponse) {
            return $target;
        }

        $value = new CustomFieldValue();
        $value->setCustomField($customField);
        $value->setValue((string) ($payload['value'] ?? ''));
        $this->applyTarget($value, $target['type'], $target['entity']);

        $this->entityManager->persist($value);
        $this->entityManager->flush();

        return $this->json($this->normalizeCustomFieldValue($value));
    }

    #[Route('/upsert', name: 'custom_field_values_upsert', methods: ['POST'])]
    public function upsert(Request $request): JsonResponse
    {
        $this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');

        $payload = $this->decodePayload($request);
        if ($payload instanceof JsonResponse) {
            return $payload;
        }

        $customField = $this->resolveCustomField($payload);
        if ($customField instanceof JsonResponse) {
            return $customField;
        }

        $target = $this->resolveTarget($payload);
        if ($target instanceof JsonResponse) {
            return $target;
        }

        $existing = $this->customFieldValueRepository->findOneBy([
            'customField'   => $customField,
            $target['type'] => $target['entity'],
        ]);

        if ($existing instanceof CustomFieldValue) {
            $existing->setValue((string) ($payload['value'] ?? ''));
            $this->entityManager->flush();

            return $this->json($this->normalizeCustomFieldValue($existing));
        }

        $value = new CustomFieldValue();
        $value->setCustomField($customField);
        $value->setValue((string) ($payload['value'] ?? ''));
        $this->applyTarget($value, $target['type'], $target['entity']);

        $this->entityManager->persist($value);
        $this->entityManager->flush();

        return $this->json($this->normalizeCustomFieldValue($value));
    }

    #[Route('/{entityType}/{entityId}', name: 'custom_field_values_list', methods: ['GET'])]
    public function listByEntity(string $entityType, string $entityId): JsonResponse
    {
        $this->denyAccessUnlessGranted('ROLE_VIEWER');

        $target = $this->resolveTarget([
            'entityType' => $entityType,
            'entityId'   => $entityId,
        ]);

        if ($target instanceof JsonResponse) {
            return $target;
        }

        $values = $this->customFieldValueRepository->findBy([
            $target['type'] => $target['entity'],
        ]);

        return $this->json(array_map(
            fn (CustomFieldValue $value) => $this->normalizeCustomFieldValue($value),
            $values
        ));
    }

    #[Route('/{id}', name: 'custom_field_values_update', methods: ['PATCH'])]
    public function update(string $id, Request $request): JsonResponse
    {
        $this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');

        $value = $this->customFieldValueRepository->find($id);
        if (!$value instanceof CustomFieldValue) {
            return $this->json(['success' => false, 'error' => 'Custom field value not found.'], 404);
        }

        $payload = $this->decodePayload($request);
        if ($payload instanceof JsonResponse) {
            return $payload;
        }

        if (array_key_exists('value', $payload)) {
            $value->setValue((string) $payload['value']);
        }

        $this->entityManager->flush();

        return $this->json($this->normalizeCustomFieldValue($value));
    }

    #[Route('/{id}', name: 'custom_field_values_delete', methods: ['DELETE'])]
    public function delete(string $id): JsonResponse
    {
        $this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');

        $value = $this->customFieldValueRepository->find($id);
        if (!$value instanceof CustomFieldValue) {
            return $this->json(['success' => false, 'error' => 'Custom field value not found.'], 404);
        }

        $this->entityManager->remove($value);
        $this->entityManager->flush();

        return $this->json(['success' => true]);
    }

    private function decodePayload(Request $request): array|JsonResponse
    {
        $payload = json_decode($request->getContent(), true);
        if (!is_array($payload)) {
            return $this->json(['success' => false, 'error' => 'Invalid JSON payload.'], 400);
        }

        return $payload;
    }

    private function resolveCustomField(array $payload): CustomField|JsonResponse
    {
        $customFieldId = isset($payload['customFieldId']) ? trim((string) $payload['customFieldId']) : '';
        if ('' !== $customFieldId) {
            $customField = $this->customFieldRepository->find($customFieldId);
            if ($customField instanceof CustomField) {
                return $customField;
            }

            return $this->json(['success' => false, 'error' => 'Custom field not found.'], 404);
        }

        $customFieldName = isset($payload['customFieldName']) ? trim((string) $payload['customFieldName']) : '';
        if ('' === $customFieldName) {
            return $this->json(['success' => false, 'error' => 'customFieldId or customFieldName is required.'], 400);
        }

        // Try to find an existing custom field by name instead of creating an orphan
        $existing = $this->customFieldRepository->findOneBy(['name' => $customFieldName]);
        if ($existing instanceof CustomField) {
            return $existing;
        }

        return $this->json([
            'success' => false,
            'error'   => sprintf('Custom field "%s" not found. Create it explicitly first.', $customFieldName),
        ], 404);
    }

    private function resolveTarget(array $payload): array|JsonResponse
    {
        $entityType = isset($payload['entityType']) ? strtolower((string) $payload['entityType']) : '';
        $entityId   = isset($payload['entityId']) ? trim((string) $payload['entityId']) : '';

        if ('' === $entityType || '' === $entityId) {
            foreach (['machine', 'composant', 'piece', 'product', 'machineComponentLink', 'machinePieceLink'] as $candidate) {
                $key = $candidate.'Id';
                if (!isset($payload[$key])) {
                    continue;
                }
                $entityType = $candidate;
                $entityId   = trim((string) $payload[$key]);

                break;
            }
        }

        $entityType = strtolower($entityType);

        if ('' === $entityType || '' === $entityId) {
            return $this->json(['success' => false, 'error' => 'Entity target is missing.'], 400);
        }

        return match ($entityType) {
            'machine'              => $this->resolveEntity('machine', $entityId, $this->machineRepository),
            'composant'            => $this->resolveEntity('composant', $entityId, $this->composantRepository),
            'piece'                => $this->resolveEntity('piece', $entityId, $this->pieceRepository),
            'product'              => $this->resolveEntity('product', $entityId, $this->productRepository),
            'machinecomponentlink' => $this->resolveEntity('machineComponentLink', $entityId, $this->machineComponentLinkRepository),
            'machinepiecelink'     => $this->resolveEntity('machinePieceLink', $entityId, $this->machinePieceLinkRepository),
            default                => $this->json(['success' => false, 'error' => 'Unsupported entity type.'], 400),
        };
    }

    private function resolveEntity(string $type, string $id, $repository): array|JsonResponse
    {
        $entity = $repository->find($id);
        if (!$entity) {
            return $this->json(['success' => false, 'error' => sprintf('%s not found.', $type)], 404);
        }

        return ['type' => $type, 'entity' => $entity];
    }

    private function applyTarget(CustomFieldValue $value, string $type, object $entity): void
    {
        switch ($type) {
            case 'machine':
                $value->setMachine($entity);

                break;

            case 'composant':
                $value->setComposant($entity);

                break;

            case 'piece':
                $value->setPiece($entity);

                break;

            case 'product':
                $value->setProduct($entity);

                break;

            case 'machineComponentLink':
                $value->setMachineComponentLink($entity);
                $value->setComposant($entity->getComposant());

                break;

            case 'machinePieceLink':
                $value->setMachinePieceLink($entity);
                $value->setPiece($this->ensurePieceExists($entity->getPiece()));

                break;
        }
    }

    /**
     * Returns the Piece if its underlying row still exists in DB, otherwise null.
     * getId() on a Doctrine proxy does NOT trigger __load(), so we force the proxy
     * to initialize explicitly to handle orphan links here instead of crashing on
     * the first real getter.
     */
    private function ensurePieceExists(?Piece $piece): ?Piece
    {
        if (null === $piece) {
            return null;
        }
        try {
            $this->entityManager->initializeObject($piece);

            return $piece;
        } catch (EntityNotFoundException) {
            return null;
        }
    }

    /**
     * getId() on a Doctrine proxy returns the identifier without triggering __load(),
     * so it never raises EntityNotFoundException even if the row is gone. Force the
     * proxy to initialize explicitly so an orphan CFV is handled here instead of
     * crashing on the first real getter.
     */
    private function ensureCustomFieldExists(?CustomField $cf): ?CustomField
    {
        if (null === $cf) {
            return null;
        }
        try {
            $this->entityManager->initializeObject($cf);

            return $cf;
        } catch (EntityNotFoundException) {
            return null;
        }
    }

    private function normalizeCustomFieldValue(CustomFieldValue $value): array
    {
        $customField = $this->ensureCustomFieldExists($value->getCustomField());

        return [
            'id'            => $value->getId(),
            'value'         => $value->getValue(),
            'customFieldId' => $customField?->getId(),
            'customField'   => $customField ? [
                'id'         => $customField->getId(),
                'name'       => $customField->getName(),
                'type'       => $customField->getType(),
                'required'   => $customField->isRequired(),
                'options'    => $customField->getOptions(),
                'orderIndex' => $customField->getOrderIndex(),
            ] : null,
            'machineId'   => $value->getMachine()?->getId(),
            'composantId' => $value->getComposant()?->getId(),
            'pieceId'     => $value->getPiece()?->getId(),
            'productId'   => $value->getProduct()?->getId(),
            'createdAt'   => $value->getCreatedAt()->format(DATE_ATOM),
            'updatedAt'   => $value->getUpdatedAt()->format(DATE_ATOM),
        ];
    }
}
