<?php

/**
 * PreferencesController.php
 * Copyright (c) 2019 james@firefly-iii.org
 *
 * This file is part of Firefly III (https://github.com/firefly-iii).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
declare(strict_types=1);

namespace FireflyIII\Http\Controllers;

use Carbon\Carbon;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Events\Preferences\UserGroupChangedPrimaryCurrency;
use FireflyIII\Events\Test\UserTestNotificationChannel;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Requests\PreferencesRequest;
use FireflyIII\Models\Account;
use FireflyIII\Models\Preference;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Singleton\PreferencesSingleton;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use JsonException;
use Safe\Exceptions\FilesystemException;

use function Safe\file_get_contents;
use function Safe\json_decode;

/**
 * Class PreferencesController.
 */
class PreferencesController extends Controller
{
    /**
     * PreferencesController constructor.
     */
    public function __construct()
    {
        parent::__construct();

        $this->middleware(
            static function ($request, $next) {
                app('view')->share('title', (string) trans('firefly.preferences'));
                app('view')->share('mainTitleIcon', 'fa-gear');

                return $next($request);
            }
        );
    }

    /**
     * Show overview of preferences.
     *
     * @return Factory|View
     *
     * @throws FireflyException
     * @throws FilesystemException
     */
    public function index(AccountRepositoryInterface $repository)
    {
        $accounts                       = $repository->getAccountsByType([AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value]);
        $isDocker                       = env('IS_DOCKER', false); // @phpstan-ignore-line
        $groupedAccounts                = [];

        /** @var Account $account */
        foreach ($accounts as $account) {
            $type                                                                        = $account->accountType->type;
            $role                                                                        = sprintf('opt_group_%s', $repository->getMetaValue($account, 'account_role'));

            if (in_array($type, [AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value], true)) {
                $role = sprintf('opt_group_l_%s', $type);
            }

            if ('opt_group_' === $role) {
                $role = 'opt_group_defaultAsset';
            }
            $groupedAccounts[(string) trans(sprintf('firefly.%s', $role))][$account->id] = $account->name;
        }
        ksort($groupedAccounts);

        /** @var array<int, int> $accountIds */
        $accountIds                     = $accounts->pluck('id')->toArray();
        $viewRange                      = app('navigation')->getViewRange(false);
        $frontpageAccountsPref          = Preferences::get('frontpageAccounts', $accountIds);
        $frontpageAccounts              = $frontpageAccountsPref->data;
        if (!is_array($frontpageAccounts)) {
            $frontpageAccounts = $accountIds;
        }
        $language                       = app('steam')->getLanguage();
        $languages                      = config('firefly.languages');
        $locale                         = Preferences::get('locale', config('firefly.default_locale', 'equal'))->data;
        $listPageSize                   = Preferences::get('listPageSize', 50)->data;
        $darkMode                       = Preferences::get('darkMode', 'browser')->data;
        $customFiscalYear               = Preferences::get('customFiscalYear', 0)->data;
        $fiscalYearStartStr             = Preferences::get('fiscalYearStart', '01-01')->data;
        $convertToPrimary               = $this->convertToPrimary;
        if (is_array($fiscalYearStartStr)) {
            $fiscalYearStartStr = '01-01';
        }
        $fiscalYearStart                = sprintf('%s-%s', Carbon::now()->format('Y'), (string) $fiscalYearStartStr);
        $tjOptionalFields               = Preferences::get('transaction_journal_optional_fields', [])->data;
        $availableDarkModes             = config('firefly.available_dark_modes');

        // notifications settings
        $slackUrl                       = Preferences::getEncrypted('slack_webhook_url', '')->data;
        $pushoverAppToken               = (string) Preferences::getEncrypted('pushover_app_token', '')->data;
        $pushoverUserToken              = (string) Preferences::getEncrypted('pushover_user_token', '')->data;
        $ntfyServer                     = Preferences::getEncrypted('ntfy_server', 'https://ntfy.sh')->data;
        $ntfyTopic                      = (string) Preferences::getEncrypted('ntfy_topic', '')->data;
        $ntfyAuth                       = '1' === Preferences::get('ntfy_auth', false)->data;
        $ntfyUser                       = Preferences::getEncrypted('ntfy_user', '')->data;
        $ntfyPass                       = (string) Preferences::getEncrypted('ntfy_pass', '')->data;
        $channels                       = config('notifications.channels');
        $forcedAvailability             = [];

        // notification preferences
        $notifications                  = [];
        foreach (config('notifications.notifications.user') as $key => $info) {
            if (true === $info['enabled']) {
                $notifications[$key]
                    = [
                        'enabled'      => true === Preferences::get(sprintf('notification_%s', $key), true)->data,
                        'configurable' => $info['configurable'],
                    ];
            }
        }
        // loop all channels to see if they are available.
        foreach ($channels as $channel => $info) {
            $forcedAvailability[$channel] = true;
        }
        $forcedAvailability['ntfy']     = '' !== $ntfyTopic;
        $forcedAvailability['pushover'] = '' !== $pushoverAppToken && '' !== $pushoverUserToken;

        ksort($languages);

        // list of locales also has "equal" which makes it equal to whatever the language is.

        try {
            $locales = json_decode(file_get_contents(resource_path(sprintf('locales/%s/locales.json', $language))), true, 512, JSON_THROW_ON_ERROR);
        } catch (JsonException $e) {
            app('log')->error($e->getMessage());
            $locales = [];
        }
        $locales                        = ['equal' => (string) trans('firefly.equal_to_language')] + $locales;
        // an important fallback is that the frontPageAccount array gets refilled automatically
        // when it turns up empty.
        if (0 === count($frontpageAccounts)) {
            $frontpageAccounts = $accountIds;
        }

        // for the demo user, the notification settings are automatically emptied.
        // this isn't really secure, but it means that the demo site has semi-secret notification settings.
        if (auth()->user()->hasRole('demo')) {
            $slackUrl          = '';
            $pushoverAppToken  = '';
            $pushoverUserToken = '';
            $ntfyServer        = '';
            $ntfyTopic         = '';
            $ntfyAuth          = false;
            $ntfyUser          = '';
            $ntfyPass          = '';
        }

        return view('preferences.index', compact(
            'language',
            'pushoverAppToken',
            'pushoverUserToken',
            'ntfyServer',
            'ntfyTopic',
            'ntfyAuth',
            'channels',
            'ntfyUser',
            'forcedAvailability',
            'ntfyPass',
            'groupedAccounts',
            'isDocker',
            'frontpageAccounts',
            'languages',
            'darkMode',
            'availableDarkModes',
            'notifications',
            'convertToPrimary',
            'slackUrl',
            'locales',
            'locale',
            'tjOptionalFields',
            'viewRange',
            'customFiscalYear',
            'listPageSize',
            'fiscalYearStart'
        ));
    }

