<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Api\Processor;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Administration\Application\Command\ActivateAccount\ActivateAccountCommand;
use App\Administration\Application\Command\ActivateAccount\ActivateAccountHandler;
use App\Administration\Application\Command\LinkParentToStudent\LinkParentToStudentCommand;
use App\Administration\Application\Command\LinkParentToStudent\LinkParentToStudentHandler;
use App\Administration\Domain\Exception\ActivationTokenAlreadyUsedException;
use App\Administration\Domain\Exception\ActivationTokenExpiredException;
use App\Administration\Domain\Exception\ActivationTokenNotFoundException;
use App\Administration\Domain\Exception\CompteNonActivableException;
use App\Administration\Domain\Exception\UserNotFoundException;
use App\Administration\Domain\Model\StudentGuardian\RelationshipType;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Domain\Policy\ConsentementParentalPolicy;
use App\Administration\Domain\Repository\ActivationTokenRepository;
use App\Administration\Domain\Repository\UserRepository;
use App\Administration\Infrastructure\Api\Resource\ActivateAccountInput;
use App\Administration\Infrastructure\Api\Resource\ActivateAccountOutput;
use App\Shared\Domain\Clock;
use Override;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Messenger\MessageBusInterface;
use Throwable;

/**
 * API Platform processor for account activation.
 *
 * @implements ProcessorInterface<ActivateAccountInput, ActivateAccountOutput>
 */
final readonly class ActivateAccountProcessor implements ProcessorInterface
{
    public function __construct(
        private ActivateAccountHandler $handler,
        private UserRepository $userRepository,
        private ActivationTokenRepository $tokenRepository,
        private ConsentementParentalPolicy $consentementPolicy,
        private Clock $clock,
        private MessageBusInterface $eventBus,
        private LinkParentToStudentHandler $linkHandler,
        private LoggerInterface $logger,
    ) {
    }

    /**
     * @param ActivateAccountInput $data
     */
    #[Override]
    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ActivateAccountOutput
    {
        $command = new ActivateAccountCommand(
            tokenValue: $data->tokenValue,
            password: $data->password,
        );

        try {
            $result = ($this->handler)($command);
        } catch (ActivationTokenNotFoundException) {
            throw new NotFoundHttpException('Token d\'activation invalide ou introuvable.');
        } catch (ActivationTokenExpiredException) {
            throw new BadRequestHttpException('Le token d\'activation a expiré. Veuillez contacter votre établissement pour obtenir un nouveau lien.');
        } catch (ActivationTokenAlreadyUsedException) {
            throw new BadRequestHttpException('Ce token d\'activation a déjà été utilisé.');
        }

        // Activate the User account
        try {
            $user = $this->userRepository->get(UserId::fromString($result->userId));
            $user->activer(
                hashedPassword: $result->hashedPassword,
                at: $this->clock->now(),
                consentementPolicy: $this->consentementPolicy,
            );
            $this->userRepository->save($user);

            // Publish domain events recorded on the User aggregate
            foreach ($user->pullDomainEvents() as $event) {
                $this->eventBus->dispatch($event);
            }

            // Delete token only after successful user activation
            // This ensures failed activations (e.g., missing parental consent) don't burn the token
            $this->tokenRepository->deleteByTokenValue($data->tokenValue);

            // Create automatic parent-student link if invitation included a studentId
            // Linking failure is non-fatal: the activation is the primary goal
            if ($result->studentId !== null) {
                try {
                    $link = ($this->linkHandler)(new LinkParentToStudentCommand(
                        studentId: $result->studentId,
                        guardianId: $result->userId,
                        relationshipType: $result->relationshipType ?? RelationshipType::OTHER->value,
                        tenantId: (string) $result->tenantId,
                    ));

                    foreach ($link->pullDomainEvents() as $linkEvent) {
                        $this->eventBus->dispatch($linkEvent);
                    }
                } catch (Throwable $e) {
                    $this->logger->warning('Auto-link parent-élève échoué lors de l\'activation : {message}', [
                        'message' => $e->getMessage(),
                        'userId' => $result->userId,
                        'studentId' => $result->studentId,
                    ]);
                }
            }
        } catch (UserNotFoundException) {
            throw new NotFoundHttpException('Utilisateur introuvable.');
        } catch (CompteNonActivableException $e) {
            throw new BadRequestHttpException($e->getMessage());
        }

        return new ActivateAccountOutput(
            userId: $result->userId,
            email: $result->email,
            role: $result->role,
        );
    }
}
