<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Persistence\InMemory;

use App\Administration\Domain\Exception\SubjectNotFoundException;
use App\Administration\Domain\Model\SchoolClass\SchoolId;
use App\Administration\Domain\Model\Subject\Subject;
use App\Administration\Domain\Model\Subject\SubjectCode;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\Subject\SubjectStatus;
use App\Administration\Domain\Repository\SubjectRepository;
use App\Shared\Domain\Tenant\TenantId;

use function mb_strtolower;

use Override;

final class InMemorySubjectRepository implements SubjectRepository
{
    /** @var array<string, Subject> Indexed by ID */
    private array $byId = [];

    /** @var array<string, Subject> Indexed by tenant:school:code */
    private array $byTenantSchoolCode = [];

    /** @var array<string, string> Maps subject ID to its current code key */
    private array $codeKeyById = [];

    #[Override]
    public function save(Subject $subject): void
    {
        $subjectIdStr = (string) $subject->id;

        // If subject already exists, remove the old code key (handles code changes)
        if (isset($this->codeKeyById[$subjectIdStr])) {
            $oldKey = $this->codeKeyById[$subjectIdStr];
            unset($this->byTenantSchoolCode[$oldKey]);
        }

        $newKey = $this->codeKey($subject->code, $subject->tenantId, $subject->schoolId);

        $this->byId[$subjectIdStr] = $subject;
        $this->byTenantSchoolCode[$newKey] = $subject;
        $this->codeKeyById[$subjectIdStr] = $newKey;
    }

    #[Override]
    public function get(SubjectId $id): Subject
    {
        $subject = $this->findById($id);

        if ($subject === null) {
            throw SubjectNotFoundException::withId($id);
        }

        return $subject;
    }

    #[Override]
    public function findById(SubjectId $id): ?Subject
    {
        return $this->byId[(string) $id] ?? null;
    }

    #[Override]
    public function findByCode(
        SubjectCode $code,
        TenantId $tenantId,
        SchoolId $schoolId,
    ): ?Subject {
        $subject = $this->byTenantSchoolCode[$this->codeKey($code, $tenantId, $schoolId)] ?? null;

        // Filtrer les matières archivées (cohérent avec findActiveByTenantAndSchool)
        if ($subject !== null && $subject->status !== SubjectStatus::ACTIVE) {
            return null;
        }

        return $subject;
    }

    #[Override]
    public function findActiveByTenantAndSchool(
        TenantId $tenantId,
        SchoolId $schoolId,
    ): array {
        $result = [];

        foreach ($this->byId as $subject) {
            if ($subject->tenantId->equals($tenantId)
                && $subject->schoolId->equals($schoolId)
                && $subject->status === SubjectStatus::ACTIVE
            ) {
                $result[] = $subject;
            }
        }

        return $result;
    }

    #[Override]
    public function delete(SubjectId $id): void
    {
        $subjectIdStr = (string) $id;

        if (isset($this->byId[$subjectIdStr])) {
            if (isset($this->codeKeyById[$subjectIdStr])) {
                unset($this->byTenantSchoolCode[$this->codeKeyById[$subjectIdStr]]);
                unset($this->codeKeyById[$subjectIdStr]);
            }
            unset($this->byId[$subjectIdStr]);
        }
    }

    private function codeKey(SubjectCode $code, TenantId $tenantId, SchoolId $schoolId): string
    {
        return $tenantId . ':' . $schoolId . ':' . mb_strtolower((string) $code, 'UTF-8');
    }
}
