<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Persistence\Doctrine;

use App\Administration\Domain\Exception\HomeworkRulesNotFoundException;
use App\Administration\Domain\Model\HomeworkRules\EnforcementMode;
use App\Administration\Domain\Model\HomeworkRules\HomeworkRule;
use App\Administration\Domain\Model\HomeworkRules\HomeworkRules;
use App\Administration\Domain\Model\HomeworkRules\HomeworkRulesId;
use App\Administration\Domain\Repository\HomeworkRulesRepository;
use App\Shared\Domain\Tenant\TenantId;

use function array_map;

use DateTimeImmutable;
use Doctrine\DBAL\Connection;

use function is_string;
use function json_decode;
use function json_encode;

use const JSON_THROW_ON_ERROR;

use Override;

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

    #[Override]
    public function save(HomeworkRules $homeworkRules): void
    {
        $rulesJson = json_encode(
            array_map(
                static fn (HomeworkRule $rule): array => $rule->toArray(),
                $homeworkRules->rules,
            ),
            JSON_THROW_ON_ERROR,
        );

        $this->connection->executeStatement(
            'INSERT INTO homework_rules (id, tenant_id, rules, enforcement_mode, enabled, created_at, updated_at)
             VALUES (:id, :tenant_id, :rules, :enforcement_mode, :enabled, :created_at, :updated_at)
             ON CONFLICT (tenant_id) DO UPDATE SET
                 rules = EXCLUDED.rules,
                 enforcement_mode = EXCLUDED.enforcement_mode,
                 enabled = EXCLUDED.enabled,
                 updated_at = EXCLUDED.updated_at',
            [
                'id' => (string) $homeworkRules->id,
                'tenant_id' => (string) $homeworkRules->tenantId,
                'rules' => $rulesJson,
                'enforcement_mode' => $homeworkRules->enforcementMode->value,
                'enabled' => $homeworkRules->enabled ? 'true' : 'false',
                'created_at' => $homeworkRules->createdAt->format(DateTimeImmutable::ATOM),
                'updated_at' => $homeworkRules->updatedAt->format(DateTimeImmutable::ATOM),
            ],
        );
    }

    #[Override]
    public function get(TenantId $tenantId): HomeworkRules
    {
        $rules = $this->findByTenantId($tenantId);

        if ($rules === null) {
            throw HomeworkRulesNotFoundException::pourTenant($tenantId);
        }

        return $rules;
    }

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

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

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

    /**
     * @param array<string, mixed> $row
     */
    private function hydrate(array $row): HomeworkRules
    {
        /** @var string $id */
        $id = $row['id'];
        /** @var string $tenantId */
        $tenantId = $row['tenant_id'];
        /** @var string $rulesJson */
        $rulesJson = $row['rules'];
        /** @var string $enforcementMode */
        $enforcementMode = $row['enforcement_mode'];
        /** @var bool|string $enabled */
        $enabled = $row['enabled'];
        /** @var string $createdAt */
        $createdAt = $row['created_at'];
        /** @var string $updatedAt */
        $updatedAt = $row['updated_at'];

        /** @var array<int, array{type: string, params: array<string, mixed>}> $rulesData */
        $rulesData = json_decode($rulesJson, true, 512, JSON_THROW_ON_ERROR);

        return HomeworkRules::reconstitute(
            id: HomeworkRulesId::fromString($id),
            tenantId: TenantId::fromString($tenantId),
            rules: array_map(HomeworkRule::fromArray(...), $rulesData),
            enforcementMode: EnforcementMode::from($enforcementMode),
            enabled: is_string($enabled) ? $enabled === 'true' || $enabled === 't' : (bool) $enabled,
            createdAt: new DateTimeImmutable($createdAt),
            updatedAt: new DateTimeImmutable($updatedAt),
        );
    }
}
