<?php

declare(strict_types=1);

namespace app\models;

use yii;

use app\models\traits\SearchByEdge;

use carono\exchange1c\controllers\ApiController;
use carono\exchange1c\interfaces\DocumentInterface;

use app\models\connection\Dellin;

use Exception;

/**
 * Заказ
 *
 * @see Account Заказчик
 * @see Supply Поставки для заказа
 */
class Order extends Document implements DocumentInterface
{
    use SearchByEdge;

    /**
     * Поставки для записи
     */
    public array|int $supplies;

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

    /**
     * Свойства
     */
    public function attributes(): array
    {
        return array_merge(
            parent::attributes(),
            [
                'ocid',
                'stts',
                'sync'
            ]
        );
    }

    /**
     * Метки свойств
     */
    public function attributeLabels(): array
    {
        return array_merge(
            parent::attributeLabels(),
            [
                'ocid' => 'Идентификатор 1C',
                'stts' => 'Статус',
                'sync' => 'Статус синхронизации с 1C'
            ]
        );
    }

    /**
     * Правила
     */
    public function rules(): array
    {
        return array_merge(
            parent::rules(),
            [
                [
                    'sync',
                    'boolean',
                    'message' => '{attribute} должен иметь логическое значение'
                ],
                [
                    'sync',
                    'default',
                    'value' => false
                ]
            ]
        );
    }

    /**
     * Подключение к аккаунту
     */
    public function connect(Account $account): ?AccountEdgeOrder
    {
        // Запись ребра: АККАУНТ -> ЗАКАЗ
        return AccountEdgeOrder::write($account->readId(), $this->readId(), data: ['stts' => 'current']) ?? throw new Exception('Не удалось инициализировать ребро: АККАУНТ -> ЗАКАЗ');
    }

    /**
     * Запись товара к заказу
     *
     * $supply = [ Supply $supply, int $amount = 1 ]
     *
     * @param string $supply_id Идентификатор поставки
     * @param string $delivery_type Тип доставки
     * @param int $amount Количество
     * @param Account $trgt Заказчик
     *
     * @return int Количество записанных поставок
     *
     * @todo Создать параметр разделителя для администрации
     */
    public function writeSupply(string $supply_id, string $delivery_type, int $amount = 1, Account $trgt = null): int
    {
        // Инициализация
        $trgt ?? $trgt = yii::$app->user->identity ?? throw new Exception('Не удалось инициализировать заказчика');

        // Проверка корзины
        if (is_null($this->_key)) {
            // Корзина не инициализирована

            // Инициализация корзины
            if (!$this->save()) {
                // Инициализация корзины (активного заказа) не удалась

                throw new Exception('Ошибка при записи заказа в базу данных');
            }

            // Инициализация ребра: АККАУНТ -> ЗАКАЗ
            if (!AccountEdgeOrder::write($trgt->readId(), $this->readId(), 'create')) {
                // Инициализация не удалась

                throw new Exception('Ошибка при записи ребра от аккаунта до заказа в базу данных');
            }
        }

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

        // Обработка поставок
        for ($i = 0; $i < $amount; $i++) {
            // Создание рёбер соразмерно запросу (добавление нескольких продуктов в корзину)

            // Запись ребра: ЗАКАЗ -> ПОСТАВКА
            if (!$supply_model = Supply::searchById($supply_id) or !$order_edge_supply = OrderEdgeSupply::write($this->readId(), $supply_model->readId(), 'write')) {
                // Поставка не найдена или запись ребра не удалась

                continue;
            } else {
                // Ребро создано (товар подключен к заказу)

                // Обновление счётчика добавленных товаров
                $amount_buffer++;

                // Запись типа доставки
                $order_edge_supply->dlvr = [
                    'type' => $delivery_type
                ];
                $order_edge_supply->update();

                // Запись в журнал
                $this->journal('write', ['target' => $supply_model->readId()]);
            }
        }

        if ($amount_buffer === 0) {
            // Отправка уведомления
            self::notification('Неудачная попытка добавить товар в корзину');
        } else if ($amount_buffer === 1) {
            // Отправка уведомления
            self::notification('Товар ' . $supply_model->catn . ' добавлен в корзину');
        } else {
            // Отправка уведомления
            self::notification('Добавлено ' . $amount_buffer . ' товаров в корзину');
        }

        return $amount_buffer;
    }

