<?php

declare(strict_types=1);

namespace App\Mcp\Tool\Machine;

use App\Entity\Composant;
use App\Entity\Constructeur;
use App\Entity\CustomField;
use App\Entity\CustomFieldValue;
use App\Entity\Machine;
use App\Entity\MachineComponentLink;
use App\Entity\MachinePieceLink;
use App\Entity\MachineProductLink;
use App\Entity\ModelType;
use App\Entity\Piece;
use App\Entity\Product;
use App\Mcp\Tool\McpToolHelper;
use App\Repository\MachineComponentLinkRepository;
use App\Repository\MachinePieceLinkRepository;
use App\Repository\MachineProductLinkRepository;
use App\Repository\MachineRepository;
use Doctrine\Common\Collections\Collection;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;

#[McpTool(
    name: 'get_machine_structure',
    description: 'Get the full machine hierarchy: machine info, component links (with composant details, slots, overrides), piece links (with piece details, quantity, overrides), and product links.',
)]
class MachineStructureTool
{
    use McpToolHelper;

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

    public function __invoke(string $machineId): CallToolResult
    {
        $machine = $this->machineRepository->find($machineId);

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

        $componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']);
        $pieceLinks     = $this->machinePieceLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']);
        $productLinks   = $this->machineProductLinkRepository->findBy(['machine' => $machine], ['createdAt' => 'ASC']);

        return $this->jsonResponse($this->normalizeStructureResponse(
            $machine,
            $componentLinks,
            $pieceLinks,
            $productLinks,
        ));
    }

    /**
     * @param MachineComponentLink[] $componentLinks
     * @param MachinePieceLink[]     $pieceLinks
     * @param MachineProductLink[]   $productLinks
     */
    private function normalizeStructureResponse(
        Machine $machine,
        array $componentLinks,
        array $pieceLinks,
        array $productLinks,
    ): array {
        $normalizedComponentLinks = $this->normalizeComponentLinks($componentLinks);
        $componentIndex           = $this->indexById($normalizedComponentLinks);
        $normalizedPieceLinks     = $this->normalizePieceLinks($pieceLinks);

        $childIds = [];
        foreach ($normalizedComponentLinks as $link) {
            $parentId = $link['parentComponentLinkId'] ?? null;
            if ($parentId && isset($componentIndex[$parentId])) {
                $componentIndex[$parentId]['childLinks'][] = $link;
                $childIds[$link['id']]                     = true;
            }
        }

        $this->attachPiecesToComponents($componentIndex, $normalizedPieceLinks);

        $rootComponents = array_filter(
            $componentIndex,
            static fn (array $link) => !isset($childIds[$link['id']]),
        );

        return [
            'machine'        => $this->normalizeMachine($machine),
            'componentLinks' => array_values($rootComponents),
            'pieceLinks'     => $normalizedPieceLinks,
            'productLinks'   => $this->normalizeProductLinks($productLinks),
        ];
    }

    private function attachPiecesToComponents(array &$componentIndex, array $pieceLinks): void
    {
        foreach ($pieceLinks as $pieceLink) {
            $parentId = $pieceLink['parentComponentLinkId'] ?? null;
            if ($parentId && isset($componentIndex[$parentId])) {
                $componentIndex[$parentId]['pieceLinks'][] = $pieceLink;
            }
        }

        foreach ($componentIndex as &$component) {
            if (!empty($component['childLinks'])) {
                $this->attachPiecesToChildComponents($component['childLinks'], $pieceLinks);
            }
        }
    }

    private function attachPiecesToChildComponents(array &$childLinks, array $pieceLinks): void
    {
        foreach ($childLinks as &$child) {
            $childId = $child['id'] ?? null;
            if ($childId) {
                foreach ($pieceLinks as $pieceLink) {
                    if (($pieceLink['parentComponentLinkId'] ?? null) === $childId) {
                        $child['pieceLinks'][] = $pieceLink;
                    }
                }
            }

            if (!empty($child['childLinks'])) {
                $this->attachPiecesToChildComponents($child['childLinks'], $pieceLinks);
            }
        }
    }

