<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\ReadModel;

use App\Administration\Application\Dto\PaginatedResult;
use App\Administration\Application\Port\PaginatedSubjectsReader;
use App\Administration\Application\Query\GetSubjects\SubjectDto;
use DateTimeImmutable;
use Doctrine\DBAL\Connection;

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

    /**
     * @return PaginatedResult<SubjectDto>
     */
    public function findPaginated(
        string $tenantId,
        string $schoolId,
        ?string $search,
        int $page,
        int $limit,
    ): PaginatedResult {
        $params = [
            'tenant_id' => $tenantId,
            'school_id' => $schoolId,
            'status' => 'active',
        ];
        $whereClause = 's.tenant_id = :tenant_id AND s.school_id = :school_id AND s.status = :status AND s.deleted_at IS NULL';

        if ($search !== null && $search !== '') {
            $whereClause .= ' AND (s.name ILIKE :search OR s.code ILIKE :search)';
            $params['search'] = '%' . $search . '%';
        }

        $countSql = "SELECT COUNT(*) FROM subjects s WHERE {$whereClause}";

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

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

        $selectSql = <<<SQL
            SELECT
                s.id, s.name, s.code, s.color, s.description, s.status,
                s.created_at, s.updated_at,
                (SELECT COUNT(*) FROM teacher_assignments ta WHERE ta.subject_id = s.id AND ta.status = 'active') AS teacher_count,
                (SELECT COUNT(DISTINCT ta.school_class_id) FROM teacher_assignments ta WHERE ta.subject_id = s.id AND ta.status = 'active') AS class_count
            FROM subjects s
            WHERE {$whereClause}
            ORDER BY s.name ASC
            LIMIT :limit OFFSET :offset
        SQL;

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

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

        $items = array_map(static function (array $row): SubjectDto {
            /** @var string $id */
            $id = $row['id'];
            /** @var string $name */
            $name = $row['name'];
            /** @var string $code */
            $code = $row['code'];
            /** @var string|null $color */
            $color = $row['color'];
            /** @var string|null $description */
            $description = $row['description'];
            /** @var string $status */
            $status = $row['status'];
            /** @var string $createdAt */
            $createdAt = $row['created_at'];
            /** @var string $updatedAt */
            $updatedAt = $row['updated_at'];
            /** @var int|string $teacherCountRaw */
            $teacherCountRaw = $row['teacher_count'] ?? 0;
            /** @var int|string $classCountRaw */
            $classCountRaw = $row['class_count'] ?? 0;

            return new SubjectDto(
                id: $id,
                name: $name,
                code: $code,
                color: $color,
                description: $description,
                status: $status,
                createdAt: new DateTimeImmutable($createdAt),
                updatedAt: new DateTimeImmutable($updatedAt),
                teacherCount: (int) $teacherCountRaw,
                classCount: (int) $classCountRaw,
            );
        }, $rows);

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