<?php

declare(strict_types=1);

namespace App\Service\Sync;

use App\DTO\SyncConfirmation;
use App\DTO\SyncExecutionResult;
use App\DTO\SyncPreviewResult;
use App\Entity\CustomField;
use App\Entity\CustomFieldValue;
use App\Entity\ModelType;
use App\Entity\Product;
use App\Enum\ModelCategory;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;

#[AutoconfigureTag('app.sync_strategy')]
class ProductSyncStrategy implements SyncStrategyInterface
{
    public function __construct(
        private readonly EntityManagerInterface $em,
    ) {}

    public function supports(ModelType $modelType): bool
    {
        return ModelCategory::PRODUCT === $modelType->getCategory();
    }

    public function preview(ModelType $modelType, array $newStructure): SyncPreviewResult
    {
        $products       = $this->em->getRepository(Product::class)->findBy(['typeProduct' => $modelType]);
        $existingFields = $this->em->getRepository(CustomField::class)->findBy(
            ['typeProduct' => $modelType],
            ['orderIndex' => 'ASC']
        );

        $proposedFields = $newStructure['customFields'] ?? [];

        // Map existing fields by orderIndex
        $existingByOrder = [];
        foreach ($existingFields as $field) {
            $existingByOrder[$field->getOrderIndex()] = $field;
        }

        // Map proposed fields by orderIndex (falls back to array index)
        $proposedByOrder = [];
        foreach ($proposedFields as $i => $pf) {
            $order                   = $pf['orderIndex'] ?? $i;
            $proposedByOrder[$order] = $pf;
        }

        $addedFields    = 0;
        $deletedFields  = 0;
        $modifiedFields = 0;

        // New fields (in proposed but not in existing)
        foreach ($proposedByOrder as $orderIndex => $pf) {
            if (!isset($existingByOrder[$orderIndex])) {
                ++$addedFields;
            } elseif ($existingByOrder[$orderIndex]->getType() !== ($pf['type'] ?? $pf['value']['type'] ?? null)) {
                ++$modifiedFields;
            }
        }

        // Deleted fields (in existing but not in proposed)
        foreach ($existingByOrder as $orderIndex => $ef) {
            if (!isset($proposedByOrder[$orderIndex])) {
                ++$deletedFields;
            }
        }

        $itemCount = count($products);

        return new SyncPreviewResult(
            modelTypeId: $modelType->getId(),
            category: 'product',
            itemCount: $itemCount,
            additions: ['customFieldValues' => $addedFields * $itemCount],
            deletions: ['customFieldValues' => $deletedFields * $itemCount],
            modifications: ['customFieldValues' => $modifiedFields * $itemCount],
        );
    }

    public function execute(ModelType $modelType, SyncConfirmation $confirmation): SyncExecutionResult
    {
        $products     = $this->em->getRepository(Product::class)->findBy(['typeProduct' => $modelType]);
        $customFields = $this->em->getRepository(CustomField::class)->findBy(
            ['typeProduct' => $modelType],
            ['orderIndex' => 'ASC']
        );

        $addedValues    = 0;
        $deletedValues  = 0;
        $modifiedValues = 0;
        $itemsUpdated   = 0;

        foreach ($products as $product) {
            $changed = false;

            // Get existing custom field values for this product
            $existingValues = $this->em->getRepository(CustomFieldValue::class)->findBy([
                'product' => $product,
            ]);

            // Map existing values by custom field ID
            $existingByFieldId = [];
            foreach ($existingValues as $cfv) {
                $existingByFieldId[$cfv->getCustomField()->getId()] = $cfv;
            }

            // For each custom field defined on the model type, ensure a value exists
            foreach ($customFields as $cf) {
                if (!isset($existingByFieldId[$cf->getId()])) {
                    // Create missing custom field value
                    $cfv = new CustomFieldValue();
                    $cfv->setCustomField($cf);
                    $cfv->setProduct($product);
                    $cfv->setValue('');
                    $this->em->persist($cfv);
                    ++$addedValues;
                    $changed = true;
                }
            }

            // Delete orphaned values if confirmDeletions
            if ($confirmation->confirmDeletions) {
                $fieldIds = array_map(fn (CustomField $cf) => $cf->getId(), $customFields);
                foreach ($existingValues as $cfv) {
                    if (!in_array($cfv->getCustomField()->getId(), $fieldIds, true)) {
                        $this->em->remove($cfv);
                        ++$deletedValues;
                        $changed = true;
                    }
                }
            }

            if ($changed) {
                $product->incrementVersion();
                ++$itemsUpdated;
            }
        }

        $this->em->flush();

        return new SyncExecutionResult(
            itemsUpdated: $itemsUpdated,
            additions: ['customFieldValues' => $addedValues],
            deletions: ['customFieldValues' => $deletedValues],
            modifications: ['customFieldValues' => $modifiedValues],
        );
    }
}
