<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\ReadModel;

use App\Administration\Application\Dto\PaginatedResult;
use App\Administration\Application\Port\PaginatedStudentImageRightsReader;
use App\Administration\Application\Query\GetStudentsImageRights\StudentImageRightsDto;
use App\Administration\Domain\Model\User\ImageRightsStatus;
use App\Administration\Domain\Model\User\Role;
use DateTimeImmutable;
use Doctrine\DBAL\Connection;

use function json_encode;

use const JSON_THROW_ON_ERROR;

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

    /**
     * @return PaginatedResult<StudentImageRightsDto>
     */
    public function findPaginated(
        string $tenantId,
        ?string $status,
        ?string $search,
        int $page,
        int $limit,
    ): PaginatedResult {
        $params = $this->buildBaseParams($tenantId);
        $whereClause = $this->buildBaseWhere();

        if ($status !== null && $status !== '') {
            $whereClause .= ' AND u.image_rights_status = :status';
            $params['status'] = $status;
        }

        if ($search !== null && $search !== '') {
            $whereClause .= ' AND (u.first_name ILIKE :search OR u.last_name ILIKE :search OR u.email ILIKE :search)';
            $params['search'] = '%' . $search . '%';
        }

        $countSql = "SELECT COUNT(*) FROM users u WHERE {$whereClause}";

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

        $offset = ($page - 1) * $limit;

        $selectSql = <<<SQL
            SELECT
                u.id, u.first_name, u.last_name, u.email,
                u.image_rights_status, u.image_rights_updated_at,
                (
                    SELECT sc.name
                    FROM class_assignments ca
                    JOIN school_classes sc ON sc.id = ca.school_class_id
                    WHERE ca.user_id = u.id AND ca.tenant_id = u.tenant_id
                    ORDER BY ca.assigned_at DESC
                    LIMIT 1
                ) AS class_name
            FROM users u
            WHERE {$whereClause}
            ORDER BY u.last_name ASC, u.first_name ASC
            LIMIT :limit OFFSET :offset
        SQL;

        $params['limit'] = $limit;
        $params['offset'] = $offset;

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

        $items = array_map(self::mapRowToDto(...), $rows);

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

    /**
     * @return StudentImageRightsDto[]
     */
    public function findAll(
        string $tenantId,
        ?string $status,
    ): array {
        $params = $this->buildBaseParams($tenantId);
        $whereClause = $this->buildBaseWhere();

        if ($status !== null && $status !== '') {
            $whereClause .= ' AND u.image_rights_status = :status';
            $params['status'] = $status;
        }

        $sql = <<<SQL
            SELECT
                u.id, u.first_name, u.last_name, u.email,
                u.image_rights_status, u.image_rights_updated_at,
                (
                    SELECT sc.name
                    FROM class_assignments ca
                    JOIN school_classes sc ON sc.id = ca.school_class_id
                    WHERE ca.user_id = u.id AND ca.tenant_id = u.tenant_id
                    ORDER BY ca.assigned_at DESC
                    LIMIT 1
                ) AS class_name
            FROM users u
            WHERE {$whereClause}
            ORDER BY u.last_name ASC, u.first_name ASC
        SQL;

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

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

    /**
     * @return array<string, string>
     */
    private function buildBaseParams(string $tenantId): array
    {
        return [
            'tenant_id' => $tenantId,
            'role' => json_encode([Role::ELEVE->value], JSON_THROW_ON_ERROR),
        ];
    }

    private function buildBaseWhere(): string
    {
        return 'u.tenant_id = :tenant_id AND u.roles::jsonb @> :role::jsonb';
    }

    /**
     * @param array<string, mixed> $row
     */
    private static function mapRowToDto(array $row): StudentImageRightsDto
    {
        /** @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 $imageRightsStatusValue */
        $imageRightsStatusValue = $row['image_rights_status'];
        /** @var string|null $imageRightsUpdatedAt */
        $imageRightsUpdatedAt = $row['image_rights_updated_at'];
        /** @var string|null $className */
        $className = $row['class_name'] ?? null;

        $statusEnum = ImageRightsStatus::from($imageRightsStatusValue);

        return new StudentImageRightsDto(
            id: $id,
            firstName: $firstName,
            lastName: $lastName,
            email: $email ?? '',
            imageRightsStatus: $statusEnum->value,
            imageRightsStatusLabel: $statusEnum->label(),
            imageRightsUpdatedAt: $imageRightsUpdatedAt !== null ? new DateTimeImmutable($imageRightsUpdatedAt) : null,
            className: $className,
        );
    }
}
