<?php

declare(strict_types=1);

namespace App\Scolarite\Infrastructure\Persistence\Doctrine;

use App\Scolarite\Domain\Model\Competency\CompetencyFramework;
use App\Scolarite\Domain\Model\Competency\CompetencyFrameworkId;
use App\Scolarite\Domain\Repository\CompetencyFrameworkRepository;
use App\Shared\Domain\Tenant\TenantId;

use function array_map;

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

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

    #[Override]
    public function save(CompetencyFramework $framework): void
    {
        $this->connection->executeStatement(
            'INSERT INTO competency_frameworks (id, tenant_id, name, is_default, created_at)
             VALUES (:id, :tenant_id, :name, :is_default, :created_at)
             ON CONFLICT (id) DO UPDATE SET
                 name = EXCLUDED.name,
                 is_default = EXCLUDED.is_default',
            [
                'id' => (string) $framework->id,
                'tenant_id' => (string) $framework->tenantId,
                'name' => $framework->name,
                'is_default' => $framework->isDefault ? 'true' : 'false',
                'created_at' => $framework->createdAt->format(DateTimeImmutable::ATOM),
            ],
        );
    }

    #[Override]
    public function findById(CompetencyFrameworkId $id, TenantId $tenantId): ?CompetencyFramework
    {
        $row = $this->connection->fetchAssociative(
            'SELECT * FROM competency_frameworks WHERE id = :id AND tenant_id = :tenant_id',
            ['id' => (string) $id, 'tenant_id' => (string) $tenantId],
        );

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

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

    #[Override]
    public function findDefault(TenantId $tenantId): ?CompetencyFramework
    {
        $row = $this->connection->fetchAssociative(
            'SELECT * FROM competency_frameworks WHERE tenant_id = :tenant_id AND is_default = true LIMIT 1',
            ['tenant_id' => (string) $tenantId],
        );

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

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

    #[Override]
    public function findByTenant(TenantId $tenantId): array
    {
        $rows = $this->connection->fetchAllAssociative(
            'SELECT * FROM competency_frameworks WHERE tenant_id = :tenant_id ORDER BY is_default DESC, name ASC',
            ['tenant_id' => (string) $tenantId],
        );

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

    /** @param array<string, mixed> $row */
    private function hydrate(array $row): CompetencyFramework
    {
        /** @var string $id */
        $id = $row['id'];
        /** @var string $tenantId */
        $tenantId = $row['tenant_id'];
        /** @var string $name */
        $name = $row['name'];
        /** @var bool $isDefault */
        $isDefault = (bool) $row['is_default'];
        /** @var string $createdAt */
        $createdAt = $row['created_at'];

        return CompetencyFramework::reconstitute(
            id: CompetencyFrameworkId::fromString($id),
            tenantId: TenantId::fromString($tenantId),
            name: $name,
            isDefault: $isDefault,
            createdAt: new DateTimeImmutable($createdAt),
        );
    }
}