    private function normalizeMachine(Machine $machine): array
    {
        $site = $machine->getSite();

        return [
            'id'        => $machine->getId(),
            'name'      => $machine->getName(),
            'reference' => $machine->getReference(),
            'prix'      => $machine->getPrix(),
            'siteId'    => $site->getId(),
            'site'      => [
                'id'   => $site->getId(),
                'name' => $site->getName(),
            ],
            'constructeurs'     => $this->normalizeConstructeurLinks($machine->getConstructeurLinks()),
            'customFields'      => $this->normalizeCustomFields($machine->getCustomFields()),
            'customFieldValues' => $this->normalizeCustomFieldValues($machine->getCustomFieldValues()),
        ];
    }

    /**
     * @param MachineComponentLink[] $links
     */
    private function normalizeComponentLinks(array $links): array
    {
        return array_map(function (MachineComponentLink $link): array {
            $composant  = $link->getComposant();
            $parentLink = $link->getParentLink();

            return [
                'id'                    => $link->getId(),
                'linkId'                => $link->getId(),
                'machineId'             => $link->getMachine()->getId(),
                'composantId'           => $composant?->getId(),
                'composant'             => $composant ? $this->normalizeComposant($composant) : null,
                'parentLinkId'          => $parentLink?->getId(),
                'parentComponentLinkId' => $parentLink?->getId(),
                'parentComponentId'     => $parentLink?->getComposant()?->getId(),
                'overrides'             => $this->normalizeOverrides($link),
                'childLinks'            => [],
                'pieceLinks'            => [],
            ];
        }, $links);
    }

    /**
     * @param MachinePieceLink[] $links
     */
    private function normalizePieceLinks(array $links): array
    {
        return array_map(function (MachinePieceLink $link): array {
            $piece      = $link->getPiece();
            $parentLink = $link->getParentLink();

            return [
                'id'                    => $link->getId(),
                'linkId'                => $link->getId(),
                'machineId'             => $link->getMachine()->getId(),
                'pieceId'               => $piece?->getId(),
                'piece'                 => $piece ? $this->normalizePiece($piece) : null,
                'parentLinkId'          => $parentLink?->getId(),
                'parentComponentLinkId' => $parentLink?->getId(),
                'parentComponentId'     => $parentLink?->getComposant()?->getId(),
                'overrides'             => $this->normalizeOverrides($link),
                'quantity'              => $piece ? $this->resolvePieceQuantity($link) : 1,
            ];
        }, $links);
    }

    private function resolvePieceQuantity(MachinePieceLink $link): int
    {
        $parentLink = $link->getParentLink();

        if (!$parentLink) {
            return $link->getQuantity();
        }

        $composant = $parentLink->getComposant();
        $piece     = $link->getPiece();

        foreach ($composant->getPieceSlots() as $slot) {
            if ($slot->getSelectedPiece()?->getId() === $piece->getId()) {
                return $slot->getQuantity();
            }
        }

        return $link->getQuantity();
    }

    /**
     * @param MachineProductLink[] $links
     */
    private function normalizeProductLinks(array $links): array
    {
        return array_map(function (MachineProductLink $link): array {
            $product = $link->getProduct();

            return [
                'id'                    => $link->getId(),
                'linkId'                => $link->getId(),
                'machineId'             => $link->getMachine()->getId(),
                'productId'             => $product->getId(),
                'product'               => $this->normalizeProduct($product),
                'parentLinkId'          => $link->getParentLink()?->getId(),
                'parentComponentLinkId' => $link->getParentComponentLink()?->getId(),
                'parentPieceLinkId'     => $link->getParentPieceLink()?->getId(),
            ];
        }, $links);
    }

