<?php

declare(strict_types=1);

namespace App\Administration\Application\Query\GetStudentsWithClass;

use App\Administration\Application\Dto\PaginatedResult;
use App\Administration\Domain\Model\User\Role;
use Doctrine\DBAL\Connection;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler(bus: 'query.bus')]
final readonly class GetStudentsWithClassHandler
{
    public function __construct(
        private Connection $connection,
    ) {
    }

    /**
     * @return PaginatedResult<StudentWithClassDto>
     */
    public function __invoke(GetStudentsWithClassQuery $query): PaginatedResult
    {
        $params = [
            'tenant_id' => $query->tenantId,
            'academic_year_id' => $query->academicYearId,
            'role' => json_encode([Role::ELEVE->value]),
        ];

        $whereClause = 'u.tenant_id = :tenant_id AND u.roles::jsonb @> :role::jsonb';

        if ($query->classId !== null) {
            $whereClause .= ' AND ca.school_class_id = :class_id';
            $params['class_id'] = $query->classId;
        }

        if ($query->search !== null && $query->search !== '') {
            $whereClause .= ' AND (LOWER(u.last_name) LIKE :search OR LOWER(u.first_name) LIKE :search)';
            $params['search'] = '%' . mb_strtolower($query->search) . '%';
        }

        // Count total
        $countSql = <<<SQL
            SELECT COUNT(*)
            FROM users u
            LEFT JOIN class_assignments ca ON ca.user_id = u.id AND ca.academic_year_id = :academic_year_id
            WHERE {$whereClause}
        SQL;

        /** @var int|string|false $totalRaw */
        $totalRaw = $this->connection->fetchOne($countSql, $params);
        $total = (int) $totalRaw;

        // Fetch paginated results
        $offset = ($query->page - 1) * $query->limit;

        $selectSql = <<<SQL
            SELECT
                u.id,
                u.first_name,
                u.last_name,
                u.email,
                u.statut,
                u.student_number,
                u.date_naissance,
                ca.school_class_id AS class_id,
                sc.name AS class_name,
                sc.level AS class_level
            FROM users u
            LEFT JOIN class_assignments ca ON ca.user_id = u.id AND ca.academic_year_id = :academic_year_id
            LEFT JOIN school_classes sc ON sc.id = ca.school_class_id
            WHERE {$whereClause}
            ORDER BY u.last_name ASC, u.first_name ASC
            LIMIT :limit OFFSET :offset
        SQL;

        $params['limit'] = $query->limit;
        $params['offset'] = $offset;

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

        $items = array_map(static function (array $row): StudentWithClassDto {
            /** @var string $id */
            $id = $row['id'];
            /** @var string $firstName */
            $firstName = $row['first_name'];
            /** @var string $lastName */
            $lastName = $row['last_name'];
            /** @var string|null $email */
            $email = $row['email'];
            /** @var string $statut */
            $statut = $row['statut'];
            /** @var string|null $studentNumber */
            $studentNumber = $row['student_number'];
            /** @var string|null $dateNaissance */
            $dateNaissance = $row['date_naissance'];
            /** @var string|null $classId */
            $classId = $row['class_id'];
            /** @var string|null $className */
            $className = $row['class_name'];
            /** @var string|null $classLevel */
            $classLevel = $row['class_level'];

            return new StudentWithClassDto(
                id: $id,
                firstName: $firstName,
                lastName: $lastName,
                email: $email,
                statut: $statut,
                studentNumber: $studentNumber,
                dateNaissance: $dateNaissance,
                classId: $classId,
                className: $className,
                classLevel: $classLevel,
            );
        }, $rows);

        return new PaginatedResult(
            items: $items,
            total: $total,
            page: $query->page,
            limit: $query->limit,
        );
    }
}
