<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Domain\Model\GradingConfiguration;

use App\Administration\Domain\Exception\CannotChangeGradingModeWithExistingGradesException;
use App\Administration\Domain\Model\GradingConfiguration\GradingConfiguration;
use App\Administration\Domain\Model\GradingConfiguration\GradingMode;
use App\Administration\Domain\Model\GradingConfiguration\SchoolGradingConfiguration;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Model\SchoolClass\SchoolId;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class SchoolGradingConfigurationTest extends TestCase
{
    private TenantId $tenantId;
    private SchoolId $schoolId;
    private AcademicYearId $academicYearId;

    protected function setUp(): void
    {
        $this->tenantId = TenantId::generate();
        $this->schoolId = SchoolId::generate();
        $this->academicYearId = AcademicYearId::generate();
    }

    #[Test]
    public function itConfiguresGradingMode(): void
    {
        $config = SchoolGradingConfiguration::configurer(
            tenantId: $this->tenantId,
            schoolId: $this->schoolId,
            academicYearId: $this->academicYearId,
            mode: GradingMode::NUMERIC_20,
            hasExistingGrades: false,
            configuredAt: new DateTimeImmutable('2026-02-01'),
        );

        self::assertSame(GradingMode::NUMERIC_20, $config->gradingConfiguration->mode);
        self::assertSame($this->tenantId, $config->tenantId);
        self::assertSame($this->schoolId, $config->schoolId);
        self::assertSame($this->academicYearId, $config->academicYearId);
    }

    #[Test]
    public function itEmitsEventOnCreation(): void
    {
        $config = SchoolGradingConfiguration::configurer(
            tenantId: $this->tenantId,
            schoolId: $this->schoolId,
            academicYearId: $this->academicYearId,
            mode: GradingMode::COMPETENCIES,
            hasExistingGrades: false,
            configuredAt: new DateTimeImmutable('2026-02-01'),
        );

        $events = $config->pullDomainEvents();
        self::assertCount(1, $events);
    }

    #[Test]
    public function itChangesGradingModeWhenNoGradesExist(): void
    {
        $config = SchoolGradingConfiguration::configurer(
            tenantId: $this->tenantId,
            schoolId: $this->schoolId,
            academicYearId: $this->academicYearId,
            mode: GradingMode::NUMERIC_20,
            hasExistingGrades: false,
            configuredAt: new DateTimeImmutable('2026-02-01'),
        );
        $config->pullDomainEvents();

        $config->changerMode(
            nouveauMode: GradingMode::COMPETENCIES,
            hasExistingGrades: false,
            at: new DateTimeImmutable('2026-02-02'),
        );

        self::assertSame(GradingMode::COMPETENCIES, $config->gradingConfiguration->mode);
        $events = $config->pullDomainEvents();
        self::assertCount(1, $events);
    }

    #[Test]
    public function itBlocksChangeWhenGradesExist(): void
    {
        $config = SchoolGradingConfiguration::configurer(
            tenantId: $this->tenantId,
            schoolId: $this->schoolId,
            academicYearId: $this->academicYearId,
            mode: GradingMode::NUMERIC_20,
            hasExistingGrades: false,
            configuredAt: new DateTimeImmutable('2026-02-01'),
        );

        $this->expectException(CannotChangeGradingModeWithExistingGradesException::class);

        $config->changerMode(
            nouveauMode: GradingMode::COMPETENCIES,
            hasExistingGrades: true,
            at: new DateTimeImmutable('2026-02-02'),
        );
    }

    #[Test]
    public function itDoesNotEmitEventWhenModeIsUnchanged(): void
    {
        $config = SchoolGradingConfiguration::configurer(
            tenantId: $this->tenantId,
            schoolId: $this->schoolId,
            academicYearId: $this->academicYearId,
            mode: GradingMode::NUMERIC_20,
            hasExistingGrades: false,
            configuredAt: new DateTimeImmutable('2026-02-01'),
        );
        $config->pullDomainEvents();

        $config->changerMode(
            nouveauMode: GradingMode::NUMERIC_20,
            hasExistingGrades: false,
            at: new DateTimeImmutable('2026-02-02'),
        );

        self::assertEmpty($config->pullDomainEvents());
    }

    #[Test]
    public function itReconstitutesFromStorage(): void
    {
        $id = SchoolGradingConfiguration::generateId();

        $config = SchoolGradingConfiguration::reconstitute(
            id: $id,
            tenantId: $this->tenantId,
            schoolId: $this->schoolId,
            academicYearId: $this->academicYearId,
            gradingConfiguration: new GradingConfiguration(GradingMode::LETTERS),
            configuredAt: new DateTimeImmutable('2026-02-01'),
            updatedAt: new DateTimeImmutable('2026-02-02'),
        );

        self::assertSame(GradingMode::LETTERS, $config->gradingConfiguration->mode);
        self::assertEmpty($config->pullDomainEvents());
    }

    #[Test]
    public function itBlocksInitialNonDefaultModeWhenGradesExist(): void
    {
        $this->expectException(CannotChangeGradingModeWithExistingGradesException::class);

        SchoolGradingConfiguration::configurer(
            tenantId: $this->tenantId,
            schoolId: $this->schoolId,
            academicYearId: $this->academicYearId,
            mode: GradingMode::COMPETENCIES,
            hasExistingGrades: true,
            configuredAt: new DateTimeImmutable('2026-02-01'),
        );
    }

    #[Test]
    public function itAllowsInitialDefaultModeWhenGradesExist(): void
    {
        $config = SchoolGradingConfiguration::configurer(
            tenantId: $this->tenantId,
            schoolId: $this->schoolId,
            academicYearId: $this->academicYearId,
            mode: GradingMode::NUMERIC_20,
            hasExistingGrades: true,
            configuredAt: new DateTimeImmutable('2026-02-01'),
        );

        self::assertSame(GradingMode::NUMERIC_20, $config->gradingConfiguration->mode);
    }

    #[Test]
    public function defaultModeIsNumeric20(): void
    {
        self::assertSame(GradingMode::NUMERIC_20, SchoolGradingConfiguration::DEFAULT_MODE);
    }
}
