<?php

declare(strict_types=1);

namespace app\controllers;

use yii;
use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\Response;
use yii\web\Cookie;
use yii\web\UploadedFile;

use app\models\Account;
use app\models\Supply;
use app\models\SupplyGroup;
use app\models\Search;
use app\models\Notification;
use app\models\Settings;
use app\models\Dellin;
use app\models\SettingsEdgeSettings;
use app\models\Terminal;

use Dadata\DadataClient as Dadata;

use Throwable;

class ProfileController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::class,
                'rules' => [
                    [
                        'allow' => true,
                        'actions' => [
                            'geolocation-init'
                        ]
                    ],
                    [
                        'allow' => true,
                        'roles' => ['@'],
                        'actions' => [
                            'index',
                            'supplies',
                            'import',
                            'monitoring',
                            'readGroups',
                            'geolocation-write',
                            'panel-suppliers-requests-search'
                        ]
                    ],
                    [
                        'allow' => true,
                        'actions' => ['panel', 'panel-notification-write'],
                        'matchCallback' => function ($rule, $action): bool {
                            if (
                                !yii::$app->user->isGuest
                                && (yii::$app->user->identity->type === 'administrator'
                                    || yii::$app->user->identity->type === 'moderator')
                            ) {
                                return true;
                            }

                            return false;
                        }
                    ],
                    [
                        'allow' => false,
                        'roles' => ['?'],
                        'denyCallback' => [$this, 'accessDenied']
                    ]
                ]
            ]
        ];
    }

    public function accessDenied()
    {
        // Инициализация
        $cookies = yii::$app->response->cookies;

        // Запись cookie с редиректом, который выполнится после авторизации
        $cookies->add(new Cookie([
            'name' => 'redirect',
            'value' => yii::$app->request->pathInfo
        ]));

        if (Yii::$app->request->isPost) {
            // POST-запрос

            // Настройка
            Yii::$app->response->format = Response::FORMAT_JSON;

            // Генерация ответа
            Yii::$app->response->content = json_encode([
                'main' => $this->renderPartial('/account/index'),
                'redirect' => '/profile',
                '_csrf' => Yii::$app->request->getCsrfToken()
            ]);
        } else if (Yii::$app->request->isGet) {
            // GET-запрос

            $this->redirect('/authentication');
        }
    }

    /**
     * Страница с настройками аккаунта
     */
    public function actionIndex(): string|array
    {
        // Инициализация
        $model = yii::$app->user->identity;
        $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
        $sidebar = $this->renderPartial('sidebar');

        // Обработка настроек аккаунта
        if ($vars = yii::$app->request->post('Account') ?? yii::$app->request->get('Account')) {
            // Обнаружены входные параметры

            if (isset($vars['opts'])) {
                // Переданы параметры

                // Инициализация
                is_array($model->opts) || $model->opts = [];

                // Запись в буфер
                $model->opts = array_merge($model->opts, $vars['opts']);

                // Запись в базу данных
                $model->update();
            } else {
                /**
                 * @todo Написать обработчик ошибок
                 */
            }
        }

        // Инициализация
        $delivery_from_terminal_list = $model->genListTerminalsFrom();
        $delivery_to_terminal_list = $model->genListTerminalsTo();
        $import_oem_list = $model->genListOem(Supply::searchByAccount(select: 'supply.onec["ЗначенияСвойств"]'));
        $array_unshift_in_start = function (array &$array, string|int $key, mixed $value) {
            $array = array_reverse($array, true);
            $array[$key] = $value;
            return $array = array_reverse($array, true);
        };
        $array_write_default_value = function (array &$array, string $key = 'Выберите', string $value = 'Выберите') use ($array_unshift_in_start) {
            if (isset($array[$key])) {
                // Смещение или ассоциация найдена

                // Деинициализация
                unset($array[$key]);

                // Инициализация
                $array_unshift_in_start($array, $key, $value);
            }
        };

        // Сортировка по алфавиту
        asort($delivery_from_terminal_list);
        asort($delivery_to_terminal_list);
        asort($import_oem_list);

        // Перемещение в начало массива значения "Выберите"
        $array_write_default_value($delivery_from_terminal_list);
        $array_write_default_value($delivery_to_terminal_list);
        $array_write_default_value($import_oem_list);

        if (yii::$app->request->isPost) {
            // POST-запрос

            yii::$app->response->format = Response::FORMAT_JSON;

            return [
                'main' => $this->renderPartial('index', compact(
                    'model',
                    'sidebar',
                    'delivery_from_terminal_list',
                    'delivery_to_terminal_list',
                    'import_oem_list',
                    'panel'
                )),
                'redirect' => '/profile',
                '_csrf' => yii::$app->request->getCsrfToken()
            ];
        }

        return $this->render('index', compact(
            'model',
            'sidebar',
            'delivery_from_terminal_list',
            'delivery_to_terminal_list',
            'import_oem_list',
            'panel'
        ));
    }

    /**
     * Страница панели управления для доверенных пользователей
     */
    public function actionPanel(): string|array
    {
        // Инициализация
        $model_notifications = null;
        $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
        $sidebar = $this->renderPartial('sidebar');

        if (!$model_settings = Settings::readLast()) {
            $model_settings = new Settings();

            if (!$model_settings->save()) {
                $this->redirect('/');
            }
        }

        if ($vars = yii::$app->request->post('Notification') ?? yii::$app->request->get('Notification')) {
            // Обнаружены входные параметры из раздела "Уведомления"

            // Реинициализация с новыми параметрами
            $model_notifications = new Notification($vars);

            // Запись уведомления и отправка (запись ребра до аккаунта)
            $model_notifications->write();
        } else if ($vars = yii::$app->request->post('Settings') ?? yii::$app->request->get('Settings')) {
            // Обнаружены входные параметры из раздела "Настройки"

            if ($to = new Settings($vars)) {
                // Настройки инициализированы

                // Отправка
                if ($to->save()) {
                    // Сохранено в базе данных

                    // Буфер
                    $from = $model_settings;

                    // Реинициализация (для представления)
                    $model_settings = $to;

                    if ($from) {
                        // Найдена старая версия настроек

                        // Запись ребра: НАСТРОЙКИ (старые) -> НАСТРОЙКИ (новые)
                        SettingsEdgeSettings::write($from->readId(), $to->readId(), 'update');
                    }
                } else {
                    // Не сохранено в базе данных

                    // Запись ошибок
                    $model_settings->addErrors($to->getErrors());
                }
            }
        }

        // Деинициализация
        unset($vars);

        // Раздел "Поставщики"

        if (yii::$app->request->isPost) {
            // AJAX-POST-запрос

            yii::$app->response->format = Response::FORMAT_JSON;

            return [
                'main' => $this->renderPartial('panel', compact(
                    'model_notifications',
                    'model_settings',
                    'sidebar',
                    'panel'
                )),
                'redirect' => '/profile/panel',
                '_csrf' => yii::$app->request->getCsrfToken()
            ];
        }

        return $this->render('panel', compact(
            'model_notifications',
            'model_settings',
            'sidebar',
            'panel'
        ));
    }

    /**
     * Страница мониторинга
     */
    public function actionMonitoring(Account|int|null $account = null): string|array
    {
        // Инициализация
        $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
        $sidebar = $this->renderPartial('sidebar');

        if (is_null($account)) {
            // Данные аккаунта не переданы

            if (yii::$app->user->isGuest) {
                // Аккаунт не аутентифицирован

                return [];
            } else {
                // Аккаунт аутентифицирован

                // Инициализация
                $account = yii::$app->user->identity;
            }
        } else {
            if (is_int($account)) {
                // Передан идентификатор (_key) аккаунта (подразумевается)

                // Инициализация (поиск в базе данных)
                if (!$account = Account::searchById(Account::collectionName() . "/$account")) {
                    // Не удалось инициализировать аккаунт

                    return [];
                }
            }
        }

        // Инициализация номера страницы
        $page_search_history = (yii::$app->request->post('search') ?? yii::$app->request->get('search')) - 1;

        if ($page_search_history <= 0) {
            // Вышли за границу поиска перед первой страницей

            // Инициализация
            $page_search_history = 0;
        }

        // Инициализация количества строк на одной странице
        $rows_amount = 10;

        $search_history = Search::SearchByEdge(
            from: 'account',
            to: 'search',
            subquery_where: [
                [
                    'account._id' => yii::$app->user->id
                ]
            ],
            foreach: ['edge' => 'account_edge_search'],
            where: 'edge._to == search._id',
            limit: $rows_amount,
            offset: $offset = ((int) $page_search_history ?? 0) * $rows_amount
        );

        // Проверка результатов
        monitoring_result_check:

        if (count($search_history) === 0 && $offset !== 0) {
            // Вышли за границу поиска после последней страницы

            // Реинициализация (вычитание для идентичного конечного результата)
            --$page_search_history;

            $search_history = Search::SearchByEdge(
                from: 'account',
                to: 'search',
                subquery_where: [
                    [
                        'account._id' => yii::$app->user->id
                    ]
                ],
                foreach: ['edge' => 'account_edge_search'],
                where: 'edge._to == search._id',
                limit: $rows_amount,
                offset: $offset = ((int) $page_search_history ?? 0) * $rows_amount
            );

            // Повторная проверка
            goto monitoring_result_check;
        }

        if (yii::$app->request->isPost) {
            // AJAX-POST-запрос

            yii::$app->response->format = Response::FORMAT_JSON;

            return [
                'main' => $this->renderPartial('monitoring', compact(
                    'sidebar',
                    'search_history',
                    'page_search_history',
                    'panel'
                )),
                'search' => $page_search_history + 1,
                'redirect' => '/profile/monitoring',
                '_csrf' => yii::$app->request->getCsrfToken()
            ];
        }

        return $this->render('monitoring', compact(
            'sidebar',
            'search_history',
            'page_search_history',
            'panel',
            'account'
        ));
    }

    /**
     * Страница поставок
     */
    public function actionSupplies(): string|array
    {
        // Инициализация
        $supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply'));
        $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
        $sidebar = $this->renderPartial('sidebar');
        $groups = self::readGroups();


        if (yii::$app->request->isPost) {
            // POST-запрос

            yii::$app->response->format = Response::FORMAT_JSON;

            return [
                'main' => $this->renderPartial('supplies', compact(
                    'supply',
                    'groups',
                    'sidebar',
                    'panel'
                )),
                'redirect' => '/profile/supplies',
                '_csrf' => yii::$app->request->getCsrfToken()
            ];
        }

        return $this->render('supplies', compact(
            'supply',
            'groups',
            'sidebar',
            'panel'
        ));
    }

    /**
     * Импорт поставок
     *
     * На данный момент только из Excel-таблицы
     */
    public function actionImport()
    {
        // Инициализация
        $supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply'));
        $supply->scenario = $supply::SCENARIO_IMPORT_EXCEL;
        $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
        $sidebar = $this->renderPartial('sidebar');
        $groups = self::readGroups();

        if (yii::$app->request->isPost) {
            // AJAX-POST-запрос

            // Настройка ответа
            yii::$app->response->format = Response::FORMAT_JSON;

            // Инициализация файлов
            $supply->file_excel_1 = UploadedFile::getInstance($supply, 'file_excel_1');
            $supply->file_excel_2 = UploadedFile::getInstance($supply, 'file_excel_2');
            $supply->file_excel_3 = UploadedFile::getInstance($supply, 'file_excel_3');

            if ($supply->importExcel()) {
                return [
                    'main' => $this->renderPartial('supplies', compact(
                        'supply',
                        'groups',
                        'sidebar',
                        'panel'
                    )),
                    '_csrf' => yii::$app->request->getCsrfToken()
                ];
            }

            yii::$app->response->statusCode = 409;
        }

        return $this->render('supplies', compact(
            'supply',
            'groups',
            'sidebar',
            'panel'
        ));
    }

    public static function readGroups()
    {
        // Инициализация
        $groups = [];

        foreach (SupplyGroup::read() as $group) {
            // Перебор всех групп

            // Генерация [КЛЮЧ => ИМЯ]
            $groups[$group->_key] = $group->name;
        }

        return $groups;
    }

    /**
     * Инициализация данных о геолокации
     *
     * @param string|null $account
     *
     * @return array|bool JSON, в случае успеха
     */
    public function actionGeolocationInit(): array|bool
    {
        if (Yii::$app->request->isPost) {
            // POST-запрос

            // Настройка ответа
            yii::$app->response->format = Response::FORMAT_JSON;

            // Инициализация аккаунта
            if (yii::$app->user->isGuest) {
                // Аккаунт не аутентифицирован

                return false;
            } else {
                // Аккаунт аутентифицирован

                // Инициализация
                $account = yii::$app->user->identity;
            }

            // Настройка ответа
            yii::$app->response->format = Response::FORMAT_JSON;

            if (false !== $return = self::geolocationCheck($account)) {
                // Удалось сгенерировать данные для возврата

                return $return;
            }

            return [
                'geolocation' => false,
                '_csrf' => yii::$app->request->getCsrfToken()
            ];
        }

        return false;
    }

    /**
     * Проверка наличия записи о геолокации
     *
     * @param Account|string|null $account
     *
     * @return array|bool JSON, в случае успеха
     *
     * @todo
     * 1. 👁_👁
     */
    public static function geolocationCheck(Account|int|null $account = null): array|bool
    {
        if (is_null($account)) {
            // Данные аккаунта не переданы

            if (yii::$app->user->isGuest) {
                // Аккаунт не аутентифицирован

                return false;
            } else {
                // Аккаунт аутентифицирован

                // Инициализация
                $account = yii::$app->user->identity;
            }
        } else {
            if (is_int($account)) {
                // Передан идентификатор (_key) аккаунта (подразумевается)

                // Инициализация (поиск в базе данных)
                if (!$account = Account::searchById(Account::collectionName() . "/$account")) {
                    // Не удалось инициализировать аккаунт

                    return false;
                }
            }
        }

        if (isset($account->geol)) {
            // Данные о геолокации найдены

            // Генерация ответа
            return [
                'geolocation' => true,
                '_csrf' => yii::$app->request->getCsrfToken()
            ];
        }

        return [
            'geolocation' => false,
            '_csrf' => yii::$app->request->getCsrfToken()
        ];
    }

    /**
     * Запись данных о геолокации по ширине и долготе
     *
     * @param string|null $account Аккаунт
     *
     * @return array|bool JSON, в случае успеха
     *
     * @todo
     * 1. Избавиться от второго запроса к DaData ($dadata->clean("address", $account->city);)
     */
    public function actionGeolocationWrite(): array|bool
    {
        if (Yii::$app->request->isPost) {
            // POST-запрос

            // Настройка ответа
            yii::$app->response->format = Response::FORMAT_JSON;

            // Инициализация аккаунта
            if (yii::$app->user->isGuest) {
                // Аккаунт не аутентифицирован

                // Запись кода ответа
                yii::$app->response->statusCode = 401;

                return false;
            } else {
                // Аккаунт аутентифицирован

                // Инициализация
                $account = yii::$app->user->identity;
            }

            // Настройка ответа
            yii::$app->response->format = Response::FORMAT_JSON;

            // Инициализация данных яндекса
            $yandex = yii::$app->request->post('yandex') ?? yii::$app->request->get('yandex');

            // Инициализация широты (среднее значение из общего периметра)
            $latitude = ($yandex['coordinates'][0][0] + $yandex['coordinates'][1][0]) / 2;

            // Инициализация долготы (среднее значение из общего периметра)
            $longitude = ($yandex['coordinates'][0][1] + $yandex['coordinates'][1][1]) / 2;

            if (empty($latitude) || empty($longitude)) {
                // Широта или долгота не передана

                // Запись кода ответа
                yii::$app->response->statusCode = 500;

                return false;
            }

            try {
                // Инициализация данных геолокации
                $dadata = new Dadata(yii::$app->params['dadata']['key'], yii::$app->params['dadata']['secret']);

                // Запись в буфер данных о геолокации
                $account->geol = $dadata->geolocate("address", $latitude, $longitude)[0];

                // Запись в буфер полученного города
                $account->city = $account->geol['data']['city'];

                // Запись в буфер новых данных (непонятно почему, но только вторым запросом можно получать часовую зону)
                $account->geol = [
                    'data' => [
                        'timezone' => $dadata->clean("address", $account->city)['data']['timezone']
                    ]
                ] + $account->geol;

                // Запись в буфер часовой зоны
                $account->zone = $account->geol['data']['timezone'];

                if ($account->update() < 1) {
                    // Не удалось записать данные

                    // Запись кода ответа
                    yii::$app->response->statusCode = 500;

                    return false;
                };
            } catch (Throwable $t) {
                // Запись кода ответа
                yii::$app->response->statusCode = 500;

                return false;
            }

            return self::syncGeolocationWithDellin($account);
        }
    }

    /**
     * Синхронизация данных о геолокации с данными терминалов ДеловыеЛинии
     *
     * Присваивает терминал Скиллпартс относительно города пользователя
     *
     * @param string|null $account Аккаунт
     *
     * @return bool Статус выполнения
     *
     * @todo Переделать запись страны как "Россия" в определение по геолокации через DaData
     */
    public static function syncGeolocationWithDellin(Account|int|null $account = null): bool
    {
        // Синхронизация с базой данных (таблица с ДеловыеЛинии)
        if (isset($account->geol['data']) && $dellin = Dellin::searchByCityKladr(str_pad($account->geol['data']['city_kladr_id'], 25, '0000000000000000000000'))) {
            // Удалось найти город с терминалами ДеловыеЛинии

            foreach ($dellin['data']['terminals']['terminal'] as $dellin) {
                // Перебор терминалов ДеловыеЛинии

                try {
                    // Поиск терминала SkillParts
                    if ($skillparts_buffer = Terminal::searchByDellinTerminalId($dellin['id'], 1)) {
                        // Удалось найти терминал SkillParts

                        // Запись данных терминала SkillParts
                        $skillparts = $skillparts_buffer[0]->dell;
                    } else {
                        // Не удалось найти терминал SkillParts

                        if ($dellin['default'] && !$dellin['isOffice'] && $dellin['receiveCargo'] && $dellin['giveoutCargo']) {
                            // Терминал ДеловыеЛинии помечен как основной, не является офисом, принимает и отправляет посылки

                            // Инициализация данных геолокации
                            $dadata = new Dadata(yii::$app->params['dadata']['key'], yii::$app->params['dadata']['secret']);

                            // Запись в буфер данных о геолокации
                            $geolocation = $dadata->geolocate("address", $dellin['latitude'], $dellin['longitude'])[0];

                            // Инициализация терминала SkillParts
                            $terminal = new Terminal;

                            // Настройка
                            $terminal->cntr = $geolocation['data']['country'];
                            $terminal->city = $geolocation['data']['city'];
                            $terminal->comm = 'Недоступен';
                            $terminal->dell = $dellin['id'];

                            // Запись в базу данных
                            if ($terminal->save()) {
                                // Удалось записать терминал в базу данных

                                // Запись данных терминала SkillParts
                                $skillparts = $terminal->dell;
                            }
                        }
                    }
                } catch (Throwable $t) {
                    continue;
                }
            }

            if (empty($skillparts)) {
                // Не удалось инициализировать терминал SkillParts

                return false;
            } else {
                // Удалось инициализировать терминал SkillParts

                // Запись связанного с городом терминала в настройки пользователя
                // !!! Берётся первый попавшийся терминал
                $account->opts = [
                    'delivery_to_terminal' => $skillparts
                ] + ($account->opts ?? []);

                // Отправка данных из буфера в базу данных
                return $account->update() >= 1;
            }
        }

        return false;
    }

    public function actionPanelSuppliersRequestsSearch()
    {
        if (Yii::$app->request->isPost) {
            // POST-запрос

            if (Account::isAdmin() || Account::isModer()) {
                // Доступ разрешен

                // Инициализация буфера ответа
                $response = [];

                // Поиск заявок на регистрацию
                $suppliers =  Account::searchSuppliersRequests();

                foreach ($suppliers as $account) {
                    // Перебор заявок

                    // Поиск данных о заявке
                    foreach ($account->jrnl as $jrnl) {
                        // Перебор журнала

                        if ($jrnl['action'] === 'create') {
                            // Запись о создании найдена

                            // Запись даты регистрации
                            $registration_date = $jrnl['date'];
                        }

                        if ($jrnl['action'] === 'request to become a supplier') {
                            // Запись о подаче заявку на становление поставщиком найдена

                            // Запись даты отправки заявки
                            $supplier_date = $jrnl['date'];

                            // Запись данных о заявке
                            $supplier_data = $jrnl['data'][0];
                        }
                    }

                    if (empty($supplier_data) || empty($supplier_date) || empty($registration_date)) {
                        // Данные из журнала не найдены

                        // Пропуск
                        continue;
                    }

                    // if (!isset($account->indx) || empty($account->indx)) {
                    if (empty($account->indx)) {
                        // Индекс не инициализирован

                        // Инициализация индекса
                        Account::generateIndexes([$account]);
                    }

                    // if (!isset($account->pswd) || empty($account->pswd)) {
                    if (empty($account->pswd)) {
                        // Пароль не инициализирован

                        // Инициализация пароля
                        $account->passwordInit();
                    }

                    // Запись в буфер ответа
                    $response[] = [
                        '_key' => $account->_key,
                        'name' => $account->name,
                        'mail' => $account->mail,
                        'boss' => $account->boss,
                        'desc' => $account->desc,
                        'simc' => $account->simc,
                        'simc' => $account->simc,
                        'comp' => $account->comp,
                        'cntc' => $account->cntc,
                        'taxn' => $account->taxn,
                        'cntg' => $account->cntg,
                        'fadd' => $account->fadd,
                        'ladd' => $account->ladd,
                        'chck' => $account->chck,
                        'indx' => $account->indx,
                        'vrfy' => Account::validateVrfy($account->vrfy),
                        'data' => $supplier_data,
                        'date' => [
                            'regs' => $registration_date,
                            'supl' => $supplier_date
                        ]
                    ];
                }

                // Настройка заголовков ответа
                Yii::$app->response->format = Response::FORMAT_JSON;

                return [
                    'suppliers' => $response,
                    '_csrf' => yii::$app->request->getCsrfToken()
                ];
            } else {
                // Запись кода ответа
                yii::$app->response->statusCode = 401;

                return;
            }
        }

        // Запись кода ответа
        yii::$app->response->statusCode = 500;

        return;
    }
}
