<?php

declare(strict_types=1);

namespace App\Tests\Functional\Scolarite\Api;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Infrastructure\Security\SecurityUser;
use App\Scolarite\Domain\Model\Evaluation\Coefficient;
use App\Scolarite\Domain\Model\Evaluation\Evaluation;
use App\Scolarite\Domain\Model\Evaluation\EvaluationId;
use App\Scolarite\Domain\Model\Evaluation\GradeScale;
use App\Scolarite\Domain\Model\Grade\Grade;
use App\Scolarite\Domain\Model\Grade\GradeStatus;
use App\Scolarite\Domain\Model\Grade\GradeValue;
use App\Scolarite\Domain\Repository\EvaluationRepository;
use App\Scolarite\Domain\Repository\EvaluationStatisticsRepository;
use App\Scolarite\Domain\Repository\GradeRepository;
use App\Scolarite\Domain\Repository\StudentAverageRepository;
use App\Scolarite\Domain\Service\AverageCalculator;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use Doctrine\DBAL\Connection;

use const JSON_THROW_ON_ERROR;

use PHPUnit\Framework\Attributes\Test;

final class StudentGradeEndpointsTest extends ApiTestCase
{
    protected static ?bool $alwaysBootKernel = true;

    private const string TENANT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
    private const string TEACHER_ID = '44444444-4444-4444-4444-444444444444';
    private const string STUDENT_ID = '22222222-2222-2222-2222-222222222222';
    private const string STUDENT2_ID = '33333333-3333-3333-3333-333333333333';
    private const string CLASS_ID = '55555555-5555-5555-5555-555555555555';
    private const string SUBJECT_ID = '66666666-6666-6666-6666-666666666666';
    private const string SUBJECT2_ID = '66666666-6666-6666-6666-666666666667';
    private const string PERIOD_ID = '11111111-1111-1111-1111-111111111111';
    private const string BASE_URL = 'http://ecole-alpha.classeo.local/api';

    private ?EvaluationId $unpublishedEvalId = null;

    protected function setUp(): void
    {
        parent::setUp();
        $this->seedFixtures();
    }

    protected function tearDown(): void
    {
        /** @var Connection $connection */
        $connection = static::getContainer()->get(Connection::class);
        $connection->executeStatement('DELETE FROM evaluation_statistics WHERE evaluation_id IN (SELECT id FROM evaluations WHERE tenant_id = :tid)', ['tid' => self::TENANT_ID]);
        $connection->executeStatement('DELETE FROM student_general_averages WHERE tenant_id = :tid', ['tid' => self::TENANT_ID]);
        $connection->executeStatement('DELETE FROM student_averages WHERE tenant_id = :tid', ['tid' => self::TENANT_ID]);
        $connection->executeStatement('DELETE FROM grade_events WHERE grade_id IN (SELECT id FROM grades WHERE tenant_id = :tid)', ['tid' => self::TENANT_ID]);
        $connection->executeStatement('DELETE FROM grades WHERE tenant_id = :tid', ['tid' => self::TENANT_ID]);
        $connection->executeStatement('DELETE FROM evaluations WHERE tenant_id = :tid', ['tid' => self::TENANT_ID]);

        parent::tearDown();
    }

    // =========================================================================
    // GET /me/grades — Auth & Access
    // =========================================================================

