<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Persistence\InMemory;

use App\Administration\Domain\Exception\ParentInvitationNotFoundException;
use App\Administration\Domain\Model\Invitation\InvitationCode;
use App\Administration\Domain\Model\Invitation\InvitationStatus;
use App\Administration\Domain\Model\Invitation\ParentInvitation;
use App\Administration\Domain\Model\Invitation\ParentInvitationId;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Domain\Repository\ParentInvitationRepository;
use App\Shared\Domain\Tenant\TenantId;

use function array_filter;
use function array_values;

use DateTimeImmutable;
use Override;

final class InMemoryParentInvitationRepository implements ParentInvitationRepository
{
    /** @var array<string, ParentInvitation> */
    private array $byId = [];

    /** @var array<string, ParentInvitation> */
    private array $byCode = [];

    /** @var array<string, string> Maps invitation ID to its last saved code */
    private array $codeIndex = [];

    #[Override]
    public function save(ParentInvitation $invitation): void
    {
        $id = (string) $invitation->id;
        $newCode = (string) $invitation->code;

        // Clean up old code index if code changed since last save
        if (isset($this->codeIndex[$id]) && $this->codeIndex[$id] !== $newCode) {
            unset($this->byCode[$this->codeIndex[$id]]);
        }

        $this->byId[$id] = $invitation;
        $this->byCode[$newCode] = $invitation;
        $this->codeIndex[$id] = $newCode;
    }

    #[Override]
    public function get(ParentInvitationId $id, TenantId $tenantId): ParentInvitation
    {
        $invitation = $this->findById($id, $tenantId);

        if ($invitation === null) {
            throw ParentInvitationNotFoundException::withId($id);
        }

        return $invitation;
    }

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

        if ($invitation === null || !$invitation->tenantId->equals($tenantId)) {
            return null;
        }

        return $invitation;
    }

    #[Override]
    public function findByCode(InvitationCode $code): ?ParentInvitation
    {
        return $this->byCode[(string) $code] ?? null;
    }

    #[Override]
    public function findAllByTenant(TenantId $tenantId): array
    {
        return array_values(array_filter(
            $this->byId,
            static fn (ParentInvitation $inv) => $inv->tenantId->equals($tenantId),
        ));
    }

    #[Override]
    public function findByStudent(UserId $studentId, TenantId $tenantId): array
    {
        return array_values(array_filter(
            $this->byId,
            static fn (ParentInvitation $inv) => $inv->studentId->equals($studentId)
                && $inv->tenantId->equals($tenantId),
        ));
    }

    #[Override]
    public function findByStatus(InvitationStatus $status, TenantId $tenantId): array
    {
        return array_values(array_filter(
            $this->byId,
            static fn (ParentInvitation $inv) => $inv->status === $status
                && $inv->tenantId->equals($tenantId),
        ));
    }

    #[Override]
    public function findExpiredSent(DateTimeImmutable $at): array
    {
        return array_values(array_filter(
            $this->byId,
            static fn (ParentInvitation $inv) => $inv->status === InvitationStatus::SENT
                && $inv->estExpiree($at),
        ));
    }

    #[Override]
    public function delete(ParentInvitationId $id, TenantId $tenantId): void
    {
        $invitation = $this->byId[(string) $id] ?? null;

        if ($invitation !== null && $invitation->tenantId->equals($tenantId)) {
            $idStr = (string) $id;
            unset($this->byCode[(string) $invitation->code]);
            unset($this->byId[$idStr]);
            unset($this->codeIndex[$idStr]);
        }
    }
}
