<?php

namespace App\Support;

use App\Models\Setting;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Schema;
use RuntimeException;

class AdminSecuritySettings
{
    public const ADMIN_SSO_REFS = 'admin_sso_refs';
    public const FINANCE_DAILY_PIN = 'finance_daily_pin';
    public const BALANCE_UPDATE_PIN = 'balance_update_pin';
    public const IMPERSONATE_KEY = 'impersonate_key';
    public const ADMIN_EDIT_PASSWORD = 'admin_edit_password';

    /**
     * @return array<string, array<string, mixed>>
     */
    public static function definitions(): array
    {
        return [
            self::ADMIN_SSO_REFS => [
                'label' => 'Admin SSO Refs',
                'env_key' => 'ADMIN_SSO_REFS',
                'setting_key' => 'admin_sso_refs',
                'config_key' => 'app.admin_sso_refs',
                'sensitive' => false,
                'help' => 'কমা দিয়ে একাধিক referral/reference লিখতে পারবেন।',
            ],
            self::FINANCE_DAILY_PIN => [
                'label' => 'Finance Daily PIN',
                'env_key' => 'FINANCE_DAILY_PIN',
                'setting_key' => 'finance_daily_pin',
                'config_key' => 'finance.daily_pin',
                'sensitive' => true,
                'help' => 'হিসাব-নিকাশ daily save/update করার PIN।',
            ],
            self::BALANCE_UPDATE_PIN => [
                'label' => 'Balance Update PIN',
                'env_key' => 'BALANCE_UPDATE_PIN',
                'setting_key' => 'balance_update_pin',
                'config_key' => 'admin_security.balance_update_pin',
                'sensitive' => true,
                'help' => 'Admin balance update form-এর PIN।',
            ],
            self::IMPERSONATE_KEY => [
                'label' => 'Impersonate Key',
                'env_key' => 'IMPERSONATE_KEY',
                'setting_key' => 'impersonate_key',
                'config_key' => 'admin_security.impersonate_key',
                'sensitive' => true,
                'help' => 'Admin থেকে user account-এ login করার security key।',
            ],
            self::ADMIN_EDIT_PASSWORD => [
                'label' => 'Admin Edit Password',
                'env_key' => 'ADMIN_EDIT_PASSWORD',
                'setting_key' => 'admin_edit_password',
                'config_key' => 'admin_security.edit_password',
                'sensitive' => true,
                'help' => 'User edit/update করার security password।',
            ],
        ];
    }

    /**
     * @return array<string, array{value: string, source: string, has_value: bool}>
     */
    public static function values(): array
    {
        $stored = self::storedValues();
        $values = [];

        foreach (self::definitions() as $field => $definition) {
            $settingKey = (string) $definition['setting_key'];

            if (array_key_exists($settingKey, $stored)) {
                $value = (string) $stored[$settingKey];
                $source = 'database';
            } else {
                $value = self::configValue($definition);
                $source = $value !== '' ? 'env' : 'empty';
            }

            $values[$field] = [
                'value' => $value,
                'source' => $source,
                'has_value' => $value !== '',
            ];
        }

        return $values;
    }

    public static function financeDailyPin(): string
    {
        return self::value(self::FINANCE_DAILY_PIN);
    }

    public static function balanceUpdatePin(): string
    {
        return self::value(self::BALANCE_UPDATE_PIN);
    }

    public static function impersonateKey(): string
    {
        return self::value(self::IMPERSONATE_KEY);
    }

    public static function adminEditPassword(): string
    {
        return self::value(self::ADMIN_EDIT_PASSWORD);
    }

    /**
     * @return array<int, string>
     */
    public static function adminSsoRefs(): array
    {
        return self::normalizeRefsToArray(self::value(self::ADMIN_SSO_REFS));
    }

    public static function value(string $field): string
    {
        $values = self::values();

        return (string) ($values[$field]['value'] ?? '');
    }

    public static function applyToConfig(): void
    {
        $values = self::values();

        Config::set('app.admin_sso_refs', self::normalizeRefsToArray((string) $values[self::ADMIN_SSO_REFS]['value']));
        Config::set('finance.daily_pin', (string) $values[self::FINANCE_DAILY_PIN]['value']);
        Config::set('admin_security.balance_update_pin', (string) $values[self::BALANCE_UPDATE_PIN]['value']);
        Config::set('admin_security.impersonate_key', (string) $values[self::IMPERSONATE_KEY]['value']);
        Config::set('admin_security.edit_password', (string) $values[self::ADMIN_EDIT_PASSWORD]['value']);
    }