    private function normalizeComposant(Composant $composant): array
    {
        $type = $composant->getTypeComposant();

        return [
            'id'                => $composant->getId(),
            'name'              => $composant->getName(),
            'reference'         => $composant->getReference(),
            'prix'              => $composant->getPrix(),
            'typeComposantId'   => $type?->getId(),
            'typeComposant'     => $this->normalizeModelType($type),
            'productId'         => $composant->getProduct()?->getId(),
            'product'           => $composant->getProduct() ? $this->normalizeProduct($composant->getProduct()) : null,
            'structure'         => $this->buildStructureFromSlots($composant),
            'constructeurs'     => $this->normalizeConstructeurLinks($composant->getConstructeurLinks()),
            'customFields'      => $type ? $this->normalizeCustomFieldDefinitions($type->getComponentCustomFields()) : [],
            'customFieldValues' => $this->normalizeCustomFieldValues($composant->getCustomFieldValues()),
        ];
    }

    private function buildStructureFromSlots(Composant $composant): array
    {
        $pieces = [];
        foreach ($composant->getPieceSlots() as $slot) {
            $pieceData = [
                'slotId'          => $slot->getId(),
                'typePieceId'     => $slot->getTypePiece()?->getId(),
                'quantity'        => $slot->getQuantity(),
                'selectedPieceId' => $slot->getSelectedPiece()?->getId(),
            ];
            if ($slot->getSelectedPiece()) {
                $pieceData['resolvedPiece'] = $this->normalizePiece($slot->getSelectedPiece());
            }
            $pieces[] = $pieceData;
        }

        $subcomponents = [];
        foreach ($composant->getSubcomponentSlots() as $slot) {
            $subcomponents[] = [
                'alias'               => $slot->getAlias(),
                'familyCode'          => $slot->getFamilyCode(),
                'typeComposantId'     => $slot->getTypeComposant()?->getId(),
                'selectedComponentId' => $slot->getSelectedComposant()?->getId(),
            ];
        }

        $products = [];
        foreach ($composant->getProductSlots() as $slot) {
            $products[] = [
                'typeProductId'     => $slot->getTypeProduct()?->getId(),
                'familyCode'        => $slot->getFamilyCode(),
                'selectedProductId' => $slot->getSelectedProduct()?->getId(),
            ];
        }

        return [
            'pieces'        => $pieces,
            'subcomponents' => $subcomponents,
            'products'      => $products,
        ];
    }

    private function normalizePiece(Piece $piece): array
    {
        $type = $piece->getTypePiece();

        return [
            'id'                => $piece->getId(),
            'name'              => $piece->getName(),
            'reference'         => $piece->getReference(),
            'prix'              => $piece->getPrix(),
            'typePieceId'       => $type?->getId(),
            'typePiece'         => $this->normalizeModelType($type),
            'productId'         => $piece->getProduct()?->getId(),
            'product'           => $piece->getProduct() ? $this->normalizeProduct($piece->getProduct()) : null,
            'constructeurs'     => $this->normalizeConstructeurLinks($piece->getConstructeurLinks()),
            'customFields'      => $type ? $this->normalizeCustomFieldDefinitions($type->getPieceCustomFields()) : [],
            'customFieldValues' => $this->normalizeCustomFieldValues($piece->getCustomFieldValues()),
        ];
    }

    private function normalizeProduct(Product $product): array
    {
        $type = $product->getTypeProduct();

        return [
            'id'                => $product->getId(),
            'name'              => $product->getName(),
            'reference'         => $product->getReference(),
            'supplierPrice'     => $product->getSupplierPrice(),
            'typeProductId'     => $type?->getId(),
            'typeProduct'       => $this->normalizeModelType($type),
            'constructeurs'     => $this->normalizeConstructeurLinks($product->getConstructeurLinks()),
            'customFields'      => $type ? $this->normalizeCustomFieldDefinitions($type->getProductCustomFields()) : [],
            'customFieldValues' => $this->normalizeCustomFieldValues($product->getCustomFieldValues()),
        ];
    }

