<?php

namespace App;

use App\Facades\Feature;
use App\Jobs\RunScheduledSyncTask;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config;
use Mtc\MercuryDataModels\Finance\Contracts\BatchRequestsQuotes;
use Mtc\MercuryDataModels\Finance\FinanceServiceHelper;
use Mtc\MercuryDataModels\Finance\Jobs\RunQuoteRequest;
use Mtc\MercuryDataModels\Setting;

class SettingRepository
{
    use DispatchesJobs;

    /**
     * List of loaded settings
     *
     * @var mixed[]
     */
    private $loaded_settings = [];

    /**
     * @param array $setting
     * @return Setting|Model
     */
    public function make(array $setting, bool $withValue = false): Setting
    {
        Cache::forget(tenant('id') . '-settings');

        $order = $setting['order'] ?? 99;
        if ($setting['name'] === 'Enabled' && $setting['type'] === 'boolean') {
            $order = 0;
        } elseif ($order === 0) {
            $order = 1;
        }

        $data = [
            'tab' => $setting['tab'],
            'section' => $setting['section'],
            'group' => $setting['group'],
            'name' => $setting['name'],
            'type' => $setting['type'],
            'order' => $order,
            'choices' => $setting['choices'] ?? null,
            'source' => $setting['source'] ?? null,
            'description' => $setting['description'] ?? null,
            'min_tier' => $setting['min_tier'] ?? null,
            'superadmin' => $setting['superadmin'] ?? null,
            'feature' => $setting['feature'] ?? null,
            'validation_rules' => $setting['validation_rules'] ?? null,
        ];
        if ($withValue || !Setting::query()->where('config_key', $setting['config_key'])->exists()) {
            $data['value'] = $setting['value'] ?? null;
        }
        return Setting::query()
            ->updateOrCreate([
                'config_key' => $setting['config_key'],
            ], $data);
    }

    /**
     * @param string $key
     * @return boolean
     */
    public function exists(string $key): bool
    {
        $this->loadSettings();

        return isset($this->loaded_settings[tenant('id')][$key]);
    }

    /**
     * Get a setting
     *
     * @param string $key
     * @return mixed
     */
    public function get(string $key, $default = null)
    {
        $this->loadSettings($default);

        return $this->loaded_settings[tenant('id')][$key] ?? $default;
    }

    public function setData(string $key, $data): void
    {
        Setting::query()
            ->updateOrCreate(
                ['config_key' => $key],
                [
                    'tab' => '',
                    'section' => '',
                    'group' => '',
                    'data' => $data,
                ]
            );
    }

    public function getData(string $key)
    {
        return Setting::query()
            ->where('config_key', $key)
            ->first()
            ?->data ?? [];
    }

    /**
     * Set the value of a setting, creating it if it does not yet exist.
     *
     * Internal markers (e.g. stock-provider bookkeeping such as the AutoGrab last-processed
     * partition) are written through here without ever being registered as user-facing
     * settings, so the first write must be able to create the row. Registered settings already
     * exist, so the NOT NULL defaults below only apply to fresh internal keys and never overwrite
     * existing metadata.
     *
     * @param string $key
     * @return void
     */
    public function update(string $key, $value)
    {
        $setting = Setting::query()->firstOrNew(
            ['config_key' => $key],
            ['tab' => '', 'section' => '', 'group' => '', 'name' => $key, 'type' => ''],
        );
        $setting->value = $setting->type !== 'text' ? $value : null;
        $setting->value_text = $setting->type === 'text' ? $value : '';
        $setting->save();

        Cache::forget(tenant('id') . '-settings');
        $this->loaded_settings[tenant('id')] = Setting::query()
            ->get()
            ->keyBy('config_key')
            ->map(fn ($setting) => $setting->value);
    }

    /**
     * @param string $key
     * @return void
     */
    public function delete(string $key): void
    {
        $setting = Setting::query()->where('config_key', $key)->first();

        if ($setting) {
            $setting->delete();
            Cache::forget(tenant('id') . '-settings');
        }
    }

    public function tabOrder(string $tab)
    {
        $index = array_search($tab, [
            'Site',
            'CRM',
            'Automotive',
            'Domains',
            'Users',
        ]);
        if ($index === false) {
            $index = 99;
        }
        return $index;
    }

    public function sectionOrder(string $tab)
    {
        $index = array_search($tab, [
            'general',
            'filter',
            'dms-vehicle-sync',
            'sales',
            'ecommerce',
            'content',
            'tracking',
            'mail',
            'social-media',
            'live-chat',
            'newsletter',
            'integrations',
            'finance',
            'specification-feature-synchronization',
            'sales-channels-feeds',
            'autotrader',
            'image-synchronization',
            'valuation',
        ]);
        if ($index === false) {
            $index = 99;
        }
        return $index;
    }

    public function groupOrder(string $tab)
    {
        $index = array_search($tab, [
            'General',
            'CRM',
            'Automotive',
            'Domains',
            'Users',
        ]);
        if ($index === false) {
            $index = 99;
        }
        return $index;
    }

    public function verify(string $config_key): bool|Response
    {
        $result = new IntegrationRepository()->verifyIntegration($config_key);
        if ($result === true) {
            $this->update($config_key, true);
            return true;
        }

        return $result;
    }

    public function triggerAction(string $key): bool
    {
        match ($key) {
            'trigger-stock-sync' => $this->dispatch(new RunScheduledSyncTask(true)),
            'trigger-finance-refetch' => $this->resetFinance(),
            default => throw new \Exception('Action not recognized'),
        };
        return true;
    }

    public function orderableGroups(Collection $groupNames): array
    {
        return $groupNames->intersect(Config::get('automotive.orderable_setting_groups'))
            ->unique()
            ->values()
            ->toArray();
    }

    private function featureEnabledForTenant(?string $feature): bool
    {
        if ($feature === null) {
            return true;
        }

        return Feature::isEnabled($feature);
    }

    private function loadSettings($default = null): void
    {
        if (empty($this->loaded_settings) && tenant('id')) {
            $this->loaded_settings[tenant('id')] = Setting::query()
                ->get()
                ->keyBy('config_key')
                ->filter(fn (Setting $setting) => $this->featureEnabledForTenant($setting->feature))
                ->map(fn (Setting $setting) => $setting->value ?? $default);
        }
    }

    private function resetFinance(): void
    {
        $provider = FinanceServiceHelper::initializeForSite();
        if ($provider instanceof BatchRequestsQuotes) {
            $this->dispatch(new RunQuoteRequest($provider, true));
        }
    }
}