    /**
     * @param array<string, mixed> $input
     * @return array<int, string>
     */
    public static function saveFromInput(array $input): array
    {
        $changed = [];
        $definitions = self::definitions();

        $refs = self::normalizeRefsToString((string) ($input[self::ADMIN_SSO_REFS] ?? ''));
        if (self::saveSetting((string) $definitions[self::ADMIN_SSO_REFS]['setting_key'], $refs)) {
            $changed[] = self::ADMIN_SSO_REFS;
        }

        foreach ($definitions as $field => $definition) {
            if ($field === self::ADMIN_SSO_REFS) {
                continue;
            }

            $clearField = 'clear_' . $field;
            $shouldClear = filter_var($input[$clearField] ?? false, FILTER_VALIDATE_BOOLEAN);
            $value = trim((string) ($input[$field] ?? ''));

            if ($shouldClear) {
                if (self::saveSetting((string) $definition['setting_key'], '')) {
                    $changed[] = $field;
                }
                continue;
            }

            if ($value === '') {
                continue;
            }

            if (self::saveSetting((string) $definition['setting_key'], $value)) {
                $changed[] = $field;
            }
        }

        self::applyToConfig();

        return $changed;
    }

    /**
     * @return array<string, string|null>
     */
    private static function storedValues(): array
    {
        if (!self::settingsTableAvailable()) {
            return [];
        }

        try {
            $keys = array_map(
                static fn(array $definition): string => (string) $definition['setting_key'],
                self::definitions()
            );

            $values = [];

            Setting::query()
                ->whereIn('key', $keys)
                ->orderBy('id')
                ->get(['key', 'value'])
                ->each(function (Setting $setting) use (&$values): void {
                    $values[(string) $setting->key] = $setting->value;
                });

            return $values;
        } catch (\Throwable) {
            return [];
        }
    }

    /**
     * @param array<string, mixed> $definition
     */
    private static function configValue(array $definition): string
    {
        $value = config((string) $definition['config_key'], '');

        if (is_array($value)) {
            return self::normalizeRefsToString($value);
        }

        return trim((string) $value);
    }

    private static function saveSetting(string $key, string $value): bool
    {
        if (!self::settingsTableAvailable()) {
            throw new RuntimeException('The settings table is not available.');
        }

        $payload = ['value' => $value];

        if (self::settingsColumnAvailable('updated_at')) {
            $payload['updated_at'] = now();
        }

        $updated = Setting::query()
            ->where('key', $key)
            ->update($payload);

        if ($updated > 0) {
            return true;
        }

        Setting::query()->create([
            'key' => $key,
            'value' => $value,
        ]);

        return true;
    }

    private static function settingsTableAvailable(): bool
    {
        static $available = null;

        if ($available !== null) {
            return $available;
        }

        try {
            $available = Schema::hasTable('settings');
        } catch (\Throwable) {
            $available = false;
        }

        return $available;
    }

    private static function settingsColumnAvailable(string $column): bool
    {
        static $cache = [];

        if (array_key_exists($column, $cache)) {
            return $cache[$column];
        }

        try {
            $cache[$column] = Schema::hasColumn('settings', $column);
        } catch (\Throwable) {
            $cache[$column] = false;
        }

        return $cache[$column];
    }

    /**
     * @param string|array<int, mixed> $refs
     */
    private static function normalizeRefsToString(string|array $refs): string
    {
        return implode(', ', self::normalizeRefsToArray($refs));
    }

    /**
     * @param string|array<int, mixed> $refs
     * @return array<int, string>
     */
    private static function normalizeRefsToArray(string|array $refs): array
    {
        $items = is_array($refs) ? $refs : explode(',', $refs);
        $normalized = [];

        foreach ($items as $ref) {
            $ref = trim((string) $ref);
            if ($ref === '' || in_array($ref, $normalized, true)) {
                continue;
            }

            $normalized[] = $ref;
        }

        return $normalized;
    }
}
