<?php
declare(strict_types=1);

namespace App\Settings;

use PDO;

final class SettingsRepository
{
    public function __construct(
        private readonly PDO $pdo,
    ) {
    }

    /** @return array<string, array{value: ?string, type: string, is_secret: bool}> */
    public function all(): array
    {
        $statement = $this->pdo->query('SELECT `key`, value, type, is_secret FROM settings ORDER BY `key` ASC');
        $settings = [];

        foreach ($statement->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $key = (string) ($row['key'] ?? '');
            if ($key === '') {
                continue;
            }

            $settings[$key] = [
                'value' => $row['value'] === null ? null : (string) $row['value'],
                'type' => (string) ($row['type'] ?? 'string'),
                'is_secret' => (bool) ($row['is_secret'] ?? false),
            ];
        }

        return $settings;
    }

    public function get(string $key, mixed $default = null): mixed
    {
        $statement = $this->pdo->prepare('SELECT value, type FROM settings WHERE `key` = :key LIMIT 1');
        $statement->execute(['key' => $key]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);

        if (!is_array($row)) {
            return $default;
        }

        return $this->decode($row['value'] ?? null, (string) ($row['type'] ?? 'string'), $default);
    }

    public function put(string $key, mixed $value, string $type = 'string', bool $secret = false): void
    {
        $encoded = $this->encode($value, $type);
        $statement = $this->pdo->prepare(
            'INSERT INTO settings (`key`, value, type, is_secret, created_at, updated_at)
             VALUES (:key, :value, :type, :is_secret, NOW(), NOW())
             ON DUPLICATE KEY UPDATE
                value = VALUES(value),
                type = VALUES(type),
                is_secret = VALUES(is_secret),
                updated_at = NOW()'
        );
        $statement->execute([
            'key' => $key,
            'value' => $encoded,
            'type' => $type,
            'is_secret' => $secret ? 1 : 0,
        ]);
    }

    public function forget(string $key): void
    {
        $statement = $this->pdo->prepare('DELETE FROM settings WHERE `key` = :key');
        $statement->execute(['key' => $key]);
    }

    private function encode(mixed $value, string $type): ?string
    {
        if ($value === null) {
            return null;
        }

        if ($type === 'boolean') {
            return filter_var($value, FILTER_VALIDATE_BOOLEAN) ? '1' : '0';
        }

        if ($type === 'integer') {
            return (string) ((int) $value);
        }

        if ($type === 'json') {
            return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
        }

        return trim((string) $value);
    }

    private function decode(mixed $value, string $type, mixed $default): mixed
    {
        if ($value === null) {
            return $default;
        }

        return match ($type) {
            'boolean' => (string) $value === '1',
            'integer' => (int) $value,
            'json' => json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR),
            default => (string) $value,
        };
    }
}