    #[Test]
    public function getMyGradesReturns401WithoutAuthentication(): void
    {
        $client = static::createClient();
        $client->request('GET', self::BASE_URL . '/me/grades', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseStatusCodeSame(401);
    }

    #[Test]
    public function getMyGradesReturns403ForTeacher(): void
    {
        $client = $this->createAuthenticatedClient(self::TEACHER_ID, ['ROLE_PROF']);
        $client->request('GET', self::BASE_URL . '/me/grades', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseStatusCodeSame(403);
    }

    #[Test]
    public function getMyGradesReturns403ForParent(): void
    {
        $parentId = '88888888-8888-8888-8888-888888888888';
        $client = $this->createAuthenticatedClient($parentId, ['ROLE_PARENT']);
        $client->request('GET', self::BASE_URL . '/me/grades', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseStatusCodeSame(403);
    }

    // =========================================================================
    // GET /me/grades — Happy path
    // =========================================================================

    #[Test]
    public function getMyGradesReturnsPublishedGradesForStudent(): void
    {
        $client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
        $client->request('GET', self::BASE_URL . '/me/grades', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();

        /** @var string $content */
        $content = $client->getResponse()->getContent();
        /** @var list<array<string, mixed>> $data */
        $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);

        // Only published grades should be returned (not unpublished)
        self::assertCount(2, $data);

        // First grade (sorted by eval date DESC, subject2 is more recent)
        self::assertSame(self::SUBJECT2_ID, $data[0]['subjectId']);
        self::assertSame(14.0, $data[0]['value']);
        self::assertSame('graded', $data[0]['status']);
        self::assertNotNull($data[0]['publishedAt']);

        // Second grade
        self::assertSame(self::SUBJECT_ID, $data[1]['subjectId']);
        self::assertSame(16.0, $data[1]['value']);
    }

    #[Test]
    public function getMyGradesDoesNotReturnUnpublishedGrades(): void
    {
        $client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
        $client->request('GET', self::BASE_URL . '/me/grades', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();

        /** @var string $content */
        $content = $client->getResponse()->getContent();
        /** @var list<array<string, mixed>> $data */
        $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);

        // The unpublished evaluation grade should not appear
        foreach ($data as $grade) {
            self::assertNotSame((string) $this->unpublishedEvalId, $grade['evaluationId']);
        }
    }

    #[Test]
    public function getMyGradesIncludesClassStatistics(): void
    {
        $client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
        $client->request('GET', self::BASE_URL . '/me/grades', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();

        /** @var string $content */
        $content = $client->getResponse()->getContent();
        /** @var list<array<string, mixed>> $data */
        $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);

        // First grade should have class statistics
        self::assertArrayHasKey('classAverage', $data[0]);
        self::assertArrayHasKey('classMin', $data[0]);
        self::assertArrayHasKey('classMax', $data[0]);
    }

    #[Test]
    public function getMyGradesReturnsEmptyForStudentWithNoGrades(): void
    {
        $noGradeStudentId = '77777777-7777-7777-7777-777777777777';
        /** @var Connection $connection */
        $connection = static::getContainer()->get(Connection::class);
        $connection->executeStatement(
            "INSERT INTO users (id, tenant_id, email, hashed_password, first_name, last_name, roles, statut, created_at, updated_at)
             VALUES (:id, :tid, 'no-grade@test.local', '', 'No', 'Grades', '[\"ROLE_ELEVE\"]', 'active', NOW(), NOW())
             ON CONFLICT (id) DO NOTHING",
            ['id' => $noGradeStudentId, 'tid' => self::TENANT_ID],
        );

        $client = $this->createAuthenticatedClient($noGradeStudentId, ['ROLE_ELEVE']);
        $client->request('GET', self::BASE_URL . '/me/grades', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();

        /** @var string $content */
        $content = $client->getResponse()->getContent();
        /** @var list<mixed> $data */
        $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
        self::assertCount(0, $data);
    }

    // =========================================================================
    // GET /me/grades/subject/{subjectId} — Happy path
    // =========================================================================

    #[Test]
    public function getMyGradesBySubjectFiltersCorrectly(): void
    {
        $client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
        $client->request('GET', self::BASE_URL . '/me/grades/subject/' . self::SUBJECT_ID, [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();

        /** @var string $content */
        $content = $client->getResponse()->getContent();
        /** @var list<array<string, mixed>> $data */
        $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);

        self::assertCount(1, $data);
        self::assertSame(self::SUBJECT_ID, $data[0]['subjectId']);
        self::assertSame(16.0, $data[0]['value']);
    }

    #[Test]
    public function getMyGradesBySubjectReturnsEmptyForUnknownSubject(): void
    {
        $unknownSubjectId = '99999999-9999-9999-9999-999999999999';
        $client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
        $client->request('GET', self::BASE_URL . '/me/grades/subject/' . $unknownSubjectId, [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();

        /** @var string $content */
        $content = $client->getResponse()->getContent();
        /** @var list<mixed> $data */
        $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
        self::assertCount(0, $data);
    }

    // =========================================================================
    // GET /me/averages — Auth & Access
    // =========================================================================

    #[Test]
    public function getMyAveragesReturns401WithoutAuthentication(): void
    {
        $client = static::createClient();
        $client->request('GET', self::BASE_URL . '/me/averages', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseStatusCodeSame(401);
    }

    #[Test]
    public function getMyAveragesReturns403ForTeacher(): void
    {
        $client = $this->createAuthenticatedClient(self::TEACHER_ID, ['ROLE_PROF']);
        $client->request('GET', self::BASE_URL . '/me/averages', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseStatusCodeSame(403);
    }

    // =========================================================================
    // GET /me/averages — Happy path
    // =========================================================================

    #[Test]
    public function getMyAveragesReturnsAveragesForStudent(): void
    {
        $client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
        $client->request('GET', self::BASE_URL . '/me/averages?periodId=' . self::PERIOD_ID, [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();
        self::assertJsonContains([
            'studentId' => self::STUDENT_ID,
            'periodId' => self::PERIOD_ID,
            'generalAverage' => 16.0,
        ]);
    }

    #[Test]
    public function getMyAveragesReturnsSubjectAverages(): void
    {
        $client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
        $client->request('GET', self::BASE_URL . '/me/averages?periodId=' . self::PERIOD_ID, [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();

        /** @var string $content */
        $content = $client->getResponse()->getContent();
        /** @var array{subjectAverages: list<array<string, mixed>>, generalAverage: float|null} $data */
        $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);

        self::assertNotEmpty($data['subjectAverages']);
        self::assertSame(self::SUBJECT_ID, $data['subjectAverages'][0]['subjectId']);
        self::assertSame(16.0, $data['subjectAverages'][0]['average']);
    }

    // =========================================================================
    // GET /me/grades — Student isolation
    // =========================================================================

    #[Test]
    public function getMyGradesReturnsOnlyCurrentStudentGrades(): void
    {
        $client = $this->createAuthenticatedClient(self::STUDENT2_ID, ['ROLE_ELEVE']);
        $client->request('GET', self::BASE_URL . '/me/grades', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();

        /** @var string $content */
        $content = $client->getResponse()->getContent();
        /** @var list<array<string, mixed>> $data */
        $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);

        // Student2 only has 1 grade (eval1, Maths), not the eval2/eval3 grades
        self::assertCount(1, $data);
        self::assertSame(12.0, $data[0]['value']);
    }

    // =========================================================================
    // GET /me/grades — Response completeness
    // =========================================================================

    #[Test]
    public function getMyGradesReturnsAllExpectedFields(): void
    {
        $client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
        $client->request('GET', self::BASE_URL . '/me/grades', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();

        /** @var string $content */
        $content = $client->getResponse()->getContent();
        /** @var list<array<string, mixed>> $data */
        $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);

        // First grade (eval2 — Français, more recent)
        $grade = $data[0];
        self::assertArrayHasKey('id', $grade);
        self::assertArrayHasKey('evaluationId', $grade);
        self::assertArrayHasKey('evaluationTitle', $grade);
        self::assertArrayHasKey('evaluationDate', $grade);
        self::assertArrayHasKey('gradeScale', $grade);
        self::assertArrayHasKey('coefficient', $grade);
        self::assertArrayHasKey('subjectId', $grade);
        self::assertArrayHasKey('value', $grade);
        self::assertArrayHasKey('status', $grade);
        self::assertArrayHasKey('publishedAt', $grade);

        self::assertSame('Dictée', $grade['evaluationTitle']);
        self::assertSame(20, $grade['gradeScale']);
        self::assertSame(2.0, $grade['coefficient']);
        self::assertSame('Français', $grade['subjectName'] ?? null);
    }

    #[Test]
    public function getMyGradesIncludesAppreciationWhenSet(): void
    {
        // Add appreciation to eval1 grade
        /** @var Connection $connection */
        $connection = static::getContainer()->get(Connection::class);
        $connection->executeStatement(
            "UPDATE grades SET appreciation = 'Excellent travail' WHERE student_id = :sid AND evaluation_id IN (SELECT id FROM evaluations WHERE title = 'DS Mathématiques' AND tenant_id = :tid)",
            ['sid' => self::STUDENT_ID, 'tid' => self::TENANT_ID],
        );

        $client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
        $client->request('GET', self::BASE_URL . '/me/grades', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();

        /** @var string $content */
        $content = $client->getResponse()->getContent();
        /** @var list<array<string, mixed>> $data */
        $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);

        // Find the Maths grade (eval1)
        $mathsGrade = null;
        foreach ($data as $grade) {
            if (($grade['evaluationTitle'] ?? null) === 'DS Mathématiques') {
                $mathsGrade = $grade;
                break;
            }
        }

        self::assertNotNull($mathsGrade, 'DS Mathématiques grade not found');
        self::assertSame('Excellent travail', $mathsGrade['appreciation']);
    }

    // =========================================================================
    // GET /me/averages — Auto-detect period
    // =========================================================================

    #[Test]
    public function getMyAveragesReturnsEmptyWhenNoPeriodCoversCurrentDate(): void
    {
        // The seeded period (2026-01-01 to 2026-03-31) does not cover today (2026-04-04)
        // So auto-detect returns no period → empty averages
        $client = $this->createAuthenticatedClient(self::STUDENT_ID, ['ROLE_ELEVE']);
        $client->request('GET', self::BASE_URL . '/me/averages', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();

        /** @var string $content */
        $content = $client->getResponse()->getContent();
        /** @var array<string, mixed> $data */
        $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR);

        self::assertArrayHasKey('studentId', $data);
        self::assertEmpty($data['subjectAverages'] ?? []);
    }

    // =========================================================================
    // Helpers
    // =========================================================================

    /**
     * @param list<string> $roles
     */
    private function createAuthenticatedClient(string $userId, array $roles): \ApiPlatform\Symfony\Bundle\Test\Client
    {
        $client = static::createClient();

        $user = new SecurityUser(
            userId: UserId::fromString($userId),
            email: 'test@classeo.local',
            hashedPassword: '',
            tenantId: TenantId::fromString(self::TENANT_ID),
            roles: $roles,
        );

        $client->loginUser($user, 'api');

        return $client;
    }

    private function seedFixtures(): void
    {
        $container = static::getContainer();
        /** @var Connection $connection */
        $connection = $container->get(Connection::class);
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $now = new DateTimeImmutable();

        $schoolId = '550e8400-e29b-41d4-a716-ff6655440001';
        $academicYearId = '550e8400-e29b-41d4-a716-ff6655440002';

        // Seed users
        $connection->executeStatement(
            "INSERT INTO users (id, tenant_id, email, hashed_password, first_name, last_name, roles, statut, created_at, updated_at)
             VALUES (:id, :tid, 'teacher-sg@test.local', '', 'Test', 'Teacher', '[\"ROLE_PROF\"]', 'active', NOW(), NOW())
             ON CONFLICT (id) DO NOTHING",
            ['id' => self::TEACHER_ID, 'tid' => self::TENANT_ID],
        );
        $connection->executeStatement(
            "INSERT INTO users (id, tenant_id, email, hashed_password, first_name, last_name, roles, statut, created_at, updated_at)
             VALUES (:id, :tid, 'student-sg@test.local', '', 'Alice', 'Durand', '[\"ROLE_ELEVE\"]', 'active', NOW(), NOW())
             ON CONFLICT (id) DO NOTHING",
            ['id' => self::STUDENT_ID, 'tid' => self::TENANT_ID],
        );
        $connection->executeStatement(
            "INSERT INTO users (id, tenant_id, email, hashed_password, first_name, last_name, roles, statut, created_at, updated_at)
             VALUES (:id, :tid, 'student2-sg@test.local', '', 'Bob', 'Martin', '[\"ROLE_ELEVE\"]', 'active', NOW(), NOW())
             ON CONFLICT (id) DO NOTHING",
            ['id' => self::STUDENT2_ID, 'tid' => self::TENANT_ID],
        );

        // Seed class and subjects
        $connection->executeStatement(
            "INSERT INTO school_classes (id, tenant_id, school_id, academic_year_id, name, status, created_at, updated_at)
             VALUES (:id, :tid, :sid, :ayid, 'Test-SG-Class', 'active', NOW(), NOW())
             ON CONFLICT (id) DO NOTHING",
            ['id' => self::CLASS_ID, 'tid' => self::TENANT_ID, 'sid' => $schoolId, 'ayid' => $academicYearId],
        );
        $connection->executeStatement(
            "INSERT INTO subjects (id, tenant_id, school_id, name, code, status, created_at, updated_at)
             VALUES (:id, :tid, :sid, 'Mathématiques', 'MATH', 'active', NOW(), NOW())
             ON CONFLICT (id) DO NOTHING",
            ['id' => self::SUBJECT_ID, 'tid' => self::TENANT_ID, 'sid' => $schoolId],
        );
        $connection->executeStatement(
            "INSERT INTO subjects (id, tenant_id, school_id, name, code, status, created_at, updated_at)
             VALUES (:id, :tid, :sid, 'Français', 'FRA', 'active', NOW(), NOW())
             ON CONFLICT (id) DO NOTHING",
            ['id' => self::SUBJECT2_ID, 'tid' => self::TENANT_ID, 'sid' => $schoolId],
        );
        $connection->executeStatement(
            "INSERT INTO academic_periods (id, tenant_id, academic_year_id, period_type, sequence, label, start_date, end_date)
             VALUES (:id, :tid, :ayid, 'trimester', 2, 'Trimestre 2', '2026-01-01', '2026-03-31')
             ON CONFLICT (id) DO NOTHING",
            ['id' => self::PERIOD_ID, 'tid' => self::TENANT_ID, 'ayid' => $academicYearId],
        );

        /** @var EvaluationRepository $evalRepo */
        $evalRepo = $container->get(EvaluationRepository::class);
        /** @var GradeRepository $gradeRepo */
        $gradeRepo = $container->get(GradeRepository::class);
        /** @var AverageCalculator $calculator */
        $calculator = $container->get(AverageCalculator::class);
        /** @var EvaluationStatisticsRepository $statsRepo */
        $statsRepo = $container->get(EvaluationStatisticsRepository::class);

        // Evaluation 1: Published, Subject 1 (Maths), older date
        $eval1 = Evaluation::creer(
            tenantId: $tenantId,
            classId: ClassId::fromString(self::CLASS_ID),
            subjectId: SubjectId::fromString(self::SUBJECT_ID),
            teacherId: UserId::fromString(self::TEACHER_ID),
            title: 'DS Mathématiques',
            description: null,
            evaluationDate: new DateTimeImmutable('2026-02-15'),
            gradeScale: new GradeScale(20),
            coefficient: new Coefficient(1.0),
            now: $now,
        );
        $eval1->publierNotes($now);
        $eval1->pullDomainEvents();
        $evalRepo->save($eval1);

        foreach ([
            [self::STUDENT_ID, 16.0],
            [self::STUDENT2_ID, 12.0],
        ] as [$studentId, $value]) {
            $grade = Grade::saisir(
                tenantId: $tenantId,
                evaluationId: $eval1->id,
                studentId: UserId::fromString($studentId),
                value: new GradeValue($value),
                status: GradeStatus::GRADED,
                gradeScale: new GradeScale(20),
                createdBy: UserId::fromString(self::TEACHER_ID),
                now: $now,
            );
            $grade->pullDomainEvents();
            $gradeRepo->save($grade);
        }

        $stats1 = $calculator->calculateClassStatistics([16.0, 12.0]);
        $statsRepo->save($eval1->id, $stats1);

        // Evaluation 2: Published, Subject 2 (Français), more recent date
        $eval2 = Evaluation::creer(
            tenantId: $tenantId,
            classId: ClassId::fromString(self::CLASS_ID),
            subjectId: SubjectId::fromString(self::SUBJECT2_ID),
            teacherId: UserId::fromString(self::TEACHER_ID),
            title: 'Dictée',
            description: null,
            evaluationDate: new DateTimeImmutable('2026-03-01'),
            gradeScale: new GradeScale(20),
            coefficient: new Coefficient(2.0),
            now: $now,
        );
        $eval2->publierNotes($now);
        $eval2->pullDomainEvents();
        $evalRepo->save($eval2);

        $grade2 = Grade::saisir(
            tenantId: $tenantId,
            evaluationId: $eval2->id,
            studentId: UserId::fromString(self::STUDENT_ID),
            value: new GradeValue(14.0),
            status: GradeStatus::GRADED,
            gradeScale: new GradeScale(20),
            createdBy: UserId::fromString(self::TEACHER_ID),
            now: $now,
        );
        $grade2->pullDomainEvents();
        $gradeRepo->save($grade2);

        $stats2 = $calculator->calculateClassStatistics([14.0]);
        $statsRepo->save($eval2->id, $stats2);

        // Evaluation 3: NOT published (grades should NOT appear for student)
        $eval3 = Evaluation::creer(
            tenantId: $tenantId,
            classId: ClassId::fromString(self::CLASS_ID),
            subjectId: SubjectId::fromString(self::SUBJECT_ID),
            teacherId: UserId::fromString(self::TEACHER_ID),
            title: 'Contrôle surprise',
            description: null,
            evaluationDate: new DateTimeImmutable('2026-03-10'),
            gradeScale: new GradeScale(20),
            coefficient: new Coefficient(0.5),
            now: $now,
        );
        // NOT published - don't call publierNotes()
        $eval3->pullDomainEvents();
        $evalRepo->save($eval3);
        $this->unpublishedEvalId = $eval3->id;

        $grade3 = Grade::saisir(
            tenantId: $tenantId,
            evaluationId: $eval3->id,
            studentId: UserId::fromString(self::STUDENT_ID),
            value: new GradeValue(8.0),
            status: GradeStatus::GRADED,
            gradeScale: new GradeScale(20),
            createdBy: UserId::fromString(self::TEACHER_ID),
            now: $now,
        );
        $grade3->pullDomainEvents();
        $gradeRepo->save($grade3);

        // Save student averages for /me/averages endpoint
        /** @var StudentAverageRepository $avgRepo */
        $avgRepo = $container->get(StudentAverageRepository::class);
        $avgRepo->saveSubjectAverage(
            $tenantId,
            UserId::fromString(self::STUDENT_ID),
            SubjectId::fromString(self::SUBJECT_ID),
            self::PERIOD_ID,
            16.0,
            1,
        );
        $avgRepo->saveGeneralAverage(
            $tenantId,
            UserId::fromString(self::STUDENT_ID),
            self::PERIOD_ID,
            16.0,
        );
    }
}
