<?php

declare(strict_types=1);

namespace App\Scolarite\Infrastructure\Api\Provider;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
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\EvaluationStatus;
use App\Scolarite\Domain\Model\Evaluation\GradeScale;
use App\Scolarite\Infrastructure\Api\Resource\EvaluationResource;
use App\Shared\Domain\Tenant\TenantId;
use App\Shared\Infrastructure\Tenant\TenantContext;

use function array_map;

use DateTimeImmutable;
use Doctrine\DBAL\Connection;

use function is_string;

use Override;
use Ramsey\Uuid\Exception\InvalidUuidStringException;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

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

    /** @return array<EvaluationResource> */
    #[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.');
        }

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

        /** @var array<string, mixed> $filters */
        $filters = $context['filters'] ?? [];
        $classIdFilter = $filters['classId'] ?? null;
        $search = $filters['search'] ?? null;

        $sql = 'SELECT e.*, c.name AS class_name, s.name AS subject_name
                FROM evaluations e
                LEFT JOIN school_classes c ON c.id = e.class_id
                LEFT JOIN subjects s ON s.id = e.subject_id
                WHERE e.teacher_id = :teacher_id
                AND e.tenant_id = :tenant_id
                AND e.status != :deleted';

        $params = [
            'teacher_id' => $teacherId,
            'tenant_id' => $tenantId,
            'deleted' => EvaluationStatus::DELETED->value,
        ];

        if (is_string($classIdFilter) && $classIdFilter !== '') {
            try {
                ClassId::fromString($classIdFilter);
            } catch (InvalidUuidStringException $e) {
                throw new BadRequestHttpException('UUID de classe invalide : ' . $e->getMessage());
            }
            $sql .= ' AND e.class_id = :class_id';
            $params['class_id'] = $classIdFilter;
        }

        if (is_string($search) && $search !== '') {
            $sql .= ' AND e.title ILIKE :search';
            $params['search'] = '%' . $search . '%';
        }

        $sql .= ' ORDER BY e.evaluation_date DESC';

        $rows = $this->connection->fetchAllAssociative($sql, $params);

        return array_map(static function (array $row): EvaluationResource {
            /** @var string $className */
            $className = $row['class_name'] ?? null;
            /** @var string $subjectName */
            $subjectName = $row['subject_name'] ?? null;

            $evaluation = self::hydrateEvaluation($row);

            return EvaluationResource::fromDomain($evaluation, $className, $subjectName);
        }, $rows);
    }

    /** @param array<string, mixed> $row */
    private static function hydrateEvaluation(array $row): Evaluation
    {
        /** @var string $id */
        $id = $row['id'];
        /** @var string $tenantId */
        $tenantId = $row['tenant_id'];
        /** @var string $classId */
        $classId = $row['class_id'];
        /** @var string $subjectId */
        $subjectId = $row['subject_id'];
        /** @var string $teacherId */
        $teacherId = $row['teacher_id'];
        /** @var string $title */
        $title = $row['title'];
        /** @var string|null $description */
        $description = $row['description'];
        /** @var string $evaluationDate */
        $evaluationDate = $row['evaluation_date'];
        /** @var string|int $gradeScaleRaw */
        $gradeScaleRaw = $row['grade_scale'];
        /** @var string|float $coefficientRaw */
        $coefficientRaw = $row['coefficient'];
        /** @var string $status */
        $status = $row['status'];
        /** @var string $createdAt */
        $createdAt = $row['created_at'];
        /** @var string $updatedAt */
        $updatedAt = $row['updated_at'];

        return Evaluation::reconstitute(
            id: EvaluationId::fromString($id),
            tenantId: TenantId::fromString($tenantId),
            classId: ClassId::fromString($classId),
            subjectId: SubjectId::fromString($subjectId),
            teacherId: UserId::fromString($teacherId),
            title: $title,
            description: $description,
            evaluationDate: new DateTimeImmutable($evaluationDate),
            gradeScale: new GradeScale((int) $gradeScaleRaw),
            coefficient: new Coefficient((float) $coefficientRaw),
            status: EvaluationStatus::from($status),
            createdAt: new DateTimeImmutable($createdAt),
            updatedAt: new DateTimeImmutable($updatedAt),
        );
    }
}
