<?php

namespace App\Services;

use App\Models\Setting;
use Illuminate\Support\Arr;

class SettingsService
{
    private const CONSULTATION_KEYS = [
        'slot_interval_minutes' => 'consultations.slot_interval_minutes',
        'availability_window_days' => 'consultations.availability_window_days',
        'minimum_action_lead_hours' => 'consultations.minimum_action_lead_hours',
        'pricing.text' => 'consultations.pricing.text',
        'pricing.audio' => 'consultations.pricing.audio',
        'pricing.video' => 'consultations.pricing.video',
    ];

    private const APP_KEYS = [
        'name' => 'app.name',
        'timezone' => 'app.timezone',
    ];

    public function get(string $key): mixed
    {
        $stored = Setting::getValue($key);
        if ($stored !== null && $stored !== '') {
            return $this->castValue($key, $stored);
        }

        return config($key);
    }

    public function set(string $key, mixed $value): void
    {
        Setting::setValue($key, $value === null ? null : (string) $value);
    }

    public function getAppName(): string
    {
        return (string) $this->get('app.name');
    }

    public function getAppTimezone(): string
    {
        return (string) $this->get('app.timezone');
    }

    public function getConsultationSlotIntervalMinutes(): int
    {
        $v = (int) $this->get('consultations.slot_interval_minutes');

        return max(15, min($v ?: 60, 120));
    }

    public function getConsultationAvailabilityWindowDays(): int
    {
        $v = (int) $this->get('consultations.availability_window_days');

        return max(1, min($v ?: 14, 30));
    }

    public function getConsultationMinimumActionLeadHours(): int
    {
        $v = (int) $this->get('consultations.minimum_action_lead_hours');

        return max(1, min($v ?: 2, 72));
    }

    /**
     * @return array{text: float, audio: float, video: float}
     */
    public function getConsultationPricing(): array
    {
        return [
            'text' => (float) $this->get('consultations.pricing.text'),
            'audio' => (float) $this->get('consultations.pricing.audio'),
            'video' => (float) $this->get('consultations.pricing.video'),
        ];
    }

    /**
     * Return all settings for admin (effective values: DB override or config default).
     */
    public function getForAdmin(): array
    {
        return [
            'app' => [
                'name' => $this->getAppName(),
                'env' => config('app.env'),
                'timezone' => $this->getAppTimezone(),
            ],
            'consultations' => [
                'slot_interval_minutes' => $this->getConsultationSlotIntervalMinutes(),
                'availability_window_days' => $this->getConsultationAvailabilityWindowDays(),
                'minimum_action_lead_hours' => $this->getConsultationMinimumActionLeadHours(),
                'pricing' => $this->getConsultationPricing(),
            ],
        ];
    }

    /**
     * Update settings from admin payload. Only allowed keys are written.
     * Supports both nested (app.name) and flat (app.name as key) validated payloads via Arr::get/Arr::has.
     *
     * @param  array<string, mixed>  $payload
     * @param  \App\Models\User|null  $user  User making the change (for audit).
     */
    public function updateFromAdmin(array $payload, ?\App\Models\User $user = null): void
    {
        $updates = [
            'app.name' => fn () => (string) Arr::get($payload, 'app.name', ''),
            'app.timezone' => fn () => (string) Arr::get($payload, 'app.timezone', ''),
            'consultations.slot_interval_minutes' => fn () => max(15, min((int) Arr::get($payload, 'consultations.slot_interval_minutes', 60), 120)),
            'consultations.availability_window_days' => fn () => max(1, min((int) Arr::get($payload, 'consultations.availability_window_days', 14), 30)),
            'consultations.minimum_action_lead_hours' => fn () => max(1, min((int) Arr::get($payload, 'consultations.minimum_action_lead_hours', 2), 72)),
            'consultations.pricing.text' => fn () => (float) Arr::get($payload, 'consultations.pricing.text', 0),
            'consultations.pricing.audio' => fn () => (float) Arr::get($payload, 'consultations.pricing.audio', 0),
            'consultations.pricing.video' => fn () => (float) Arr::get($payload, 'consultations.pricing.video', 0),
        ];

        foreach ($updates as $key => $getValue) {
            if (! Arr::has($payload, $key)) {
                continue;
            }
            $newValue = $getValue();
            if (in_array($key, ['app.name', 'app.timezone'], true)) {
                $newValue = $newValue === '' ? null : (string) $newValue;
            } else {
                $newValue = $newValue === null ? null : (string) $newValue;
            }
            $oldValue = Setting::getValue($key);
            $this->set($key, $newValue);
            if ($user) {
                $this->logAudit($user->id, $key, $oldValue, $newValue);
            }
        }
    }

    private function logAudit(int $userId, string $key, ?string $oldValue, ?string $newValue): void
    {
        \App\Models\SettingAuditLog::create([
            'user_id' => $userId,
            'key' => $key,
            'old_value' => $oldValue,
            'new_value' => $newValue,
        ]);
    }

    private function castValue(string $key, string $stored): mixed
    {
        if (str_starts_with($key, 'consultations.pricing.') || str_starts_with($key, 'consultations.slot_') || str_starts_with($key, 'consultations.availability_') || str_starts_with($key, 'consultations.minimum_')) {
            return is_numeric($stored) ? (float) $stored : $stored;
        }

        return $stored;
    }
}
