<?php

/*
 * Copyright © 2020 Miguel Nogueira
 *
 *   This file is part of Raspberry Staff Manager.
 *
 *     Raspberry Staff Manager is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     Raspberry Staff Manager is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with Raspberry Staff Manager.  If not, see <https://www.gnu.org/licenses/>.
 */

namespace App\Helpers;

use App\Enums\OverridableFeatures;
use App\Enums\Overrides;
use App\Exceptions\EmptyOptionsException;
use App\Exceptions\OptionNotFoundException;
use App\Options as Option;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use LogicException;


class Options
{

    /**
     * Returns an assortment of settings found in the mentioned category
     *
     * @param  string  $category The category
     * @return Collection The settings in this category
     *
     * @throws EmptyOptionsException
     */
    public function getCategory(string $category): Collection
    {
        $options = Option::where('option_category', $category)->get();
        if ($options->isEmpty()) {
            throw new EmptyOptionsException('There are no options in category '.$category);
        }

        return $options;
    }

    public function getOption(string $option): string
    {
        $value = Cache::get($option);

        if (is_null($value)) {
            Log::debug('Option '.$option.'not found in cache, refreshing from database');
            $value = Option::where('option_name', $option)->first();
            if (is_null($value)) {
                throw new OptionNotFoundException('This option does not exist.');
            }
            Cache::put($option, $value->option_value);
            Cache::put($option.'_desc', 'Undefined description');

            return $this->modifyResponse($option, $value->option_value);
        }

        return $this->modifyResponse($option, $value);
    }

    public function isFeature(string $optionName): bool
    {
       if ($this->getCategory('app_features')->contains('option_name', $optionName)) {
           return true;
       }
       return false;
    }

    /**
     * CHeck if given option is locallu overriden
     *
     * @param string $option Option to check
     * @throws LogicException Thrown when feature is not locally overridable, e.g. not defined in the settings file
     * @return bool
     */
    public function isOverriden(string $option): bool {

        if (!array_key_exists($option, config('local-overrides.features'))) {
            throw new LogicException('Feature does not exist or is not locally overridable');
        }
        $feature = config("local-overrides.features.$option");

        return ($feature == Overrides::forceEnable->value || $feature == Overrides::killSwitch->value);
    }

    /**
     * Defines an option to store in the database.
     *
     * @param string $option Option name.
     * @param string $value Option value.
     * @param string $description Description for this, can be null
     * @param string|null $category Option category
     * @return void
     */
    public function setOption(string $option, string $value, string $description, string $category = null)
    {
        Option::create([
            'option_name' => $option,
            'option_value' => $value,
            'friendly_name' => $description,
            'option_category' => $category,
        ]);

        Cache::put($option, $value, now()->addDay());
        Cache::put($option.'_desc', $description, now()->addDay());
    }

    /**
     * Gets and deletes option
     *
     * @param string $option Option to pull
     * @return array
     */
    public function pullOption($option): array
    {
        $oldOption = Option::where('option_name', $option)->first();
        Option::find($oldOption->id)->delete();

        // putMany is overkill here
        return [
            Cache::pull($option),
            Cache::pull($option.'_desc'),
        ];
    }

    /**
     * Updates an option
     *
     * @param $option
     * @param $newValue
     * @return void
     * @throws OptionNotFoundException
     */
    public function changeOption($option, $newValue)
    {
        $dbOption = Option::where('option_name', $option);

        if ($dbOption->first()) {
            $dbOptionInstance = Option::find($dbOption->first()->id);
            Cache::forget($option);

            Log::debug('Changing db configuration option', [
                'old_value' => $dbOptionInstance->option_value,
                'new_value' => $newValue,
            ]);

            $dbOptionInstance->option_value = $newValue;
            $dbOptionInstance->save();

            Log::debug('New db configuration option saved',
                [
                    'option' => $dbOptionInstance->option_value,
                ]);

            Cache::put('option_name', $newValue, now()->addDay());
        } else {
            throw new OptionNotFoundException('This option does not exist.');
        }
    }

    /**
     * Check if option exists
     *
     * @param string $option
     * @return bool
     */
    public function optionExists(string $option): bool
    {
        $dbOption = Option::where('option_name', $option)->first();
        $locallyCachedOption = Cache::get($option);

        return ! is_null($dbOption) || ! is_null($locallyCachedOption);
    }


    /**
     * Get the actual status for the override in question
     *
     * @param string $option
     * @return bool|Overrides
     */
    public function getOverride(string $option): bool|string {
        return config("local-overrides.features.$option");
    }


    /**
     * Modifies the outgoing option value according to its override value
     *
     * @param string $option
     * @param int|string $value Setting value
     * @return int|string Modified setting value accordidng to business rules
     */
    private function modifyResponse(string $option, int|string $value): int|string
    {
        // TODO: This method should handle bools (feature on/off), we need to move this to getOption, but only after its been refactored. (this is a quick fix)
        if (!$this->isFeature($option)) {
            return $value;
        }
        $modifiedValue = $value;

        try {

            if ($this->isOverriden($option) && $this->getOverride($option) == Overrides::forceEnable->value) {
                $modifiedValue = Overrides::forceEnable->value;
            }

            if ($this->isOverriden($option) && $this->getOverride($option) == Overrides::killSwitch->value) {
                $modifiedValue = 0;
            }

        } catch (LogicException $exception) {
            Log::debug('Illegal attempt to modify setting value: not overridable; not modifying', [
                'msg' => $exception->getMessage()
            ]);
            return $value;
        }

        return $modifiedValue;
    }

}
