<?php

declare(strict_types=1);

namespace App\Scolarite\Application\Query\GetHomeworkExceptionsReport;

use App\Administration\Domain\Model\User\User;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Domain\Repository\UserRepository;
use App\Scolarite\Domain\Model\Homework\Homework;
use App\Scolarite\Domain\Model\Homework\HomeworkRuleException;
use App\Scolarite\Domain\Repository\HomeworkRepository;
use App\Scolarite\Domain\Repository\HomeworkRuleExceptionRepository;
use App\Shared\Domain\Tenant\TenantId;

use function array_filter;

use DateTimeImmutable;

use function str_contains;

use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler(bus: 'query.bus')]
final readonly class GetHomeworkExceptionsReportHandler
{
    public function __construct(
        private HomeworkRuleExceptionRepository $exceptionRepository,
        private HomeworkRepository $homeworkRepository,
        private UserRepository $userRepository,
    ) {
    }

    /** @return array<HomeworkExceptionDto> */
    public function __invoke(GetHomeworkExceptionsReportQuery $query): array
    {
        $tenantId = TenantId::fromString($query->tenantId);
        $exceptions = $this->exceptionRepository->findByTenant($tenantId);

        $startDate = $query->startDate !== null ? new DateTimeImmutable($query->startDate) : null;
        $endDate = $query->endDate !== null ? new DateTimeImmutable($query->endDate . ' 23:59:59') : null;

        $filtered = array_filter(
            $exceptions,
            fn (HomeworkRuleException $e): bool => $this->matchesFilters(
                $e,
                $startDate,
                $endDate,
                $query->teacherId,
                $query->ruleType,
            ),
        );

        if ($filtered === []) {
            return [];
        }

        $homeworkCache = $this->preloadHomeworks($filtered, $tenantId);
        $teacherCache = $this->preloadTeachers($filtered);

        $results = [];

        foreach ($filtered as $exception) {
            $hwId = (string) $exception->homeworkId;
            $teacherId = (string) $exception->createdBy;

            $homework = $homeworkCache[$hwId] ?? null;
            $teacher = $teacherCache[$teacherId] ?? null;

            $results[] = new HomeworkExceptionDto(
                id: (string) $exception->id,
                homeworkId: $hwId,
                homeworkTitle: $homework !== null ? $homework->title : '(supprimé)',
                ruleType: $exception->ruleType,
                justification: $exception->justification,
                teacherId: $teacherId,
                teacherName: $teacher !== null ? $teacher->firstName . ' ' . $teacher->lastName : 'Inconnu',
                createdAt: $exception->createdAt->format(DateTimeImmutable::ATOM),
            );
        }

        return $results;
    }

    /**
     * @param array<HomeworkRuleException> $exceptions
     *
     * @return array<string, Homework|null>
     */
    private function preloadHomeworks(array $exceptions, TenantId $tenantId): array
    {
        $cache = [];

        foreach ($exceptions as $exception) {
            $hwId = (string) $exception->homeworkId;

            if (!isset($cache[$hwId])) {
                $cache[$hwId] = $this->homeworkRepository->findById($exception->homeworkId, $tenantId);
            }
        }

        return $cache;
    }

    /**
     * @param array<HomeworkRuleException> $exceptions
     *
     * @return array<string, User|null>
     */
    private function preloadTeachers(array $exceptions): array
    {
        $cache = [];

        foreach ($exceptions as $exception) {
            $teacherId = (string) $exception->createdBy;

            if (!isset($cache[$teacherId])) {
                $cache[$teacherId] = $this->userRepository->findById(UserId::fromString($teacherId));
            }
        }

        return $cache;
    }

    private function matchesFilters(
        HomeworkRuleException $exception,
        ?DateTimeImmutable $startDate,
        ?DateTimeImmutable $endDate,
        ?string $teacherId,
        ?string $ruleType,
    ): bool {
        if ($startDate !== null && $exception->createdAt < $startDate) {
            return false;
        }

        if ($endDate !== null && $exception->createdAt > $endDate) {
            return false;
        }

        if ($teacherId !== null && (string) $exception->createdBy !== $teacherId) {
            return false;
        }

        if ($ruleType !== null && !str_contains($exception->ruleType, $ruleType)) {
            return false;
        }

        return true;
    }
}
