<?php

declare(strict_types=1);

namespace App\Mcp\Tool\Machine;

use App\Entity\CustomField;
use App\Entity\CustomFieldValue;
use App\Entity\Machine;
use App\Entity\MachineComponentLink;
use App\Entity\MachineConstructeurLink;
use App\Entity\MachinePieceLink;
use App\Entity\MachineProductLink;
use App\Entity\Site;
use App\Mcp\Tool\McpToolHelper;
use App\Repository\MachineComponentLinkRepository;
use App\Repository\MachinePieceLinkRepository;
use App\Repository\MachineProductLinkRepository;
use App\Repository\MachineRepository;
use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security;

#[McpTool(
    name: 'clone_machine',
    description: 'Clone an existing machine with all its links (components, pieces, products), custom fields, and constructeurs. Requires ROLE_GESTIONNAIRE.',
)]
class CloneMachineTool
{
    use McpToolHelper;

    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly MachineRepository $machineRepository,
        private readonly MachineComponentLinkRepository $machineComponentLinkRepository,
        private readonly MachinePieceLinkRepository $machinePieceLinkRepository,
        private readonly MachineProductLinkRepository $machineProductLinkRepository,
        private readonly Security $security,
    ) {}

    public function __invoke(
        string $machineId,
        string $name,
        string $siteId,
        string $reference = '',
    ): CallToolResult {
        $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

        $source = $this->machineRepository->find($machineId);
        if (!$source instanceof Machine) {
            $this->mcpError('not_found', "Machine not found: {$machineId}");
        }

        $site = $this->entityManager->getRepository(Site::class)->find($siteId);
        if (!$site) {
            $this->mcpError('not_found', "Site not found: {$siteId}");
        }

        // Create new machine
        $newMachine = new Machine();
        $newMachine->setName($name);
        $newMachine->setSite($site);
        if ('' !== $reference) {
            $newMachine->setReference($reference);
        }
        $newMachine->setPrix($source->getPrix());

        // Copy constructeur links
        foreach ($source->getConstructeurLinks() as $link) {
            $newLink = new MachineConstructeurLink();
            $newLink->setMachine($newMachine);
            $newLink->setConstructeur($link->getConstructeur());
            $newLink->setSupplierReference($link->getSupplierReference());
            $this->entityManager->persist($newLink);
        }

        $this->entityManager->persist($newMachine);

        // Copy custom fields and values
        $this->cloneCustomFields($source, $newMachine);

        // Copy component links (preserving hierarchy with two-pass)
        $componentLinkMap = $this->cloneComponentLinks($source, $newMachine);

        // Copy piece links
        $pieceLinkMap = $this->clonePieceLinks($source, $newMachine, $componentLinkMap);

        // Copy product links
        $this->cloneProductLinks($source, $newMachine, $componentLinkMap, $pieceLinkMap);

        $this->entityManager->flush();

        return $this->jsonResponse([
            'id'         => $newMachine->getId(),
            'name'       => $newMachine->getName(),
            'reference'  => $newMachine->getReference(),
            'siteId'     => $site->getId(),
            'clonedFrom' => $source->getId(),
        ]);
    }

    private function cloneCustomFields(Machine $source, Machine $target): void
    {
        foreach ($source->getCustomFields() as $cf) {
            $newCf = new CustomField();
            $newCf->setName($cf->getName());
            $newCf->setType($cf->getType());
            $newCf->setRequired($cf->isRequired());
            $newCf->setDefaultValue($cf->getDefaultValue());
            $newCf->setOptions($cf->getOptions());
            $newCf->setOrderIndex($cf->getOrderIndex());
            $newCf->setMachine($target);
            $this->entityManager->persist($newCf);
        }

        foreach ($source->getCustomFieldValues() as $cfv) {
            $newValue = new CustomFieldValue();
            $newValue->setMachine($target);
            $newValue->setCustomField($cfv->getCustomField());
            $newValue->setValue($cfv->getValue());
            $this->entityManager->persist($newValue);
        }
    }

    /**
     * @return array<string, MachineComponentLink> Map of old link ID to new link
     */
    private function cloneComponentLinks(Machine $source, Machine $target): array
    {
        $sourceLinks = $this->machineComponentLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']);
        $linkMap     = [];

        // First pass: create all links without parent relationships
        foreach ($sourceLinks as $link) {
            $newLink = new MachineComponentLink();
            $newLink->setMachine($target);
            $newLink->setComposant($link->getComposant());
            $newLink->setNameOverride($link->getNameOverride());
            $newLink->setReferenceOverride($link->getReferenceOverride());
            $newLink->setPrixOverride($link->getPrixOverride());
            $this->entityManager->persist($newLink);
            $linkMap[$link->getId()] = $newLink;
        }

        // Second pass: set parent relationships
        foreach ($sourceLinks as $link) {
            $parent = $link->getParentLink();
            if ($parent && isset($linkMap[$parent->getId()])) {
                $linkMap[$link->getId()]->setParentLink($linkMap[$parent->getId()]);
            }
        }

        return $linkMap;
    }

    /**
     * @param array<string, MachineComponentLink> $componentLinkMap
     *
     * @return array<string, MachinePieceLink> Map of old link ID to new link
     */
    private function clonePieceLinks(Machine $source, Machine $target, array $componentLinkMap): array
    {
        $sourceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']);
        $linkMap     = [];

        foreach ($sourceLinks as $link) {
            $newLink = new MachinePieceLink();
            $newLink->setMachine($target);
            $newLink->setPiece($link->getPiece());
            $newLink->setNameOverride($link->getNameOverride());
            $newLink->setReferenceOverride($link->getReferenceOverride());
            $newLink->setPrixOverride($link->getPrixOverride());
            $newLink->setQuantity($link->getQuantity());

            $parent = $link->getParentLink();
            if ($parent && isset($componentLinkMap[$parent->getId()])) {
                $newLink->setParentLink($componentLinkMap[$parent->getId()]);
            }

            $this->entityManager->persist($newLink);
            $linkMap[$link->getId()] = $newLink;
        }

        return $linkMap;
    }

    /**
     * @param array<string, MachineComponentLink> $componentLinkMap
     * @param array<string, MachinePieceLink>     $pieceLinkMap
     */
    private function cloneProductLinks(
        Machine $source,
        Machine $target,
        array $componentLinkMap,
        array $pieceLinkMap,
    ): void {
        $sourceLinks = $this->machineProductLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']);
        $linkMap     = [];

        // First pass: create all links
        foreach ($sourceLinks as $link) {
            $newLink = new MachineProductLink();
            $newLink->setMachine($target);
            $newLink->setProduct($link->getProduct());

            $parentComponent = $link->getParentComponentLink();
            if ($parentComponent && isset($componentLinkMap[$parentComponent->getId()])) {
                $newLink->setParentComponentLink($componentLinkMap[$parentComponent->getId()]);
            }

            $parentPiece = $link->getParentPieceLink();
            if ($parentPiece && isset($pieceLinkMap[$parentPiece->getId()])) {
                $newLink->setParentPieceLink($pieceLinkMap[$parentPiece->getId()]);
            }

            $this->entityManager->persist($newLink);
            $linkMap[$link->getId()] = $newLink;
        }

        // Second pass: set parent product link relationships
        foreach ($sourceLinks as $link) {
            $parent = $link->getParentLink();
            if ($parent && isset($linkMap[$parent->getId()])) {
                $linkMap[$link->getId()]->setParentLink($linkMap[$parent->getId()]);
            }
        }
    }
}
