<?php

declare(strict_types=1);

namespace App\SuperAdmin\Infrastructure\Persistence\Doctrine;

use App\Shared\Domain\Tenant\TenantId;
use App\SuperAdmin\Domain\Exception\EstablishmentIntrouvableException;
use App\SuperAdmin\Domain\Model\Establishment\Establishment;
use App\SuperAdmin\Domain\Model\Establishment\EstablishmentId;
use App\SuperAdmin\Domain\Model\Establishment\EstablishmentStatus;
use App\SuperAdmin\Domain\Model\SuperAdmin\SuperAdminId;
use App\SuperAdmin\Domain\Repository\EstablishmentRepository;
use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use Override;

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

    #[Override]
    public function save(Establishment $establishment): void
    {
        $this->connection->executeStatement(
            <<<'SQL'
                INSERT INTO establishments (id, tenant_id, name, subdomain, database_name, status, created_at, created_by, last_activity_at)
                VALUES (:id, :tenant_id, :name, :subdomain, :database_name, :status, :created_at, :created_by, :last_activity_at)
                ON CONFLICT (id) DO UPDATE SET
                    name = EXCLUDED.name,
                    status = EXCLUDED.status,
                    last_activity_at = EXCLUDED.last_activity_at
            SQL,
            [
                'id' => (string) $establishment->id,
                'tenant_id' => (string) $establishment->tenantId,
                'name' => $establishment->name,
                'subdomain' => $establishment->subdomain,
                'database_name' => $establishment->databaseName,
                'status' => $establishment->status->value,
                'created_at' => $establishment->createdAt->format('Y-m-d H:i:sP'),
                'created_by' => $establishment->createdBy !== null ? (string) $establishment->createdBy : null,
                'last_activity_at' => $establishment->lastActivityAt?->format('Y-m-d H:i:sP'),
            ],
        );
    }

    #[Override]
    public function get(EstablishmentId $id): Establishment
    {
        $row = $this->connection->fetchAssociative(
            'SELECT * FROM establishments WHERE id = :id',
            ['id' => (string) $id],
        );

        if ($row === false) {
            throw EstablishmentIntrouvableException::avecId($id);
        }

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

    #[Override]
    public function findByTenantId(TenantId $tenantId): ?Establishment
    {
        $row = $this->connection->fetchAssociative(
            'SELECT * FROM establishments WHERE tenant_id = :tenant_id',
            ['tenant_id' => (string) $tenantId],
        );

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

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

    /**
     * @return Establishment[]
     */
    #[Override]
    public function findAll(): array
    {
        $rows = $this->connection->fetchAllAssociative(
            'SELECT * FROM establishments ORDER BY name ASC',
        );

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

    /**
     * @param array<string, mixed> $row
     */
    private function hydrate(array $row): Establishment
    {
        /** @var string $id */
        $id = $row['id'];
        /** @var string $tenantId */
        $tenantId = $row['tenant_id'];
        /** @var string $name */
        $name = $row['name'];
        /** @var string $subdomain */
        $subdomain = $row['subdomain'];
        /** @var string $databaseName */
        $databaseName = $row['database_name'];
        /** @var string $status */
        $status = $row['status'];
        /** @var string $createdAt */
        $createdAt = $row['created_at'];
        /** @var string|null $createdBy */
        $createdBy = $row['created_by'];
        /** @var string|null $lastActivityAt */
        $lastActivityAt = $row['last_activity_at'];

        return Establishment::reconstitute(
            id: EstablishmentId::fromString($id),
            tenantId: TenantId::fromString($tenantId),
            name: $name,
            subdomain: $subdomain,
            databaseName: $databaseName,
            status: EstablishmentStatus::from($status),
            createdAt: new DateTimeImmutable($createdAt),
            createdBy: $createdBy !== null ? SuperAdminId::fromString($createdBy) : null,
            lastActivityAt: $lastActivityAt !== null ? new DateTimeImmutable($lastActivityAt) : null,
        );
    }
}
