<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Api\Controller;

use App\Administration\Application\Command\SendParentInvitation\SendParentInvitationCommand;
use App\Administration\Application\Command\SendParentInvitation\SendParentInvitationHandler;
use App\Administration\Infrastructure\Api\Resource\ParentInvitationResource;
use App\Administration\Infrastructure\Security\ParentInvitationVoter;
use App\Administration\Infrastructure\Security\SecurityUser;
use App\Shared\Infrastructure\Tenant\TenantContext;

use function array_key_exists;
use function count;

use DomainException;

use function is_array;
use function is_string;
use function sprintf;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Throwable;

#[Route('/api/parent-invitations/bulk', name: 'api_parent_invitations_bulk', methods: ['POST'])]
#[IsGranted(ParentInvitationVoter::CREATE)]
final class BulkParentInvitationController extends AbstractController
{
    private const int MAX_BULK_SIZE = 500;

    public function __construct(
        private readonly SendParentInvitationHandler $handler,
        private readonly TenantContext $tenantContext,
        private readonly MessageBusInterface $eventBus,
    ) {
    }

    public function __invoke(Request $request): JsonResponse
    {
        $currentUser = $this->getUser();
        if (!$currentUser instanceof SecurityUser) {
            return new JsonResponse(['detail' => 'Utilisateur non authentifié.'], Response::HTTP_UNAUTHORIZED);
        }

        if (!$this->tenantContext->hasTenant()) {
            return new JsonResponse(['detail' => 'Tenant non défini.'], Response::HTTP_UNAUTHORIZED);
        }

        $body = json_decode((string) $request->getContent(), true);

        if (!is_array($body) || !array_key_exists('invitations', $body)) {
            return new JsonResponse(['detail' => 'Le champ "invitations" est requis.'], Response::HTTP_BAD_REQUEST);
        }

        $items = $body['invitations'];

        if (!is_array($items) || count($items) === 0) {
            return new JsonResponse(['detail' => 'La liste d\'invitations est vide.'], Response::HTTP_BAD_REQUEST);
        }

        if (count($items) > self::MAX_BULK_SIZE) {
            return new JsonResponse(
                ['detail' => sprintf('Maximum %d invitations par requête.', self::MAX_BULK_SIZE)],
                Response::HTTP_BAD_REQUEST,
            );
        }

        $tenantId = (string) $this->tenantContext->getCurrentTenantId();
        $createdBy = $currentUser->userId();
        $results = [];
        $errors = [];

        /** @var mixed $item */
        foreach ($items as $index => $item) {
            if (!is_array($item)) {
                $errors[] = ['line' => $index + 1, 'error' => 'Format invalide.'];

                continue;
            }

            $studentId = $item['studentId'] ?? null;
            $parentEmail = $item['parentEmail'] ?? null;

            if (!is_string($studentId) || $studentId === '' || !is_string($parentEmail) || $parentEmail === '') {
                $errors[] = ['line' => $index + 1, 'error' => 'Les champs studentId et parentEmail sont requis.'];

                continue;
            }

            try {
                $command = new SendParentInvitationCommand(
                    tenantId: $tenantId,
                    studentId: $studentId,
                    parentEmail: $parentEmail,
                    createdBy: $createdBy,
                );

                $invitation = ($this->handler)($command);

                foreach ($invitation->pullDomainEvents() as $event) {
                    $this->eventBus->dispatch($event);
                }

                $results[] = ParentInvitationResource::fromDomain($invitation);
            } catch (DomainException $e) {
                $errors[] = ['line' => $index + 1, 'email' => $parentEmail, 'error' => $e->getMessage()];
            } catch (Throwable) {
                $errors[] = ['line' => $index + 1, 'email' => $parentEmail, 'error' => 'Erreur interne lors de la création de l\'invitation.'];
            }
        }

        return new JsonResponse([
            'created' => count($results),
            'errors' => $errors,
            'total' => count($items),
        ], count($errors) > 0 && count($results) === 0 ? Response::HTTP_BAD_REQUEST : Response::HTTP_OK);
    }
}
