<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;

class SystemSetting extends Model
{
    use HasFactory;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'key',
        'value',
        'type',
        'group',
        'description',
        'is_public',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'is_public' => 'boolean',
    ];

    /**
     * Setting types.
     */
    const TYPE_STRING = 'string';
    const TYPE_BOOLEAN = 'boolean';
    const TYPE_INTEGER = 'integer';
    const TYPE_JSON = 'json';

    /**
     * Setting groups.
     */
    const GROUP_GENERAL = 'general';
    const GROUP_AUTH = 'auth';
    const GROUP_OTP = 'otp';
    const GROUP_EMAIL = 'email';
    const GROUP_SECURITY = 'security';

    /**
     * Cache duration in seconds.
     */
    const CACHE_DURATION = 3600; // 1 hour

    /**
     * Get a setting value by key.
     *
     * @param string $key
     * @param mixed $default
     * @return mixed
     */
    public static function get(string $key, mixed $default = null): mixed
    {
        $cacheKey = "setting.{$key}";

        return Cache::remember($cacheKey, self::CACHE_DURATION, function () use ($key, $default) {
            $setting = self::where('key', $key)->first();

            if (!$setting) {
                return $default;
            }

            return self::castValue($setting->value, $setting->type);
        });
    }

    /**
     * Set a setting value.
     *
     * @param string $key
     * @param mixed $value
     * @param string $type
     * @param string $group
     * @param string|null $description
     * @param bool $isPublic
     * @return self
     */
    public static function set(
        string $key,
        mixed $value,
        string $type = self::TYPE_STRING,
        string $group = self::GROUP_GENERAL,
        ?string $description = null,
        bool $isPublic = false
    ): self {
        $setting = self::updateOrCreate(
            ['key' => $key],
            [
                'value' => self::prepareValue($value, $type),
                'type' => $type,
                'group' => $group,
                'description' => $description,
                'is_public' => $isPublic,
            ]
        );

        // Clear cache
        Cache::forget("setting.{$key}");

        return $setting;
    }

    /**
     * Get all public settings.
     *
     * @return array
     */
    public static function getPublic(): array
    {
        return Cache::remember('settings.public', self::CACHE_DURATION, function () {
            return self::where('is_public', true)
                ->get()
                ->pluck('value', 'key')
                ->toArray();
        });
    }

    /**
     * Get all settings by group.
     *
     * @param string $group
     * @return array
     */
    public static function getByGroup(string $group): array
    {
        return Cache::remember("settings.group.{$group}", self::CACHE_DURATION, function () use ($group) {
            return self::where('group', $group)
                ->get()
                ->pluck('value', 'key')
                ->toArray();
        });
    }

    /**
     * Cast a value to the appropriate type.
     *
     * @param mixed $value
     * @param string $type
     * @return mixed
     */
    protected static function castValue(mixed $value, string $type): mixed
    {
        return match ($type) {
            self::TYPE_BOOLEAN => filter_var($value, FILTER_VALIDATE_BOOLEAN),
            self::TYPE_INTEGER => (int) $value,
            self::TYPE_JSON => json_decode($value, true),
            default => $value,
        };
    }

    /**
     * Prepare a value for storage.
     *
     * @param mixed $value
     * @param string $type
     * @return string
     */
    protected static function prepareValue(mixed $value, string $type): string
    {
        return match ($type) {
            self::TYPE_JSON => json_encode($value),
            self::TYPE_BOOLEAN => $value ? '1' : '0',
            default => (string) $value,
        };
    }

    /**
     * Clear all settings cache.
     *
     * @return void
     */
    public static function clearCache(): void
    {
        $keys = self::pluck('key');
        foreach ($keys as $key) {
            Cache::forget("setting.{$key}");
        }
        Cache::forget('settings.public');
    }
}
