<?php

declare(strict_types=1);

namespace App\Scolarite\Infrastructure\Api\Provider;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Administration\Domain\Model\User\Role;
use App\Administration\Infrastructure\Security\SecurityUser;
use App\Scolarite\Infrastructure\Api\Resource\StudentGradeResource;
use App\Shared\Infrastructure\Tenant\TenantContext;

use function array_map;

use Doctrine\DBAL\Connection;

use function in_array;
use function is_string;

use Override;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

/**
 * @implements ProviderInterface<StudentGradeResource>
 */
final readonly class StudentGradeCollectionProvider implements ProviderInterface
{
    public function __construct(
        private Connection $connection,
        private TenantContext $tenantContext,
        private Security $security,
    ) {
    }

    /** @return list<StudentGradeResource> */
    #[Override]
    public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
    {
        if (!$this->tenantContext->hasTenant()) {
            throw new UnauthorizedHttpException('Bearer', 'Tenant non défini.');
        }

        $user = $this->security->getUser();

        if (!$user instanceof SecurityUser) {
            throw new UnauthorizedHttpException('Bearer', 'Authentification requise.');
        }

        if (!in_array(Role::ELEVE->value, $user->getRoles(), true)) {
            throw new AccessDeniedHttpException('Accès réservé aux élèves.');
        }

        $tenantId = (string) $this->tenantContext->getCurrentTenantId();
        $studentId = $user->userId();

        /** @var string|null $subjectId */
        $subjectId = $uriVariables['subjectId'] ?? null;

        if (is_string($subjectId) && $subjectId === '') {
            $subjectId = null;
        }

        $subjectFilter = $subjectId !== null ? 'AND s.id = :subject_id' : '';

        $rows = $this->connection->fetchAllAssociative(
            "SELECT g.id AS grade_id, g.value, g.status AS grade_status, g.appreciation,
                    e.id AS evaluation_id, e.title AS evaluation_title,
                    e.evaluation_date, e.grade_scale, e.coefficient,
                    e.grades_published_at,
                    s.id AS subject_id, s.name AS subject_name,
                    es.average AS class_average, es.min_grade AS class_min, es.max_grade AS class_max
             FROM grades g
             JOIN evaluations e ON g.evaluation_id = e.id
             JOIN subjects s ON e.subject_id = s.id
             LEFT JOIN evaluation_statistics es ON es.evaluation_id = e.id
             WHERE g.student_id = :student_id
             AND g.tenant_id = :tenant_id
             AND e.grades_published_at IS NOT NULL
             AND e.status != :deleted_status
             {$subjectFilter}
             ORDER BY e.evaluation_date DESC, e.created_at DESC",
            $subjectId !== null
                ? ['student_id' => $studentId, 'tenant_id' => $tenantId, 'deleted_status' => 'deleted', 'subject_id' => $subjectId]
                : ['student_id' => $studentId, 'tenant_id' => $tenantId, 'deleted_status' => 'deleted'],
        );

        return array_map(self::hydrateResource(...), $rows);
    }

    /** @param array<string, mixed> $row */
    private static function hydrateResource(array $row): StudentGradeResource
    {
        $resource = new StudentGradeResource();

        /** @var string $gradeId */
        $gradeId = $row['grade_id'];
        $resource->id = $gradeId;

        /** @var string $evaluationId */
        $evaluationId = $row['evaluation_id'];
        $resource->evaluationId = $evaluationId;

        /** @var string $evaluationTitle */
        $evaluationTitle = $row['evaluation_title'];
        $resource->evaluationTitle = $evaluationTitle;

        /** @var string $evaluationDate */
        $evaluationDate = $row['evaluation_date'];
        $resource->evaluationDate = $evaluationDate;

        /** @var string|int $gradeScale */
        $gradeScale = $row['grade_scale'];
        $resource->gradeScale = (int) $gradeScale;

        /** @var string|float $coefficient */
        $coefficient = $row['coefficient'];
        $resource->coefficient = (float) $coefficient;

        /** @var string $subjectIdVal */
        $subjectIdVal = $row['subject_id'];
        $resource->subjectId = $subjectIdVal;

        /** @var string|null $subjectName */
        $subjectName = $row['subject_name'];
        $resource->subjectName = $subjectName;

        /** @var string|float|null $value */
        $value = $row['value'];
        $resource->value = $value !== null ? (float) $value : null;

        /** @var string $gradeStatus */
        $gradeStatus = $row['grade_status'];
        $resource->status = $gradeStatus;

        /** @var string|null $appreciation */
        $appreciation = $row['appreciation'];
        $resource->appreciation = $appreciation;

        /** @var string|null $publishedAt */
        $publishedAt = $row['grades_published_at'];
        $resource->publishedAt = $publishedAt;

        /** @var string|float|null $classAverage */
        $classAverage = $row['class_average'];
        $resource->classAverage = $classAverage !== null ? (float) $classAverage : null;

        /** @var string|float|null $classMin */
        $classMin = $row['class_min'];
        $resource->classMin = $classMin !== null ? (float) $classMin : null;

        /** @var string|float|null $classMax */
        $classMax = $row['class_max'];
        $resource->classMax = $classMax !== null ? (float) $classMax : null;

        return $resource;
    }
}