    /**
     * Удаление поставки
     *
     * @param Supply|array $supply Товары
     *
     * @return int Количество удалённых рёбер
     *
     * @todo Доделать
     */
    public function deleteSupply(Supply|array $supply): int
    {
        // Инициализация
        $amount = 0;

        if ($supply instanceof Supply) {
            // Передана инстанция класса поставки или второй элемент массива не является числом

            // Унификация входных данных
            $supply = [
                $supply->catn => [
                    'auto' => 1
                ]
            ];
        }

        foreach ($supply as $catn => $data) {
            // Перебор целей

            var_dump('ок');

            foreach ($data as $type => $delete_amount) {
                // Перебор данных цели

                var_dump('да');
                if ($supply = Supply::searchByCatn($catn)) {
                    // Поставка найдена

                    $edges = OrderEdgeSupply::searchByVertex($this->readId(), $supply->readId(), limit: $delete_amount, filter: ['order_edge_supply.dlvr.type == \'' . $type . '\'']);

                    for (; count($edges) > $amount; $amount++) {

                        var_dump(count($edges), $amount, $delete_amount);
                        var_dump(PHP_EOL);

                        // Удаление из базы данных
                        $edges[$amount]->delete();

                        // Запись в журнал
                        $this->journal(
                            'delete',
                            [
                                'target' => [
                                    $supply->readId() => $type
                                ]
                            ]
                        );
                    }
                }
            }
        }

        // Генерация вставки текста с типом доставки
        $type = Supply::DeliveryToRussian($type);

        if ($amount === 0) {
            // Отправка уведомления
            self::notification('Не удалось удалить товар из корзины');
        } else if ($amount === 1) {
            // Отправка уведомления
            self::notification("Товар $supply->catn c $type удалён из корзины");
        } else {
            // Отправка уведомления
            self::notification("Удалено $amount товаров из корзины");
        }

        return $amount;
    }

    /**
     * Поиск заказа
     *
     * @todo Привести в порядок
     */
    public static function searchSmart(
        Account|string $account = null,
        string $stts = 'current',
        string|null $search = null,
        int $limit = 1,
        int $page = 1,
        string|null $select = null,
        bool $supplies = false,
        int|null $from = null,
        int|null $to = null,
        bool $count = false,
        bool $debug = false
    ): int|array|null {
        // Инициализация аккаунта
        if (empty($account)) {
            // Не получен аккаунт

            $subquery_where = [
                [
                    'account._id' => Account::initAccount()->readId()
                ]
            ];
        } else if ($account instanceof Account) {
            // Получен аккаунт

            if (Account::isMinimalAuthorized(Account::initAccount())) {
                $subquery_where = [
                    [
                        'account._id' =>  $account->readId()
                    ]
                ];
            } else {
                throw new Exception('У вас нет прав на обработку другого пользователя');
            }
        } else if (str_contains($account, '@all')) {
            // Получен запрос на обработку всех аккаунтов

            $subquery_where = [];
        } else {
            throw new Exception('Не удалось инициализировать пользователя');
        }

        // Инициализация типа заказа
        if (strcasecmp($stts, '@all') !== 0) {
            // Если не указан поиск всех заказов

            $subquery_where[] = [
                'account_edge_order.stts' => $stts
            ];
        }

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

        // Инициализация фильтрации
        if (isset($from, $to)) {
            // Задан период

            // Инициализация логики
            $foreach = [
                ['edge' => 'account_edge_order'],
                ['jrnl' => 'order.jrnl']
            ];

            // Инициализация фильтра
            $where = "edge._to == order._id && jrnl.action == 'requested' && jrnl.date >= $from && jrnl.date <= $to";
        } else {
            // Ничего не задано

            // Инициализация логики
            $foreach = ['edge' => 'account_edge_order'];

            // Инициализация фильтра
            $where = "edge._to == order._id";
        }

        // Поиск заказов в базе данных
        $orders = self::searchByEdge(
            from: 'account',
            to: 'order',
            subquery_where: $subquery_where,
            foreach: $foreach,
            where: $where,
            limit: $limit,
            offset: $offset,
            sort: ['DESC'],
            select: $select,
            direction: 'INBOUND',
            count: !$supplies && $count,
            debug: $debug
        );

        if ($debug) {
            var_dump($orders);
            die;
        }

        if (!$supplies && $count) {
            // Запрошен подсчет заказов

            return $orders;
        }

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

        // Инициализация архитектуры буфера вывода
        foreach ($orders ?? [null] as $key => $order) {
            // Перебор заказов

            // Запись в буфер возврата
            $return[$key]['order'] = $order;
        }

        if ($supplies) {
            // Запрошен поиск поставок

            foreach ($return as $key => &$container) {
                // Перебор заказов


                if ($container['order'] instanceof Order) {
                    // Инстанция заказа

                    // Инициализация заказа
                    $order = $container['order'];
                } else {
                    // Массив с заказом (подразумевается)

                    // Инициализация настроек
                    $config = $container['order'];
                    unset($config['_id'], $config['_rev'], $config['_id']);

                    // Инициализация заказа
                    $order = new Order($config);
                }

                // Чтение полного содержания
                $return[$key]['supplies'] = $order->supplies($limit, $page, $search, count: $count);

                if ($count) {
                    // Запрошен подсчет поставок (переделать под подсчёт)

                    return $return[$key]['supplies'];
                }
            }
        }

        return $return;
    }

