<?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 yii\web\User;

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 Exception;
use moonland\phpexcel\Excel;
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'
                        ]
                    ],
                    [
                        'allow' => true,
                        'actions' => ['panel', 'panel-notification-write'],
                        'matchCallback' => function ($rule, $action): bool {
                            if (
                                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(): string|array
    {
        // Инициализация
        $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
        $sidebar = $this->renderPartial('sidebar');

        // Инициализация номера страницы
        $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'
        ));
    }

    /**
     * Страница поставок
     */
    public function actionSupplies(): string|array
    {
        // Инициализация
        $model = 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(
                    'model',
                    'groups',
                    'sidebar',
                    'panel'
                )),
                'redirect' => '/profile/supplies',
                '_csrf' => yii::$app->request->getCsrfToken()
            ];
        }

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

    /**
     * Импорт поставок
     *
     * На данный момент только из Excel-таблицы
     */
    public function actionImport()
    {
        // Инициализация
        $model = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply'));
        $model->scenario = $model::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;

            $model->file_excel = UploadedFile::getInstances($model, 'file_excel');

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

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

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

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

        foreach (SupplyGroup::readAll() 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 (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 (yii::$app->user->isGuest) {
                // Аккаунт не аутентифицирован

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

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

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

            // Инициализация IP-адреса
            $ip = yii::$app->request->userIp === 'localhost' || yii::$app->request->userIp === '127.0.0.1' ? '46.226.227.20' : yii::$app->request->userIp;

            // Проверка записи геолокации
            if (isset($account->geol)) {
                // Удалось найти данные геолокации
            } else {
                // Не удалось найти данные геолокации

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

                    // Запись в буфер данных о геолокации
                    $account->geol = $dadata->iplocate($ip);
                } catch (Throwable $t) {
                    return false;
                }

                // Запись в буфер данных о типе геолокации
                $account->geol = ['type' => 'ip'] + $account->geol;

                self::syncGeolocationWithDellin($account);
            }

            return self::geolocationAccuracyCheck($account);
        }

        return false;
    }

    /**
     * Генерация ответа по данным геолокации
     *
     * Проверка точности и наличия данных о геолокации
     *
     * @param Account|string|null $account
     *
     * @return array|bool JSON, в случае успеха
     */
    public static function geolocationAccuracyCheck(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 [
                'requestGps' => match ($account->geol['type'] ?? null) {
                    'gps' => false,
                    default => true
                },
                '_csrf' => yii::$app->request->getCsrfToken()
            ];
        }

        return false;
    }

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

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

            // 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 (yii::$app->user->isGuest) {
                // Аккаунт не аутентифицирован

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

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

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

            // Инициализация широты
            $latitude = yii::$app->request->post('latitude') ?? yii::$app->request->get('latitude');

            // Инициализация долготы
            $longitude = yii::$app->request->post('longitude') ?? yii::$app->request->get('longitude');

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

                return false;
            }

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

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

            // Запись в буфер данных о типе геолокации
            $account->geol = ['type' => 'gps'] + $account->geol;

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

    /**
     * Синхронизация данных о геолокации с данными терминалов ДеловыеЛинии
     *
     * Присваивает терминал Скиллпартс относительно города пользователя
     *
     * @param string|null $account Аккаунт
     *
     * @return bool Статус выполнения
     *
     * @todo Переделать запись страны как "Россия" в определение по геолокации через DaData
     */
    public static function syncGeolocationWithDellin(Account|int|null $account = null): 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 ($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;
    }
}
