<?php

declare(strict_types=1);

namespace App\Module\ProjectManagement\Infrastructure\ApiPlatform\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\TaskStatus;
use App\Module\ProjectManagement\Domain\Entity\Workflow;
use App\Module\ProjectManagement\Infrastructure\ApiPlatform\Resource\SwitchWorkflowOutput;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Throwable;

/**
 * Wraps the switch-workflow operation for a project.
 * Input: Project (URI variable) + body { workflowId, mapping: { sourceStatusId: targetStatusId|null } }.
 *
 * @implements ProcessorInterface<Project, SwitchWorkflowOutput>
 */
final readonly class SwitchProjectWorkflowProcessor implements ProcessorInterface
{
    public function __construct(
        private EntityManagerInterface $entityManager,
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): SwitchWorkflowOutput
    {
        /** @var Project $project */
        $project = $data;

        $request = $context['request'] ?? null;
        $body    = $request ? json_decode($request->getContent(), true) : [];

        $workflowId = $body['workflowId'] ?? null;
        $mapping    = $body['mapping'] ?? [];

        if (!is_int($workflowId) || !is_array($mapping)) {
            throw new HttpException(422, 'Body must contain workflowId (int) and mapping (object).');
        }

        $targetWorkflow = $this->entityManager->find(Workflow::class, $workflowId);
        if (!$targetWorkflow instanceof Workflow) {
            throw new NotFoundHttpException('Target workflow not found.');
        }

        // 1) Lister les statuts source effectivement référencés par les tâches du projet
        $rows = $this->entityManager->getConnection()->fetchAllAssociative(
            'SELECT DISTINCT status_id FROM task WHERE project_id = :pid AND status_id IS NOT NULL',
            ['pid' => $project->getId()],
        );
        $referencedSourceIds = array_map(static fn ($r) => (int) $r['status_id'], $rows);

        // 2) Vérifier que chaque source a un mapping
        $missing = [];
        foreach ($referencedSourceIds as $srcId) {
            if (!array_key_exists((string) $srcId, $mapping)) {
                $missing[] = $srcId;
            }
        }
        if ([] !== $missing) {
            throw new HttpException(422, 'Missing mapping for source status IDs: '.implode(', ', $missing));
        }

        // 3) Valider que chaque target appartient au workflow cible (ou est null)
        foreach ($mapping as $srcId => $targetId) {
            if (null === $targetId) {
                continue;
            }
            $target = $this->entityManager->find(TaskStatus::class, $targetId);
            if (!$target instanceof TaskStatus
                || $target->getWorkflow()?->getId() !== $targetWorkflow->getId()) {
                throw new HttpException(422, sprintf(
                    'Target status %s does not belong to workflow %d.',
                    var_export($targetId, true),
                    $targetWorkflow->getId(),
                ));
            }
        }

        // 4) Transaction unique
        $conn = $this->entityManager->getConnection();
        $conn->beginTransaction();

        try {
            $migrated = 0;
            foreach ($mapping as $srcId => $targetId) {
                $affected = $conn->executeStatement(
                    'UPDATE task SET status_id = :tid WHERE project_id = :pid AND status_id = :sid',
                    ['tid' => $targetId, 'pid' => $project->getId(), 'sid' => (int) $srcId],
                );
                $migrated += $affected;
            }

            $project->setWorkflow($targetWorkflow);
            $this->entityManager->flush();
            $conn->commit();
        } catch (Throwable $e) {
            $conn->rollBack();

            throw $e;
        }

        return new SwitchWorkflowOutput(
            projectId: $project->getId(),
            workflowId: $targetWorkflow->getId(),
            migratedTaskCount: $migrated,
        );
    }
}
