<?php

namespace App\Services;

use App\Models\Setting;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;

class SettingsService
{
    private const CACHE_KEY = 'settings.all';

    private const CACHE_TTL = 3600;

    public function get(string $key, mixed $default = null): mixed
    {
        $settings = $this->all();

        if (! array_key_exists($key, $settings)) {
            return $default;
        }

        return $settings[$key];
    }

    public function set(string $key, mixed $value, bool $encrypted = false): void
    {
        $storedValue = $encrypted && $value !== null
            ? Crypt::encryptString((string) $value)
            : (string) $value;

        DB::transaction(function () use ($key, $storedValue, $encrypted): void {
            Setting::query()->updateOrCreate(
                ['key' => $key],
                [
                    'value' => $storedValue,
                    'is_encrypted' => $encrypted,
                ],
            );
        });

        $this->clearCache();
    }

    /**
     * @return array<string, mixed>
     */
    public function all(): array
    {
        /** @var array<string, mixed> $settings */
        $settings = Cache::store('file')->remember(
            self::CACHE_KEY,
            self::CACHE_TTL,
            fn (): array => $this->loadFromDatabase(),
        );

        return $settings;
    }

    public function clearCache(): void
    {
        Cache::store('file')->forget(self::CACHE_KEY);
    }

    /**
     * @param  array<string, mixed>  $settings
     */
    public function setMany(array $settings): void
    {
        DB::transaction(function () use ($settings): void {
            foreach ($settings as $key => $payload) {
                if (is_array($payload)) {
                    $this->set(
                        (string) $key,
                        $payload['value'] ?? null,
                        (bool) ($payload['is_encrypted'] ?? false),
                    );

                    continue;
                }

                $this->set((string) $key, $payload);
            }
        });
    }

    /**
     * @return array<string, mixed>
     */
    private function loadFromDatabase(): array
    {
        $settings = [];

        Setting::query()
            ->orderBy('key')
            ->get()
            ->each(function (Setting $setting) use (&$settings): void {
                $settings[$setting->key] = $this->resolveValue($setting);
            });

        return $settings;
    }

    private function resolveValue(Setting $setting): mixed
    {
        if ($setting->value === null) {
            return null;
        }

        if ($setting->is_encrypted) {
            try {
                return Crypt::decryptString($setting->value);
            } catch (\Throwable) {
                return null;
            }
        }

        return $setting->value;
    }
}
