<?php

declare(strict_types=1);

namespace app\models;

use yii;

use app\models\traits\SearchByEdge;
use app\models\Dellin as DellinModel;
use app\models\Settings;
use app\models\connection\Dellin;

use datetime;
use exception;
use throwable;

/**
 * Поиск
 *
 * @see Product Поиск по товарам
 */
class Search extends Document
{
    use SearchByEdge;

    /**
     * Имя коллекции
     */
    public static function collectionName(): string
    {
        return 'search';
    }

    /**
     * Свойства
     */
    public function attributes(): array
    {
        return array_merge(
            parent::attributes(),
            [
                'text',
                'type',
                'ipv4',
                'head'
            ]
        );
    }

    /**
     * Метки свойств
     */
    public function attributeLabels(): array
    {
        return array_merge(
            parent::attributeLabels(),
            [
                'text' => 'Текст',
                'type' => 'Тип',
                'ipv4' => 'IPv4',
                'head' => 'Заголовки'
            ]
        );
    }

    /**
     * Правила
     */
    public function rules(): array
    {
        return array_merge(
            parent::rules(),
            [
                [
                    'text',
                    'required'
                ],
                [
                    'ipv4',
                    'default',
                    'value' => yii::$app->request->userIP
                ],
                [
                    'head',
                    'default',
                    'value' => yii::$app->request->getHeaders()
                ]
            ]
        );
    }

    /**
     * Запись
     *
     * @param string $text Текст запроса
     * @param string $type Тип запроса
     * @param Account|null $account Пользователь совершивший запрос
     */
    public static function write(string $text, string $type = 'general', Account|null $account = null): ?self
    {
        // Инициализация
        $vertex = new self;
        isset($account) && yii::$app->user->isGuest ?: $account = yii::$app->user->identity;

        // Настройки
        $vertex->text = $text;
        $vertex->type = $type;

        if ($vertex->save()) {
            // Поиск записан

            // Запись ребра: АККАУНТ -> ПОИСК
            return $account && AccountEdgeSearch::writeSafe($account->id, $vertex->readId(), 'request') ? $vertex : null;
        }

        return null;
    }