    private function normalizeModelType(?ModelType $type): ?array
    {
        if (!$type instanceof ModelType) {
            return null;
        }

        return [
            'id'        => $type->getId(),
            'name'      => $type->getName(),
            'code'      => $type->getCode(),
            'category'  => $type->getCategory()->value,
            'structure' => $type->getStructure(),
        ];
    }

    private function normalizeConstructeurLinks(Collection $constructeurLinks): array
    {
        $items = [];
        foreach ($constructeurLinks as $link) {
            $items[] = [
                'id'           => $link->getId(),
                'constructeur' => [
                    'id'    => $link->getConstructeur()->getId(),
                    'name'  => $link->getConstructeur()->getName(),
                    'email' => $link->getConstructeur()->getEmail(),
                    'phone' => $this->constructeurPhone($link->getConstructeur()),
                ],
                'supplierReference' => $link->getSupplierReference(),
            ];
        }

        return $items;
    }

    private function constructeurPhone(Constructeur $constructeur): ?string
    {
        $first = $constructeur->getTelephones()->first();

        return false !== $first ? $first->getNumero() : null;
    }

    private function normalizeCustomFields(Collection $customFields): array
    {
        $items = [];
        foreach ($customFields as $customField) {
            if (!$customField instanceof CustomField) {
                continue;
            }
            $items[] = [
                'id'           => $customField->getId(),
                'name'         => $customField->getName(),
                'type'         => $customField->getType(),
                'required'     => $customField->isRequired(),
                'options'      => $customField->getOptions(),
                'defaultValue' => $customField->getDefaultValue(),
                'orderIndex'   => $customField->getOrderIndex(),
            ];
        }

        return $items;
    }

    private function normalizeCustomFieldDefinitions(Collection $customFields): array
    {
        $items = [];
        foreach ($customFields as $cf) {
            if (!$cf instanceof CustomField) {
                continue;
            }
            $items[] = [
                'id'           => $cf->getId(),
                'name'         => $cf->getName(),
                'type'         => $cf->getType(),
                'required'     => $cf->isRequired(),
                'options'      => $cf->getOptions(),
                'defaultValue' => $cf->getDefaultValue(),
                'orderIndex'   => $cf->getOrderIndex(),
            ];
        }

        usort($items, static fn (array $a, array $b) => $a['orderIndex'] <=> $b['orderIndex']);

        return $items;
    }

    private function normalizeCustomFieldValues(Collection $customFieldValues): array
    {
        $items = [];
        foreach ($customFieldValues as $cfv) {
            if (!$cfv instanceof CustomFieldValue) {
                continue;
            }
            $cf      = $cfv->getCustomField();
            $items[] = [
                'id'          => $cfv->getId(),
                'value'       => $cfv->getValue(),
                'customField' => [
                    'id'           => $cf->getId(),
                    'name'         => $cf->getName(),
                    'type'         => $cf->getType(),
                    'required'     => $cf->isRequired(),
                    'options'      => $cf->getOptions(),
                    'defaultValue' => $cf->getDefaultValue(),
                    'orderIndex'   => $cf->getOrderIndex(),
                ],
            ];
        }

        return $items;
    }

    private function normalizeOverrides(object $link): ?array
    {
        $name      = method_exists($link, 'getNameOverride') ? $link->getNameOverride() : null;
        $reference = method_exists($link, 'getReferenceOverride') ? $link->getReferenceOverride() : null;
        $prix      = method_exists($link, 'getPrixOverride') ? $link->getPrixOverride() : null;

        if (null === $name && null === $reference && null === $prix) {
            return null;
        }

        return [
            'name'      => $name,
            'reference' => $reference,
            'prix'      => $prix,
        ];
    }

    private function indexById(array $links): array
    {
        $indexed = [];
        foreach ($links as $link) {
            if (is_array($link) && isset($link['id'])) {
                $indexed[$link['id']] = $link;
            }
        }

        return $indexed;
    }
}
