<?php

declare(strict_types=1);

namespace App\Scolarite\Infrastructure\Persistence\Doctrine;

use App\Scolarite\Domain\Model\Competency\CompetencyEvaluation;
use App\Scolarite\Domain\Model\Competency\CompetencyEvaluationId;
use App\Scolarite\Domain\Model\Competency\CompetencyId;
use App\Scolarite\Domain\Model\Evaluation\EvaluationId;
use App\Scolarite\Domain\Repository\CompetencyEvaluationRepository;

use function array_map;

use Doctrine\DBAL\Connection;
use Override;

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

    #[Override]
    public function save(CompetencyEvaluation $competencyEvaluation): void
    {
        $this->connection->executeStatement(
            'INSERT INTO competency_evaluations (id, evaluation_id, competency_id)
             VALUES (:id, :evaluation_id, :competency_id)
             ON CONFLICT (evaluation_id, competency_id) DO UPDATE SET id = competency_evaluations.id',
            [
                'id' => (string) $competencyEvaluation->id,
                'evaluation_id' => (string) $competencyEvaluation->evaluationId,
                'competency_id' => (string) $competencyEvaluation->competencyId,
            ],
        );
    }

    #[Override]
    public function findById(CompetencyEvaluationId $id): ?CompetencyEvaluation
    {
        $row = $this->connection->fetchAssociative(
            'SELECT * FROM competency_evaluations WHERE id = :id',
            ['id' => (string) $id],
        );

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

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

    #[Override]
    public function findByEvaluation(EvaluationId $evaluationId): array
    {
        $rows = $this->connection->fetchAllAssociative(
            'SELECT ce.*, c.code AS competency_code, c.name AS competency_name
             FROM competency_evaluations ce
             JOIN competencies c ON c.id = ce.competency_id
             WHERE ce.evaluation_id = :evaluation_id
             ORDER BY c.sort_order ASC',
            ['evaluation_id' => (string) $evaluationId],
        );

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

    /** @param array<string, mixed> $row */
    private function hydrate(array $row): CompetencyEvaluation
    {
        /** @var string $id */
        $id = $row['id'];
        /** @var string $evaluationId */
        $evaluationId = $row['evaluation_id'];
        /** @var string $competencyId */
        $competencyId = $row['competency_id'];

        return CompetencyEvaluation::reconstitute(
            id: CompetencyEvaluationId::fromString($id),
            evaluationId: EvaluationId::fromString($evaluationId),
            competencyId: CompetencyId::fromString($competencyId),
        );
    }
}
