<?php

declare(strict_types=1);

namespace App\Scolarite\Infrastructure\Persistence\InMemory;

use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Exception\ScheduleSlotNotFoundException;
use App\Scolarite\Domain\Model\Schedule\DayOfWeek;
use App\Scolarite\Domain\Model\Schedule\ScheduleSlot;
use App\Scolarite\Domain\Model\Schedule\ScheduleSlotId;
use App\Scolarite\Domain\Model\Schedule\TimeSlot;
use App\Scolarite\Domain\Repository\ScheduleSlotRepository;
use App\Shared\Domain\Tenant\TenantId;

use function array_filter;
use function array_values;

use Override;

final class InMemoryScheduleSlotRepository implements ScheduleSlotRepository
{
    /** @var array<string, ScheduleSlot> */
    private array $byId = [];

    #[Override]
    public function save(ScheduleSlot $slot): void
    {
        $this->byId[(string) $slot->id] = $slot;
    }

    #[Override]
    public function get(ScheduleSlotId $id, TenantId $tenantId): ScheduleSlot
    {
        $slot = $this->findById($id, $tenantId);

        if ($slot === null) {
            throw ScheduleSlotNotFoundException::avecId($id);
        }

        return $slot;
    }

    #[Override]
    public function findById(ScheduleSlotId $id, TenantId $tenantId): ?ScheduleSlot
    {
        $slot = $this->byId[(string) $id] ?? null;

        if ($slot !== null && !$slot->tenantId->equals($tenantId)) {
            return null;
        }

        return $slot;
    }

    #[Override]
    public function delete(ScheduleSlotId $id, TenantId $tenantId): void
    {
        $slot = $this->findById($id, $tenantId);

        if ($slot !== null) {
            unset($this->byId[(string) $id]);
        }
    }

    #[Override]
    public function findByClass(ClassId $classId, TenantId $tenantId): array
    {
        return array_values(array_filter(
            $this->byId,
            static fn (ScheduleSlot $s) => $s->tenantId->equals($tenantId)
                && $s->classId->equals($classId),
        ));
    }

    #[Override]
    public function findRecurringByClass(ClassId $classId, TenantId $tenantId): array
    {
        return array_values(array_filter(
            $this->byId,
            static fn (ScheduleSlot $s) => $s->tenantId->equals($tenantId)
                && $s->classId->equals($classId)
                && $s->isRecurring,
        ));
    }

    #[Override]
    public function findByTeacher(UserId $teacherId, TenantId $tenantId): array
    {
        return array_values(array_filter(
            $this->byId,
            static fn (ScheduleSlot $s) => $s->tenantId->equals($tenantId)
                && $s->teacherId->equals($teacherId),
        ));
    }

    #[Override]
    public function findOverlappingForClass(
        ClassId $classId,
        DayOfWeek $dayOfWeek,
        string $startTime,
        string $endTime,
        TenantId $tenantId,
        ?ScheduleSlotId $excludeId = null,
    ): array {
        $timeSlot = new TimeSlot($startTime, $endTime);

        return array_values(array_filter(
            $this->byId,
            static fn (ScheduleSlot $s) => $s->tenantId->equals($tenantId)
                && $s->classId->equals($classId)
                && $s->dayOfWeek === $dayOfWeek
                && $s->timeSlot->overlaps($timeSlot)
                && ($excludeId === null || !$s->id->equals($excludeId)),
        ));
    }

    #[Override]
    public function findOverlappingForTeacher(
        UserId $teacherId,
        DayOfWeek $dayOfWeek,
        string $startTime,
        string $endTime,
        TenantId $tenantId,
        ?ScheduleSlotId $excludeId = null,
    ): array {
        $timeSlot = new TimeSlot($startTime, $endTime);

        return array_values(array_filter(
            $this->byId,
            static fn (ScheduleSlot $s) => $s->tenantId->equals($tenantId)
                && $s->teacherId->equals($teacherId)
                && $s->dayOfWeek === $dayOfWeek
                && $s->timeSlot->overlaps($timeSlot)
                && ($excludeId === null || !$s->id->equals($excludeId)),
        ));
    }

    #[Override]
    public function findOverlappingForRoom(
        string $room,
        DayOfWeek $dayOfWeek,
        string $startTime,
        string $endTime,
        TenantId $tenantId,
        ?ScheduleSlotId $excludeId = null,
    ): array {
        $timeSlot = new TimeSlot($startTime, $endTime);

        return array_values(array_filter(
            $this->byId,
            static fn (ScheduleSlot $s) => $s->tenantId->equals($tenantId)
                && $s->room === $room
                && $s->dayOfWeek === $dayOfWeek
                && $s->timeSlot->overlaps($timeSlot)
                && ($excludeId === null || !$s->id->equals($excludeId)),
        ));
    }
}