    /**
     * Store new preferences.
     *
     * @return Redirector|RedirectResponse
     *
     * @throws FireflyException
     *
     * @SuppressWarnings("PHPMD.ExcessiveMethodLength")
     * @SuppressWarnings("PHPMD.NPathComplexity")
     */
    public function postIndex(PreferencesRequest $request)
    {
        // front page accounts
        $frontpageAccounts = [];
        if (is_array($request->get('frontpageAccounts')) && count($request->get('frontpageAccounts')) > 0) {
            foreach ($request->get('frontpageAccounts') as $id) {
                $frontpageAccounts[] = (int) $id;
            }
            Preferences::set('frontpageAccounts', $frontpageAccounts);
        }

        // extract notifications:
        $all               = $request->all();
        foreach (config('notifications.notifications.user') as $key => $info) {
            $key = sprintf('notification_%s', $key);
            if (array_key_exists($key, $all)) {
                Preferences::set($key, true);
            }
            if (!array_key_exists($key, $all)) {
                Preferences::set($key, false);
            }
        }

        // view range:
        Preferences::set('viewRange', $request->get('viewRange'));
        // forget session values:
        session()->forget('start');
        session()->forget('end');
        session()->forget('range');

        // notification settings, cannot be set by the demo user.
        if (!auth()->user()->hasRole('demo')) {

            $variables = ['slack_webhook_url', 'pushover_app_token', 'pushover_user_token', 'ntfy_server', 'ntfy_topic', 'ntfy_user', 'ntfy_pass'];
            foreach ($variables as $variable) {
                if ('' === $all[$variable]) {
                    Preferences::delete($variable);
                }
                if ('' !== $all[$variable]) {
                    Preferences::setEncrypted($variable, $all[$variable]);
                }
            }
            Preferences::set('ntfy_auth', $all['ntfy_auth'] ?? false);
        }

        // convert primary
        $convertToPrimary  = 1 === (int) $request->get('convertToPrimary');
        if ($convertToPrimary && !$this->convertToPrimary) {
            // set to true!
            Log::debug('User sets convertToPrimary to true.');
            Preferences::set('convert_to_primary', true);
            $singleton = PreferencesSingleton::getInstance();
            $singleton->resetPreferences();
            event(new UserGroupChangedPrimaryCurrency(auth()->user()->userGroup));
        }
        Preferences::set('convert_to_primary', $convertToPrimary);

        // custom fiscal year
        $customFiscalYear  = 1 === (int) $request->get('customFiscalYear');
        Preferences::set('customFiscalYear', $customFiscalYear);
        $fiscalYearString  = (string) $request->get('fiscalYearStart');
        if ('' !== $fiscalYearString) {
            $fiscalYearStart = Carbon::parse($fiscalYearString, config('app.timezone'))->format('m-d');
            Preferences::set('fiscalYearStart', $fiscalYearStart);
        }

        // save page size:
        Preferences::set('listPageSize', 50);
        $listPageSize      = (int) $request->get('listPageSize');
        if ($listPageSize > 0 && $listPageSize < 1337) {
            Preferences::set('listPageSize', $listPageSize);
        }

        // language:
        /** @var Preference $currentLang */
        $currentLang       = Preferences::get('language', 'en_US');
        $lang              = $request->get('language');
        if (array_key_exists($lang, config('firefly.languages'))) {
            Preferences::set('language', $lang);
        }
        if ($currentLang->data !== $lang) {
            // this string is untranslated on purpose.
            session()->flash('info', 'All translations are supplied by volunteers. There might be errors and mistakes. I appreciate your feedback.');
        }

        // same for locale:
        if (!auth()->user()->hasRole('demo')) {
            $locale = (string) $request->get('locale');
            $locale = '' === $locale ? null : $locale;
            Preferences::set('locale', $locale);
        }

        // optional fields for transactions:
        $setOptions        = $request->get('tj') ?? [];
        $optionalTj        = [
            'interest_date'      => array_key_exists('interest_date', $setOptions),
            'book_date'          => array_key_exists('book_date', $setOptions),
            'process_date'       => array_key_exists('process_date', $setOptions),
            'due_date'           => array_key_exists('due_date', $setOptions),
            'payment_date'       => array_key_exists('payment_date', $setOptions),
            'invoice_date'       => array_key_exists('invoice_date', $setOptions),
            'internal_reference' => array_key_exists('internal_reference', $setOptions),
            'notes'              => array_key_exists('notes', $setOptions),
            'attachments'        => array_key_exists('attachments', $setOptions),
            'external_url'       => array_key_exists('external_url', $setOptions),
            'location'           => array_key_exists('location', $setOptions),
            'links'              => array_key_exists('links', $setOptions),
        ];
        Preferences::set('transaction_journal_optional_fields', $optionalTj);

        // dark mode
        $darkMode          = $request->get('darkMode') ?? 'browser';
        if (in_array($darkMode, config('firefly.available_dark_modes'), true)) {
            Preferences::set('darkMode', $darkMode);
        }

        session()->flash('success', (string) trans('firefly.saved_preferences'));
        Preferences::mark();

        return redirect(route('preferences.index'));
    }

    public function testNotification(Request $request): mixed
    {

        $all     = $request->all();
        $channel = $all['channel'] ?? '';

        switch ($channel) {
            default:
                session()->flash('error', (string) trans('firefly.notification_test_failed', ['channel' => $channel]));

                break;

            case 'email':
            case 'slack':
            case 'pushover':
            case 'ntfy':
                /** @var User $user */
                $user = auth()->user();
                app('log')->debug(sprintf('Now in testNotification("%s") controller.', $channel));
                event(new UserTestNotificationChannel($channel, $user));
                session()->flash('success', (string) trans('firefly.notification_test_executed', ['channel' => $channel]));
        }

        return '';
    }
}
