<?php

declare(strict_types=1);

namespace App\Scolarite\Infrastructure\Persistence\Doctrine;

use App\Scolarite\Domain\Model\Competency\Competency;
use App\Scolarite\Domain\Model\Competency\CompetencyFrameworkId;
use App\Scolarite\Domain\Model\Competency\CompetencyId;
use App\Scolarite\Domain\Repository\CompetencyRepository;
use App\Shared\Domain\Tenant\TenantId;

use function array_map;

use Doctrine\DBAL\Connection;
use Override;

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

    #[Override]
    public function save(Competency $competency): void
    {
        $this->connection->executeStatement(
            'INSERT INTO competencies (id, framework_id, code, name, description, parent_id, sort_order)
             VALUES (:id, :framework_id, :code, :name, :description, :parent_id, :sort_order)
             ON CONFLICT (id) DO UPDATE SET
                 code = EXCLUDED.code,
                 name = EXCLUDED.name,
                 description = EXCLUDED.description,
                 parent_id = EXCLUDED.parent_id,
                 sort_order = EXCLUDED.sort_order',
            [
                'id' => (string) $competency->id,
                'framework_id' => (string) $competency->frameworkId,
                'code' => $competency->code,
                'name' => $competency->name,
                'description' => $competency->description,
                'parent_id' => $competency->parentId !== null ? (string) $competency->parentId : null,
                'sort_order' => $competency->sortOrder,
            ],
        );
    }

    #[Override]
    public function findById(CompetencyId $id, TenantId $tenantId): ?Competency
    {
        $row = $this->connection->fetchAssociative(
            'SELECT c.* FROM competencies c
             JOIN competency_frameworks cf ON cf.id = c.framework_id
             WHERE c.id = :id AND cf.tenant_id = :tenant_id',
            ['id' => (string) $id, 'tenant_id' => (string) $tenantId],
        );

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

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

    #[Override]
    public function findByFramework(CompetencyFrameworkId $frameworkId): array
    {
        $rows = $this->connection->fetchAllAssociative(
            'SELECT * FROM competencies WHERE framework_id = :framework_id ORDER BY sort_order ASC',
            ['framework_id' => (string) $frameworkId],
        );

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

    /** @param array<string, mixed> $row */
    private function hydrate(array $row): Competency
    {
        /** @var string $id */
        $id = $row['id'];
        /** @var string $frameworkId */
        $frameworkId = $row['framework_id'];
        /** @var string $code */
        $code = $row['code'];
        /** @var string $name */
        $name = $row['name'];
        /** @var string|null $description */
        $description = $row['description'];
        /** @var string|null $parentId */
        $parentId = $row['parent_id'];
        /** @var string|int $sortOrder */
        $sortOrder = $row['sort_order'];

        return Competency::reconstitute(
            id: CompetencyId::fromString($id),
            frameworkId: CompetencyFrameworkId::fromString($frameworkId),
            code: $code,
            name: $name,
            description: $description,
            parentId: $parentId !== null ? CompetencyId::fromString($parentId) : null,
            sortOrder: (int) $sortOrder,
        );
    }
}