    /**
     * Поиск содержимых поставок заказа
     *
     * @todo В будущем возможно заказ не только поставок реализовать
     * Переписать реестр и проверку на дубликаты, не понимаю как они работают
     */
    public function supplies(int $limit = 1, int $page = 1, string|null $search = null, bool $count = false): Supply|int|array|null
    {
        // Инициализация аккаунта
        $account = Account::initAccount();

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

        if (!empty($search)) {
            // Передан поиск по продуктам в заказах

            // Запись ограничения по максимальному значению
            $limit = 9999;
        }

        // Поиск по рёбрам: ЗАКАЗ -> ПОСТАВКА
        $connections = Supply::searchByEdge(
            from: 'order',
            to: 'supply',
            edge: 'order_edge_supply',
            subquery_where: [
                [
                    'order._id' => $this->readId()
                ]
            ],
            where: 'order_edge_supply != []',
            limit: $limit,
            offset: $offset,
            filterStart: ['catn' => $search],
            direction: 'INBOUND',
            select: '{order_edge_supply}',
            count: $count
        );

        if ($count) {
            // Подсчёт запрошен

            return $connections;
        }

        // Инициализация реестра дубликатов
        $supplies = [];

        foreach ($connections as $key => &$connection) {
            // Перебор объектов для заказа

            foreach ($connection['order_edge_supply'] as $edge) {
                // Перебор связанных поставок

                // Инициализация связанной с заказом поставки
                $supply = Supply::searchById($edge['_to']);

                // Инициализация поставщика в буфере
                if (empty($supplies[$supply->prod][$supply->catn][$edge['dlvr']['type']])) $supplies[$supply->prod][$supply->catn][$edge['dlvr']['type']] = [
                    'supply' => $supply,
                    'account' => Account::searchBySupplyId($supply->readId()),
                    'product' => Product::searchBySupplyId($supply->readId()),
                    'currency' => 'руб',
                    'amount' => 1
                ];
                else ++$supplies[$supply->prod][$supply->catn][$edge['dlvr']['type']]['amount'];

                // Инициализация буфера с обрабатываемой поставкой
                $buffer = &$supplies[$supply->prod][$supply->catn][$edge['dlvr']['type']];

                // Запись ребра
                $buffer['edge'] = $edge;

                if (empty($buffer['supply']->cost) || $buffer['supply']->cost < 1) {
                    // Если стоимость не найдена или равна нулю (явная ошибка)

                    // Удаление из базы данных
                    $this->deleteSupply($buffer['supply']);

                    // Инициализация стоимости товара для уведомления (чтобы там не было NULL)
                    $cost = $buffer['supply']->cost ?? 0;

                    // Отправка уведомлений покупателю
                    Notification::_write("Стоимость товара $supply->catn равна $cost", account: $account->_key, type: Notification::TYPE_ERROR);
                    Notification::_write("Товар $supply->catn удалён", account: $account->_key, type: Notification::TYPE_ERROR);

                    // Отправка уведомления поставщику

                    // Отправка уведомления модератору

                    // Удаление из списка
                    unset($connections[$key]);

                    // Выход из цикла
                    break;
                }

                try {
                    // Инициализация данных геолокации
                    try {
                        $from = (int) $buffer['account']['opts']['delivery_from_terminal'] ?? empty(Settings::searchActive()->delivery_from_default) ? 36 : (int) Settings::searchActive()->delivery_from_default;
                    } 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 (($buffer_connection = $buffer['product']['bffr']["$from-$to-" . $edge['dlvr']['type']] ?? false) && time() < $buffer_connection['expires']) {
                        // Найдены данные доставки в буфере и их срок хранения не превышен, информация актуальна

                        // Запись в буфер вывода
                        $buffer['delivery'] = $buffer_connection['data'];
                    } else {
                        // Инициализация инстанции продукта в базе данных (реинициализация под ActiveRecord)

                        $product = Product::searchByCatnAndProd($buffer['product']['catn'], $buffer['product']['prod']);

                        // Инициализация доставки Dellin (автоматическая)
                        $product->bffr = ($product->bffr ?? []) + [
                            "$from-$to-" . $edge['dlvr']['type'] => [
                                'data' => $buffer['delivery'] = Dellin::calcDeliveryAdvanced(
                                    $from,
                                    $to,
                                    (int) ($buffer['product']['wght'] ?? 0),
                                    (int) ($buffer['product']['dmns']['x'] ?? 0),
                                    (int) ($buffer['product']['dmns']['y'] ?? 0),
                                    (int) ($buffer['product']['dmns']['z'] ?? 0),
                                    avia: $edge['dlvr']['type'] === 'avia'
                                ),
                                'expires' => time() + 86400
                            ]
                        ];

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


                    // Запись цены (цена поставки + цена доставки + наша наценка)
                    $buffer['cost'] = ($supply->cost ?? $supply->onec['Цены']['Цена']['ЦенаЗаЕдиницу'] ?? throw new exception('Не найдена цена товара')) + ($buffer['delivery']['price']['all'] ?? $buffer['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0) ?? 0;
                } catch (Exception $e) {
                    $buffer['delivery'] = null;
                }
            }
        }

        return $supplies;
    }

    /**
     * Проверка на то, что все поставки подтверждены
     *
     * @param array $order_edge_supply Поставки
     *
     * @return bool Статус подтверждения всех поставок (true если все и false если хотя бы одна из них не подтверждена)
     */
    public static function checkSuppliesStts(array $order_edge_supply): bool
    {
        if (isset($order_edge_supply['_key'])) {
            // Получено ребро: ЗАКАЗ -> ПОСТАВКА

                if (empty($order_edge_supply['stts']) || $order_edge_supply['stts'] === 'processing' || $order_edge_supply['stts'] === 'requested') {
                    // Найдена неподтверждённая поставка

                    return false;
                }

            return true;
        } else if (isset($order_edge_supply[0]['_key'])) {
                // Получен массив (подразумевается, что с рёбрами: ЗАКАЗ -> ПОСТАВКА)

                foreach ($order_edge_supply as $edge) {
                    // Перебор поставок

                    if (empty($edge['stts']) || $edge['stts'] === 'processing' || $edge['stts'] === 'requested') {
                        // Найдена неподтверждённая поставка

                        return false;
                    }
                }
            return true;
        }

        return false;
    }

    /**
     * @return DocumentInterface[]
     */
    public static function findDocuments1c(): ?array
    {
        // yii::$app->on(ApiController::EVENT_AFTER_EXPORT_ORDERS, self::afterExport1c());

        $orders = self::searchByEdge(
            from: 'account',
            to: 'order',
            edge: 'account_edge_order',
            direction: 'INBOUND',
            subquery_where: 'account_edge_order.type == "processed"',
            where: ['sync' => false]
        );

        foreach ($orders as &$order) {
            // Перебор заказов

            // Запись о том, что синхронизация проведена
            $order->sync = true;

            $order->update();
        }

        return $orders;
    }

    /**
     * @return OfferInterface[]
     */
    public function getOffers1c(): mixed
    {
        // Инициализация
        $supplies = [];

        foreach ($this->jrnl as $key => $jrnl) {
            // Перебор журнала

            // if (isset($supplies[$key]) && $supplies[$key]['id'] !== $jrnl['target']) {
            //     // Запись уже существует и идентификаторы не совпадают

            //     $key .= '_du'

            //     // Реинициализация
            //     $supplies[$key]['id'] = $jrnl['target'];
            // } else {
            //     // Инициализация
            // }


            if (($jrnl['action'] ?? null) === 'write') {
                // Найдено событие записи товара к заказу

                $supplies[$key]['id'] = $jrnl['target'];

                $supplies[$key]['amount'] ?? $supplies[$key]['amount'] = 0;

                ++$supplies[$key]['amount'];
            } else if (($jrnl['action'] ?? null) === 'delete') {
                // Найдено событие удаления товара из заказа

                $supplies[$key]['id'] = $jrnl['target'];

                $supplies[$key]['amount'] ?? $supplies[$key]['amount'] = 0;

                --$supplies[$key]['amount'];
            }
        }

        // file_put_contents('supplies.txt', print_r($supplies, true));

        if (count($supplies) > 0) {
            // Поставки были записаны

            // Инициализация
            $supplies_buffer = [];

            foreach ($supplies as $id => $supply) {
                // Перебор поставок

                if ($supply['amount'] < 1) {
                    continue;
                }

                if ($response = Supply::searchById($supply['id'])) {
                    // Поставка найдена в базе данных

                    $supplies_buffer[] = $response;
                }
            }

            return $supplies_buffer;
        }
        // file_put_contents('AAAAAAAAAAAAAAAAAAA.txt', print_r(1, true));

        return [];
    }

    /**
     * Неизвестно для чего
     */
    public function getRequisites1c(): void
    {
        // return true;
    }

    /**
     * Получить контрагента (пользователя от лица которого происходит операция)
     *
     * @return PartnerInterface
     */
    public function getPartner1c(): Account
    {
        return yii::$app->user->identity ?? throw new Exception('Не удалось идентифицировать пользователя');
    }

    public function getExportFields1c($context = null)
    {
        return [];
    }

    /**
     * Возвращаем имя поля в базе данных, в котором хранится ID из 1с
     *
     * @return string
     */
    public static function getIdFieldName1c()
    {
        return 'ocid';
    }

    public function setRaw1cData($cml, $object): void
    {
    }

    protected static function afterExport1c(): void
    {
        file_put_contents('afterExport1c.txt', print_r(1, true));
    }

    /**
     * Отправка уведомления
     */
    public static function notification(string $text, string|null $type = Notification::TYPE_NOTICE): Notification|array|null
    {
        // Отправка
        return Notification::_write($text, type: $type);
    }

    /**
     * Посчитать количство заказов или количество поставок в заказах
     *
     * @param int $limit Ограничение
     * @param bool $supplies Считать поставки в активном заказе (иначе - заказы)
     *
     * @return int Количество
     */
    public static function count(int $limit = 500, bool $supplies = false): int
    {
        return (int) self::searchSmart(supplies: $supplies, stts: $supplies ? 'current' : '@all', limit: $limit, count: true);
    }

    /**
     * Генерация ярлыка на русском языке для статуса заказа
     *
     * @param string|null $status Статус заказа
     *
     * @return string Ярлык статуса на русском языке
     */
    public static function statusToRussian(?string $status = 'processing'): string
    {
        return match ($status) {
            'processing' => 'Обрабатывается',
            'requested' => 'Запрошен',
            'accepted' => 'Ожидается отправка',
            'going' => 'Доставляется',
            'ready' => 'Готов к выдаче',
            'completed' => 'Завершен',
            'reserved' => 'Резервирован',
            default => 'Обрабатывается'
        };
    }

    /**
     * Генерация списка статусов на русском языке
     *
     * @param string|null $active Активный статус, который выведется первым в списке
     *
     * @return string Лист статусов на русском языке
     */
    public static function statusListInRussian(?string $active = 'processing'): array
    {
        return [(empty($active) ? 'processing' : $active) => static::statusToRussian($active)] + [
            'processing' => 'Обрабатывается',
            'requested' => 'Запрошен',
            'accepted' => 'Ожидается отправка',
            'going' => 'Доставляется',
            'ready' => 'Готов к выдаче',
            'completed' => 'Завершен',
            'reserved' => 'Резервирован'
        ];
    }
}
