<?php

namespace MintyPHP\Service\Settings;

class SettingCacheService
{
    private const HOT_PATH_KEYS = [
        SettingKeys::APP_TITLE_KEY,
        SettingKeys::APP_LOCALE_KEY,
        SettingKeys::APP_REGISTRATION_KEY,
    ];

    private ?array $settings = null;
    private ?string $cacheFilePath;
    private ?SettingsMetadataGateway $settingsMetadataGateway;

    public function __construct(?string $cacheFilePath = null, ?SettingsMetadataGateway $settingsMetadataGateway = null)
    {
        $this->cacheFilePath = $cacheFilePath;
        $this->settingsMetadataGateway = $settingsMetadataGateway;
    }

    public function get(string $key): ?string
    {
        $settings = $this->all();
        $value = $settings[$key] ?? null;
        if ($value === null) {
            return null;
        }
        $value = trim((string) $value);
        return $value !== '' ? $value : null;
    }

    public function all(): array
    {
        if ($this->settings !== null) {
            return $this->settings;
        }

        $file = $this->filePath();
        if (!is_file($file)) {
            $this->settings = $this->hydrateColdStartCache();
            return $this->settings;
        }

        $loaded = include $file;
        $this->settings = is_array($loaded) ? $loaded : [];
        return $this->settings;
    }

    public function update(array $values): bool
    {
        $current = $this->all();
        foreach ($values as $key => $value) {
            $settingKey = trim((string) $key);
            if ($settingKey === '') {
                continue;
            }
            $current[$settingKey] = $value === null ? null : (string) $value;
        }
        return $this->write($current);
    }

    public function write(array $settings): bool
    {
        $file = $this->filePath();
        $dir = dirname($file);
        if (!is_dir($dir) && !@mkdir($dir, 0775, true) && !is_dir($dir)) {
            return false;
        }

        // Write to a temp file first, then rename — rename is atomic on most filesystems,
        // so readers never see a half-written file. LOCK_EX prevents concurrent write corruption.
        $tmp = tempnam($dir, 'settings_');
        if ($tmp === false) {
            return false;
        }

        // PHP include returns an array — loaded by OPcache on subsequent requests, much faster than DB.
        $payload = "<?php\n\nreturn " . $this->exportValue($settings) . ";\n";
        $written = @file_put_contents($tmp, $payload, LOCK_EX);
        if ($written === false) {
            @unlink($tmp);
            return false;
        }

        if (!@rename($tmp, $file)) {
            @unlink($tmp);
            return false;
        }

        $this->settings = $settings;
        return true;
    }

    public function filePath(): string
    {
        if (is_string($this->cacheFilePath) && $this->cacheFilePath !== '') {
            return $this->cacheFilePath;
        }

        $storagePath = defined('APP_STORAGE_PATH')
            ? (string) APP_STORAGE_PATH
            : dirname(__DIR__, 3) . '/storage';

        return rtrim($storagePath, '/') . '/runtime/settings.php';
    }

    private function hydrateColdStartCache(): array
    {
        if (!$this->settingsMetadataGateway instanceof SettingsMetadataGateway) {
            return [];
        }

        $hydrated = [];
        foreach (self::HOT_PATH_KEYS as $key) {
            $value = $this->settingsMetadataGateway->getValue($key);
            if ($value === null) {
                continue;
            }
            $hydrated[$key] = (string) $value;
        }

        if ($this->write($hydrated)) {
            return $this->settings ?? $hydrated;
        }

        $this->settings = $hydrated;
        return $hydrated;
    }

    private function exportValue(mixed $value, int $depth = 0): string
    {
        if (!is_array($value)) {
            return var_export($value, true);
        }

        if ($value === []) {
            return '[]';
        }

        $indent = str_repeat('    ', $depth);
        $childIndent = str_repeat('    ', $depth + 1);
        $lines = [];

        foreach ($value as $key => $item) {
            $lines[] = $childIndent
                . var_export($key, true)
                . ' => '
                . $this->exportValue($item, $depth + 1)
                . ',';
        }

        return "[\n" . implode("\n", $lines) . "\n" . $indent . ']';
    }
}
