<?php

declare(strict_types=1);

namespace App\Scolarite\Infrastructure\Persistence\Doctrine;

use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Repository\StudentAverageRepository;
use App\Shared\Domain\Tenant\TenantId;
use Doctrine\DBAL\Connection;
use Override;

final readonly class DoctrineStudentAverageRepository implements StudentAverageRepository
{
    public function __construct(
        private Connection $connection,
    ) {
    }

    #[Override]
    public function saveSubjectAverage(
        TenantId $tenantId,
        UserId $studentId,
        SubjectId $subjectId,
        string $periodId,
        float $average,
        int $gradeCount,
    ): void {
        $this->connection->executeStatement(
            'INSERT INTO student_averages (id, tenant_id, student_id, subject_id, period_id, average, grade_count, updated_at)
             VALUES (gen_random_uuid(), :tenant_id, :student_id, :subject_id, :period_id, :average, :grade_count, NOW())
             ON CONFLICT (student_id, subject_id, period_id) DO UPDATE SET
                 average = EXCLUDED.average,
                 grade_count = EXCLUDED.grade_count,
                 updated_at = NOW()',
            [
                'tenant_id' => (string) $tenantId,
                'student_id' => (string) $studentId,
                'subject_id' => (string) $subjectId,
                'period_id' => $periodId,
                'average' => $average,
                'grade_count' => $gradeCount,
            ],
        );
    }

    #[Override]
    public function saveGeneralAverage(
        TenantId $tenantId,
        UserId $studentId,
        string $periodId,
        float $average,
    ): void {
        $this->connection->executeStatement(
            'INSERT INTO student_general_averages (id, tenant_id, student_id, period_id, average, updated_at)
             VALUES (gen_random_uuid(), :tenant_id, :student_id, :period_id, :average, NOW())
             ON CONFLICT (student_id, period_id) DO UPDATE SET
                 average = EXCLUDED.average,
                 updated_at = NOW()',
            [
                'tenant_id' => (string) $tenantId,
                'student_id' => (string) $studentId,
                'period_id' => $periodId,
                'average' => $average,
            ],
        );
    }

    #[Override]
    public function findSubjectAveragesForStudent(
        UserId $studentId,
        string $periodId,
        TenantId $tenantId,
    ): array {
        $rows = $this->connection->fetchAllAssociative(
            'SELECT average FROM student_averages
             WHERE student_id = :student_id
             AND period_id = :period_id
             AND tenant_id = :tenant_id',
            [
                'student_id' => (string) $studentId,
                'period_id' => $periodId,
                'tenant_id' => (string) $tenantId,
            ],
        );

        return array_map(
            /** @param array<string, mixed> $row */
            static function (array $row): float {
                /** @var string|float $avg */
                $avg = $row['average'];

                return (float) $avg;
            },
            $rows,
        );
    }

    #[Override]
    public function findDetailedSubjectAveragesForStudent(
        UserId $studentId,
        string $periodId,
        TenantId $tenantId,
    ): array {
        $rows = $this->connection->fetchAllAssociative(
            'SELECT sa.subject_id, sa.average, sa.grade_count, s.name as subject_name
             FROM student_averages sa
             LEFT JOIN subjects s ON s.id = sa.subject_id
             WHERE sa.student_id = :student_id
             AND sa.period_id = :period_id
             AND sa.tenant_id = :tenant_id
             ORDER BY s.name ASC',
            [
                'student_id' => (string) $studentId,
                'period_id' => $periodId,
                'tenant_id' => (string) $tenantId,
            ],
        );

        return array_map(
            /** @param array<string, mixed> $row */
            static function (array $row): array {
                /** @var string $subjectId */
                $subjectId = $row['subject_id'];
                /** @var string|null $subjectName */
                $subjectName = $row['subject_name'];
                /** @var string|float $average */
                $average = $row['average'];
                /** @var string|int $gradeCount */
                $gradeCount = $row['grade_count'];

                return [
                    'subjectId' => $subjectId,
                    'subjectName' => $subjectName,
                    'average' => (float) $average,
                    'gradeCount' => (int) $gradeCount,
                ];
            },
            $rows,
        );
    }

    #[Override]
    public function findGeneralAverageForStudent(
        UserId $studentId,
        string $periodId,
        TenantId $tenantId,
    ): ?float {
        $row = $this->connection->fetchAssociative(
            'SELECT average FROM student_general_averages
             WHERE student_id = :student_id
             AND period_id = :period_id
             AND tenant_id = :tenant_id',
            [
                'student_id' => (string) $studentId,
                'period_id' => $periodId,
                'tenant_id' => (string) $tenantId,
            ],
        );

        if ($row === false) {
            return null;
        }

        /** @var string|float $average */
        $average = $row['average'];

        return (float) $average;
    }

    #[Override]
    public function deleteSubjectAverage(
        UserId $studentId,
        SubjectId $subjectId,
        string $periodId,
        TenantId $tenantId,
    ): void {
        $this->connection->executeStatement(
            'DELETE FROM student_averages
             WHERE student_id = :student_id
             AND subject_id = :subject_id
             AND period_id = :period_id
             AND tenant_id = :tenant_id',
            [
                'student_id' => (string) $studentId,
                'subject_id' => (string) $subjectId,
                'period_id' => $periodId,
                'tenant_id' => (string) $tenantId,
            ],
        );
    }

    #[Override]
    public function deleteGeneralAverage(
        UserId $studentId,
        string $periodId,
        TenantId $tenantId,
    ): void {
        $this->connection->executeStatement(
            'DELETE FROM student_general_averages
             WHERE student_id = :student_id
             AND period_id = :period_id
             AND tenant_id = :tenant_id',
            [
                'student_id' => (string) $studentId,
                'period_id' => $periodId,
                'tenant_id' => (string) $tenantId,
            ],
        );
    }
}