    /**
     * Поиск содержимого поиска (продуктов)
     *
     * @todo В будущем возможно заказ не только поставок реализовать
     * Переписать реестр и проверку на дубликаты, не понимаю как они работают
     */
    public static function content(array $products, int $limit = 50, int $page = 1): Supply|int|array|null
    {
        // Инициализация буфера вывода
        $response = $products;

        // Генерация сдвига по запрашиваемым данным (система страниц)
        $offset = $limit * ($page - 1);

        foreach ($response as &$row) {
            // Перебор продуктов

            if ($row instanceof Product) {
                // В массиве объект - инстанция товара

                // Преобразование к массиву в буфер (унификация данных)
                $_row = $row->attributes;
            } else {
                // В массиве не товар (подразумевается, что это массив с параметрами)

                // Запись в буфер (унификация данных)
                $_row = $row;
            }

            // Поиск поставок привязанных к продуктам
            $connections = Supply::searchByEdge(
                from: 'product',
                to: 'supply',
                edge: 'supply_edge_product',
                limit: $limit,
                offset: $offset,
                direction: 'OUTBOUND',
                subquery_where: [
                    ['product._key' => $_row['_key']],
                    ['supply.catn == product.catn'],
                    ['supply_edge_product.type' => 'connect']
                ],
                where: 'supply._id == supply_edge_product[0]._from',
                select: '{supply, supply_edge_product}'
            );

            // Инициализация буфера
            $buffer_connections = [];

            if (count($connections) === 100) {
                // Если в базе данных хранится много поставок

                // Инициализация
                $_row['overload'] = true;
            }

            foreach ($connections as $key => &$connection) {
                // Перебор поставок

                if ($cost = $connection['supply']['cost'] < 1) {
                    // Цена меньше единицы (подразумевается как ошибка)

                    // Скрыть из выдачи
                    unset($connections[$key]);

                    continue;
                }

                // Инициализация аккаунта
                $connection['account'] = Account::searchBySupplyId($connection['supply_edge_product'][0]['_from']);

                // Инициализация продукта
                $connection['product'] = Product::searchBySupplyId($connection['supply_edge_product'][0]['_from']);

                try {
                    // Доставка "auto"

                    try {
                        $from = (int) (Warehouse::searchBySupply(Supply::searchByCatnAndProd($connection['supply']['catn'], $connection['supply']['prod']))[0]->trmn ?? Settings::searchActive()?->delivery_from_default ?? 36);
                    } catch (exception $e) {
                        $from = empty(Settings::searchActive()->delivery_from_default) ? 36 : (int) Settings::searchActive()->delivery_from_default;
                    }

                    try {
                        $to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36;
                    } catch (Exception $e) {
                        $to = 36;
                    }

                    if (DellinModel::searchByTerminalId($from)->data['cityID'] === DellinModel::searchByTerminalId($to)->data['cityID']) {
                        // Доставка в пределах города

                        $connection['delivery'] = [
                            'price' => [
                                'all' => 1
                            ],
                            'ready' => 1,
                            'type' => 'auto'
                        ];

                        goto skip_avia;
                    }
                    // Доставка в другие города

                    // Инициализация буфера доставки
                    $buffer_connection = $connection['product']['bffr']["$from-$to"] ?? null;

                    if (isset($buffer_connection) && !empty($buffer_connection['data']) && time() < $buffer_connection['expires'] ?? 0) {
                        // Найдены данные доставки в буфере
                        // и срок хранения не превышен, информация актуальна

                        // Запись в буфер вывода
                        $connection['delivery'] = $buffer_connection['data'];
                        $connection['delivery']['type'] = 'auto';
                    } else {
                        // Инициализация инстанции продукта в базе данных
                        $product = Product::searchByCatnAndProd($connection['product']['catn'], $connection['product']['prod']);

                        if ($connection['delivery'] = Dellin::calcDeliveryAdvanced(
                            $from,
                            $to,
                            (int) ($connection['product']['wght'] ?? 0),
                            (int) ($connection['product']['dmns']['x'] ?? 0),
                            (int) ($connection['product']['dmns']['y'] ?? 0),
                            (int) ($connection['product']['dmns']['z'] ?? 0)
                        )) {
                            // Получены данные доставки

                            // Инициализация доставки Dellin (автоматическая)
                            $product->bffr = [
                                "$from-$to" => [
                                    'data' => $connection['delivery'],
                                    'expires' => time() + 86400
                                ]
                            ] + ($product->bffr ?? []);

                            // Отправка в базу данных
                            $product->update();
                        }

                        // Запись типа доставки
                        $connection['delivery']['type'] = 'auto';
                    }
                } catch (Exception $e) {
                    $connection['delivery']['error'] = true;

                    // var_dump($e->getMessage());
                    // var_dump($e->getTrace());
                    // var_dump($e->getFile());

                    // var_dump(json_decode($e->getMessage(), true)['errors']);
                    // die;
                } finally {
                    // echo $connection['delivery']['price']['all'];
                    // Инициализация цены (цена поставки + цена доставки + наша наценка)
                    $connection['cost'] = $cost + ($connection['delivery']['price']['all'] ?? $connection['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0);
                }

                // Инициализация версии для рассчета доставки по воздуху
                $buffer_delivery_avia = $connection;

                try {
                    // Доставка "avia"

                    if ($cost = $connection['supply']['cost'] < 1) {
                        // Цена меньше единицы (подразумевается как ошибка)
                        // Этот код не будет выполняться, так как цена одна на обе позиции и аналогичная проверка выше уже есть
                        // Однако я это оставлю для возможных доработок в будущем

                        // Скрыть из выдачи
                        unset($connections[$key]);

                        continue;
                    }

                    try {
                        $from = (int) (Warehouse::searchBySupply(Supply::searchByCatnAndProd($connection['supply']['catn'], $connection['supply']['prod']))[0]->trmn ?? Settings::searchActive()?->delivery_from_default ?? 36);
                    } catch (Exception $e) {
                        $from = empty(Settings::searchActive()->delivery_from_default) ? 36 : (int) Settings::searchActive()->delivery_from_default;
                    }

                    try {
                        $to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36;
                    } catch (Exception $e) {
                        $to = 36;
                    }

                    // Доставка в другие города

                    // Инициализация буфера доставки
                    $buffer_connection = $connection['product']['bffr']["$from-$to-avia"] ?? null;

                    if (isset($buffer_connection) && !empty($buffer_connection['data']) && time() < $buffer_connection['expires'] ?? 0) {
                        // Найдены данные доставки в буфере
                        // и срок хранения не превышен, информация актуальна

                        // Запись в буфер вывода
                        $buffer_delivery_avia['delivery'] = $buffer_connection['data'];
                        $buffer_delivery_avia['delivery']['type'] = 'avia';
                    } else {
                        // Инициализация инстанции продукта в базе данных
                        $product = Product::searchByCatnAndProd($buffer_delivery_avia['product']['catn'], $connection['product']['prod']);

                        if ($buffer_delivery_avia['delivery'] = Dellin::calcDeliveryAdvanced(
                            $from,
                            $to,
                            (int) ($buffer_delivery_avia['product']['wght'] ?? 0),
                            (int) ($buffer_delivery_avia['product']['dmns']['x'] ?? 0),
                            (int) ($buffer_delivery_avia['product']['dmns']['y'] ?? 0),
                            (int) ($buffer_delivery_avia['product']['dmns']['z'] ?? 0),
                            avia: true
                        )) {
                            // Получены данные доставки

                            // Инициализация доставки Dellin (автоматическая)
                            $product->bffr = [
                                "$from-$to-avia" => [
                                    'data' => $buffer_delivery_avia['delivery'],
                                    'expires' => time() + 86400
                                ]
                            ] + ($product->bffr ?? []);

                            // Отправка в базу данных
                            $product->update();
                        }

                        // Запись типа доставки
                        $buffer_delivery_avia['delivery']['type'] = 'avia';
                    }
                } catch (exception $e) {
                    $buffer_delivery_avia['delivery']['error'] = true;

                    // var_dump($e->getMessage());
                    // var_dump($e->getTrace());
                    // var_dump($e->getFile());

                    // var_dump(json_decode($e->getMessage(), true)['errors']);
                    // die;
                } finally {
                    if (!isset($buffer_delivery_avia['delivery']['error']) || $buffer_delivery_avia['delivery']['error'] !== true) {
                        // Если рассчиталась доставка самолётом

                        // echo $buffer_delivery_avia['delivery']['price']['all']; die;

                        // Инициализация цены (цена поставки + цена доставки + наша наценка)
                        $buffer_delivery_avia['cost'] = $cost + ($buffer_delivery_avia['delivery']['price']['all'] ?? $buffer_delivery_avia['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0);

                        // Запись в буфер
                        $buffer_connections[] = $buffer_delivery_avia;
                    }
                }

                // Пропуск доставки "avia"
                skip_avia:
            }


            // Запись обработанных данных
            $_row['supplies'] = array_merge($connections, $buffer_connections);

            // Запись из буфера
            $row = $_row;
        }

        return $response;
    }

    /**
     * Генерация HTML-кода с найденным товаром
     *
     * Я сам в ахуе, переделывать не буду
     *
     * @param array $row Товар сгенерированный через Search::content()
     * @param string|null $cover Обложка
     * @param array $list Реестр найденных товаров
     * @param bool $analogs Запрошены аналоги (не выведет пустые товары)
     *
     * @return string HTML-элемент с товаром
     */
    public static function generate(array &$row, string|null &$cover = null, array &$list = [], bool $analogs = false): string
    {
        foreach ($row['imgs'] ?? [] as &$img) {
            // Перебор изображений для обложки

            if ($img['covr'] ?? false) {
                // Найдена обложка

                $cover = $img['h150'];

                break;
            }
        }

        if (is_null($cover)) {
            // Обложка не инициализирована

            if (!$cover = $imgs[0]['h150'] ?? false) {
                // Не удалось использовать первое изображение как обложку

                // Запись обложки по умолчанию
                $cover = Product::cover($row['prod'], 150);
            }
        }

        // Инициализация буфера с HTML поставок
        $supplies_html = '';

        // Инициализация блокировщика для пустого блока (на случай, если нет поставок, чтобы не было дубликатов вывода)
        $empty_block = false;

        // Инициализация счётчика поставок
        $supplies_amount = count($row['supplies'] ?? []);

        // Инициализация указателя номера цикла
        $supply_iterator = 1;

        foreach (empty($row['supplies']) || $supplies_amount === 0 ? [null] : $row['supplies'] as &$supply) {
            // Перебор поставок

            // Запись в список найденных
            $list[$row['prod']] = [$row['catn']] + (isset($list[$row['prod']]) ? $list[$row['prod']] : []);

            // Инициализация модификатора класса
            if ($supplies_amount > $supply_iterator) {
                // Это не последняя строка с товаром и его поставками

                $supply_class_modifier = 'mb-1';
            } else {
                // Это последняя строка с товаром и его поставками

                $supply_class_modifier = '';
            }

            if (is_null($supply)) {
                // Поставки отсутствуют

                // Генерация данных об отсутствии заказов
                goto no_supplies;
            } else {
                // Обычная обработка поставки

                // Инициализация переменных
                extract($supply);
            }

            // Инициализация цены
            $price_raw = ($supply['cost'] ?? 0) + ($cost ?? 0);
            // $price = $price_raw . ' ' . $supply_edge_product[0]['onec']['Цены']['Цена']['Валюта'] ?? 'руб';
            $price = $price_raw . ' руб';

            // Инициализация количества
            // $amount_raw = $amount =  $supply['amnt'] ?? $supply_edge_product[0]['onec']['Количество'] ?? 0;

            // Инициализация цены и её представления
            $amount_raw = $amount = count(@SupplyEdgeProduct::searchByVertex(@Supply::collectionName() . '/' . $supply['_key'], @Product::searchByCatnAndProd($supply['catn'], $supply['prod'])->readId(), limit: 999)) ?? 1;
            $amount .= ' шт';

            if ($amount_raw < 1 || $price_raw < 1) {
                // Количество 0 или цена 0

                // Поставки отстутвуют
                no_supplies:

                // Проверка на блокировку или запрошены аналоги
                if ($empty_block || $analogs) continue;

                $supplies_html .= <<<HTML
                    <div class="row $supply_class_modifier m-0 h-100 text-right">
                        <a class="col-auto ml-auto my-auto text-dark" href="/order/new/custom" role="button" onclick="return false;">
                            <small>
                                Заказать поиск у оператора
                            </small>
                        </a>
                    </div>
                HTML;

                // Запись блокировщика
                $empty_block = true;

                // Обновление счётчика
                ++$supply_iterator;

                continue;
            }

            // Инициализация доставки
            if (isset($delivery['error']) || $delivery === '?') {
                // Не удалось рассчитать доставку

                // Инициализация типа доставки
                $delivery_type = $delivery['type'] ?? 'auto';

                // Инициализация индикатора
                $delivery_icon = '<i class="mr-1 fas fa-truck"></i>';

                // Инициализация времени
                $delivery = '?';
            } else {
                // Удалось рассчитать доставку

                // Инициализация типа доставки
                $delivery_type = $delivery['type'] ?? 'auto';

                // Инициализация индикатора
                $delivery_icon = match ($delivery_type) {
                    'avia' => '<i class="mr-1 fas fa-plane"></i>',
                    default => '<i class="mr-1 fas fa-truck"></i>'
                };

                if ($delivery['ready'] ?? false) {
                    // Указана дата готовности к получению

                    // Инициализация доставки
                    $delivery = $delivery['ready'];
                } else {
                    // Не указана дата готовности к получению

                    // Инициализация даты отправки
                    try {
                        // Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель)

                        $delivery_send_date = datetime::createFromFormat('Y-m-d', $delivery['orderDates']['arrivalToOspSender'])->getTimestamp();
                    } catch (throwable $e) {
                        // Взять данные из "pickup" (Дата передачи груза на адресе отправителя)

                        $delivery_send_date = datetime::createFromFormat('Y-m-d', $delivery['orderDates']['pickup'])->getTimestamp();
                    }

                    // Инициализация времени доставки
                    try {
                        // Доставка по воздуху (подразумевается), данные из "giveoutFromOspReceiver" (Дата и время, с которого груз готов к выдаче на терминале)

                        $delivery_converted = @datetime::createFromFormat('Y-m-d H:i:s', $delivery['orderDates']['giveoutFromOspReceiver'])->getTimestamp();
                    } catch (Throwable $e) {
                        // Автоматическая доставка (подразумевается), данные из "arrivalToOspReceiver" (Дата прибытия натерминал-получатель)

                        $delivery_converted = @datetime::createFromFormat('Y-m-d', $delivery['orderDates']['arrivalToOspReceiver'])->getTimestamp();
                    }

                    // Инициализация доставки
                    $delivery = ceil(($delivery_converted - ($delivery_send_date ?? 0)) / 60 / 60 / 24) + 1;
                }
            }

            // Инициализация индекса аккаунта
            $index = $account['indx'] ?? 'Неизвестен';

            // Генерация
            $supplies_html .= <<<HTML
                <div class="row $supply_class_modifier m-0 text-right">
                    <small class="ml-auto col-1 ml-2 my-auto pl-2 pr-0">$index</small>
                    <small class="col-1 my-auto pl-2 pr-0 text-center">$amount</small>
                    <small class="col-auto mr-2 my-auto pl-2 pr-0 text-left" title="Ориентировочно">$delivery_icon $delivery дн</small>
                    <b class="col-auto my-auto my-auto text-center">$price</b>
                    <a class="col-1 ml-0 py-2 text-dark d-flex button_white rounded" title="Добавить {$row['catn']} в корзину" role="button" onclick="return cart_write('{$supply['_id']}', '$delivery_type');">
                        <i class="fas fa-cart-arrow-down pr-1 m-auto"></i>
                    </a>
                </div>
            HTML;

            // Обновление счётчика
            ++$supply_iterator;
        }

        return $supplies_html;
    }
}
