<?php

/**
 * ============================================================
 *
 * @project   biiproject
 *
 * @author    Andika Debi Putra
 *
 * @email     andikadebiputra@gmail.com
 *
 * @website   https://biiproject.com
 *
 * @copyright Copyright (c) 2026 Andika Debi Putra
 * @license   Proprietary - All Rights Reserved
 *
 * @version   1.0.0
 *
 * @created   2026-05-01
 * ============================================================
 *
 * Unauthorized copying, modification, distribution, or use
 * of this file is strictly prohibited without prior written
 * permission from the author.
 * ============================================================
 */

namespace App\Services\SystemConfig;

use App\Models\SystemSettingRevision;
use App\Repositories\SystemSettingRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;

class SystemConfigService
{
    private const CACHE_KEY = 'system_settings.all';

    private const PUBLIC_CACHE_KEY = 'system_settings.public';

    private const CACHE_TTL_MINUTES = 60;

    protected static ?array $resolvedSettings = null;

    public function __construct(
        protected SystemSettingRepository $repository,
        protected SettingFileUploader $fileUploader = new SettingFileUploader,
    ) {}

    public static function definitions(): array
    {
        return SettingDefinitions::ALL;
    }

    public function all(bool $forceRefresh = false): array
    {
        if ($forceRefresh) {
            $this->invalidateCache();
        }

        if (self::$resolvedSettings !== null) {
            return self::$resolvedSettings;
        }

        try {
            return self::$resolvedSettings = Cache::remember(
                self::CACHE_KEY,
                now()->addMinutes(self::CACHE_TTL_MINUTES),
                fn () => $this->resolveAll(),
            );
        } catch (\Exception) {
            return self::$resolvedSettings = $this->resolveAll();
        }
    }

    public function getPublicSettings(): array
    {
        try {
            return Cache::remember(
                self::PUBLIC_CACHE_KEY,
                now()->addMinutes(self::CACHE_TTL_MINUTES),
                fn () => $this->buildPublicSettings(),
            );
        } catch (\Exception) {
            return $this->buildPublicSettings();
        }
    }

    public function grouped(): array
    {
        $all = $this->all();
        $grouped = [];

        foreach (SettingDefinitions::ALL as $key => $meta) {
            $grouped[$meta['group']][$key] = $all[$key] ?? $meta['default'] ?? null;
        }

        return $grouped;
    }

    public function get(string $key, mixed $default = null): mixed
    {
        if (! array_key_exists($key, SettingDefinitions::ALL)) {
            return $default;
        }

        return $this->all()[$key] ?? $default;
    }

    public function update(array $input, array $files = [], ?int $actorId = null, ?Request $request = null): void
    {
        $current = $this->all();

        foreach (SettingDefinitions::ALL as $key => $meta) {
            $oldValue = $current[$key] ?? $meta['default'] ?? null;
            $newValue = $this->resolveNewValue($key, $meta, $input, $files, $oldValue);

            if (SettingValueCaster::isUnchanged($oldValue, $newValue)) {
                continue;
            }

            $setting = $this->repository->upsert([
                'key' => $key,
                'value' => SettingValueCaster::serialize($newValue),
                'type' => $meta['type'],
                'group' => $meta['group'],
                'is_public' => (bool) $meta['is_public'],
                'description' => $meta['description'],
                'created_by' => $actorId,
                'updated_by' => $actorId,
            ]);

            $this->writeRevision($setting->id, $key, $oldValue, $newValue, $actorId, $request);
        }

        $this->invalidateCache();
    }

    protected function resolveNewValue(string $key, array $meta, array $input, array $files, mixed $oldValue): mixed
    {
        if ($meta['type'] === 'image_path') {
            return isset($files[$key])
                ? $this->fileUploader->replace($key, $files[$key], $oldValue)
                : $oldValue;
        }

        return array_key_exists($key, $input)
            ? SettingValueCaster::normalize($input[$key], (string) $meta['type'])
            : $oldValue;
    }

    protected function resolveAll(): array
    {
        try {
            if (! $this->repository->tableExists()) {
                return $this->definitionDefaults();
            }
            $rows = $this->repository->all();
        } catch (\Exception) {
            return $this->definitionDefaults();
        }

        $resolved = [];
        foreach (SettingDefinitions::ALL as $key => $meta) {
            $record = $rows->firstWhere('key', $key);
            $resolved[$key] = $record
                ? SettingValueCaster::deserialize($record->value, (string) $record->type)
                : ($meta['default'] ?? null);
        }

        return $resolved;
    }

    protected function buildPublicSettings(): array
    {
        $all = $this->all();
        $public = [];

        foreach (SettingDefinitions::ALL as $key => $meta) {
            if (! $meta['is_public']) {
                continue;
            }
            $public[$key] = $all[$key] ?? $meta['default'] ?? null;
        }

        return $public;
    }

    protected function definitionDefaults(): array
    {
        $defaults = [];
        foreach (SettingDefinitions::ALL as $key => $meta) {
            $defaults[$key] = $meta['default'] ?? null;
        }

        return $defaults;
    }

    protected function writeRevision(
        int $settingId,
        string $key,
        mixed $oldValue,
        mixed $newValue,
        ?int $actorId,
        ?Request $request,
    ): void {
        SystemSettingRevision::query()->create([
            'system_setting_id' => $settingId,
            'key' => $key,
            'old_value' => $oldValue === null ? null : json_encode($oldValue, JSON_UNESCAPED_SLASHES),
            'new_value' => $newValue === null ? null : json_encode($newValue, JSON_UNESCAPED_SLASHES),
            'changed_by' => $actorId,
            'changed_ip' => $request?->ip(),
            'changed_agent' => $request?->userAgent(),
            'created_at' => now(),
        ]);
    }

    protected function invalidateCache(): void
    {
        Cache::forget(self::CACHE_KEY);
        Cache::forget(self::PUBLIC_CACHE_KEY);
        self::$resolvedSettings = null;
    }
}
