<?php

declare(strict_types=1);

namespace App\Scolarite\Infrastructure\Persistence\Doctrine;

use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Model\Competency\CompetencyEvaluationId;
use App\Scolarite\Domain\Model\Competency\CompetencyId;
use App\Scolarite\Domain\Model\Competency\StudentCompetencyResult;
use App\Scolarite\Domain\Model\Competency\StudentCompetencyResultId;
use App\Scolarite\Domain\Repository\StudentCompetencyResultRepository;
use App\Shared\Domain\Tenant\TenantId;

use function array_map;

use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use Override;

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

    #[Override]
    public function save(StudentCompetencyResult $result): void
    {
        $this->connection->executeStatement(
            'INSERT INTO student_competency_results (id, tenant_id, competency_evaluation_id, student_id, level_code, created_at, updated_at)
             VALUES (:id, :tenant_id, :competency_evaluation_id, :student_id, :level_code, :created_at, :updated_at)
             ON CONFLICT (competency_evaluation_id, student_id) DO UPDATE SET
                 level_code = EXCLUDED.level_code,
                 updated_at = EXCLUDED.updated_at',
            [
                'id' => (string) $result->id,
                'tenant_id' => (string) $result->tenantId,
                'competency_evaluation_id' => (string) $result->competencyEvaluationId,
                'student_id' => (string) $result->studentId,
                'level_code' => $result->levelCode,
                'created_at' => $result->createdAt->format(DateTimeImmutable::ATOM),
                'updated_at' => $result->updatedAt->format(DateTimeImmutable::ATOM),
            ],
        );
    }

    #[Override]
    public function delete(StudentCompetencyResult $result): void
    {
        $this->connection->executeStatement(
            'DELETE FROM student_competency_results WHERE id = :id AND tenant_id = :tenant_id',
            [
                'id' => (string) $result->id,
                'tenant_id' => (string) $result->tenantId,
            ],
        );
    }

    #[Override]
    public function findByCompetencyEvaluation(
        CompetencyEvaluationId $competencyEvaluationId,
        TenantId $tenantId,
    ): array {
        $rows = $this->connection->fetchAllAssociative(
            'SELECT * FROM student_competency_results
             WHERE competency_evaluation_id = :competency_evaluation_id
             AND tenant_id = :tenant_id',
            [
                'competency_evaluation_id' => (string) $competencyEvaluationId,
                'tenant_id' => (string) $tenantId,
            ],
        );

        return array_map($this->hydrate(...), $rows);
    }

    #[Override]
    public function findByCompetencyEvaluationAndStudent(
        CompetencyEvaluationId $competencyEvaluationId,
        UserId $studentId,
        TenantId $tenantId,
    ): ?StudentCompetencyResult {
        $row = $this->connection->fetchAssociative(
            'SELECT * FROM student_competency_results
             WHERE competency_evaluation_id = :competency_evaluation_id
             AND student_id = :student_id
             AND tenant_id = :tenant_id',
            [
                'competency_evaluation_id' => (string) $competencyEvaluationId,
                'student_id' => (string) $studentId,
                'tenant_id' => (string) $tenantId,
            ],
        );

        if ($row === false) {
            return null;
        }

        return $this->hydrate($row);
    }

    #[Override]
    public function findByStudentAndCompetency(
        UserId $studentId,
        CompetencyId $competencyId,
        TenantId $tenantId,
    ): array {
        $rows = $this->connection->fetchAllAssociative(
            'SELECT scr.*
             FROM student_competency_results scr
             JOIN competency_evaluations ce ON ce.id = scr.competency_evaluation_id
             JOIN evaluations e ON e.id = ce.evaluation_id
             WHERE scr.student_id = :student_id
             AND ce.competency_id = :competency_id
             AND scr.tenant_id = :tenant_id
             ORDER BY e.evaluation_date ASC',
            [
                'student_id' => (string) $studentId,
                'competency_id' => (string) $competencyId,
                'tenant_id' => (string) $tenantId,
            ],
        );

        return array_map($this->hydrate(...), $rows);
    }

    /** @param array<string, mixed> $row */
    private function hydrate(array $row): StudentCompetencyResult
    {
        /** @var string $id */
        $id = $row['id'];
        /** @var string $tenantId */
        $tenantId = $row['tenant_id'];
        /** @var string $ceId */
        $ceId = $row['competency_evaluation_id'];
        /** @var string $studentId */
        $studentId = $row['student_id'];
        /** @var string $levelCode */
        $levelCode = $row['level_code'];
        /** @var string $createdAt */
        $createdAt = $row['created_at'];
        /** @var string $updatedAt */
        $updatedAt = $row['updated_at'];

        return StudentCompetencyResult::reconstitute(
            id: StudentCompetencyResultId::fromString($id),
            tenantId: TenantId::fromString($tenantId),
            competencyEvaluationId: CompetencyEvaluationId::fromString($ceId),
            studentId: UserId::fromString($studentId),
            levelCode: $levelCode,
            createdAt: new DateTimeImmutable($createdAt),
            updatedAt: new DateTimeImmutable($updatedAt),
        );
    }
}
