<?php

declare(strict_types=1);

namespace app\models;

use yii;

use app\models\traits\Xml2Array;
use app\models\Account;
use app\models\Product;
use app\models\SupplyEdgeProduct;
use app\models\Settings;
use app\models\Import;
use app\models\File;
use app\models\ImportEdgeSupply;
use app\models\WarehouseEdgeImport;
use app\models\ImportEdgeFile;

use carono\exchange1c\interfaces\OfferInterface;
use carono\exchange1c\interfaces\ProductInterface;
use carono\exchange1c\controllers\ApiController;

use moonland\phpexcel\Excel;

use DateTime;
use DateTimeZone;
use DatePeriod;
use DateInterval;

use Exception;

/**
 * Поставка (выгрузка товаров от поставщиков)
 *
 * Представляет собой предложения от поставщиков которые добавляются
 * в универсальные лоты товаров в асспортименте магазина
 *
 * @see Product Продукт (туда добавляются поставки)
 */
class Supply extends Product implements ProductInterface, OfferInterface
{
    use Xml2Array;

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

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

    /**
     * Метки свойств
     */
    public function attributeLabels(): array
    {
        return array_merge(
            parent::attributeLabels(),
            [
                'cost' => 'Стоимость',
                'onec' => 'Данные 1С',
                'oemn' => 'OEM-номера',
                'ocid' => 'Идентификатор 1C'
            ]
        );
    }

    /**
     * Правила
     */
    public function rules(): array
    {
        return array_merge(
            parent::rules(),
            []
        );
    }

    /**
     * После сохранения
     */
    public function afterSave($data, $vars): void
    {
        // Инициализация
        $account = Account::initAccount();

        if (isset($account)) {
            // Инициализирован аккаунт

            if (AccountEdgeSupply::searchByVertex($account->readId(), $this->readId())) {
                // Ребро: "АККАУНТ -> ПОСТАВКА" уже существует

            } else {
                // Ребра не существует

                // Запись ребра: АККАУНТ -> ПОСТАВКА
                (new AccountEdgeSupply)->write($account->readId(), $this->readId(), 'import');
            }
        }
    }

    /**
     * Запись реквизитов из 1С
     */
    public function setRequisite1c($name, $value): mixed
    {
        return true;
    }

    /**
     * Запись группы из 1С
     */
    public function setGroup1c($group): mixed
    {
        // Чтение группы
        // if ($group = SupplyGroup::readByOcid($group->id)) {
        //     // Запись ребра: ПОСТАВКА => ГРУППА ПОСТАВОК
        //     return static::writeEdgeBetweenGroup(static::collectionName() . '/' . $this->_key, $group->collectionName() . '/' . $group->_key);
        // }

        return true;
    }

    /**
     * Поиск через связь с аккаунтом
     *
     * @param string|null $id Идентификатор пользователя
     * @param string|array|null $select Запрашиваемые значения
     */
    public static function searchByAccount(string|null $id = null, string|array|null $select = null, int|null $limit = 10): array
    {
        isset($id) ?: $id = yii::$app->user->id ?? throw new Exception('Не найден идентификатор');

        return self::searchByEdge(
            from: 'account',
            to: 'supply',
            subquery_where: [
                [
                    'account._id' => $id
                ]
            ],
            where: 'supply._id == account_edge_supply[0]._to AND supply.onec["ЗначенияСвойств"] != null',
            select: $select,
            limit: $limit
        );
    }

    /**
     * Поиск через связь с инстанцией импорта
     *
     * @param string|null $id Идентификатор инстанции импорта
     */
    public static function searchByImport(string|null $id = null, int|null $limit = 10): array
    {
        // Инициализация буфера вывода
        $supplies = [];

        foreach (ImportEdgeSupply::find()->where(['_from' => $id])->limit($limit)->all() as $edge) {
            // Перебор найденных рёбер

            // Поиск поставки
            $supply = static::searchById($edge->_to);

            if (empty($supply)) continue;

            // Запись поставки в буфер вывода
            $supplies[] = $supply;
        }

        return $supplies;
    }

    /**
     * Запись данных свойств по UUID 1C
     *
     * Ищет записанные свойства из 1C по их идентификатору и добавляет к ним
     * недостающие данные. Это костыль оставшийся от реляционных баз данных
     *
     * @todo Понять что может храниться внутри "$model->onec['ЗначенияСвойств']['ЗначенияСвойства']" и переписать
     */
    public static function createProperties1c($properties, Account|null $account = null): void
    {
        // Инициализация
        $account ?? $account = yii::$app->user->identity;
        $models = self::searchByAccount($account->readId());
        $properties = self::xml2array($properties->xml);

        $account->on(ApiController::EVENT_AFTER_OFFER_SYNC, self::afterImport1c());

        foreach ($models as $model) {
            // Перебор записей

            // Инициализация
            $changes = false;
            $transit = $model->onec;

            foreach ($model->onec['ЗначенияСвойств'] as $attribute_name => $attribute_value) {
                // Перебор аттрибутов

                foreach ($properties as $property) {
                    // Перебор свойств

                    if (is_array($attribute_value) && is_array($property) && $attribute_value['Ид'] === $property['Ид']) {
                        // Совпадение идентификаторов

                        // Объединение данных
                        $transit['ЗначенияСвойств'][$attribute_name] = array_merge($attribute_value, $property, $transit['ЗначенияСвойств'][$attribute_name]);

                        // Запись индикатора наличия изменений
                        $changes = true;
                    } else {
                        // Объединение данных
                        $transit['ЗначенияСвойств'][$attribute_name] = $property;
                    }
                }
            }

            if ($changes) {
                // Если указано, что записаны изменения

                // Настройка ($transit нужен из-за особенностей __set())
                $model->onec = $transit;

                foreach ($model->onec['ЗначенияСвойств'] as $property) {
                    // Перебор всех свойств

                    if (is_array($property)) {
                        // if ($property['Ид'] === $account->opts['import_sections_oem']) {
                        //     // Если идентификатор свойства совпадает с указанным в настройках свойства хранящего OEM номера

                        // Настройка
                        $model->catn = $property['Значение'];
                        // }
                    }
                }

                // Запись
                $model->save();
            }
        }
    }

    /**
     * Запись параметров из 1С
     */
    public function setProperty1c($property): mixed
    {
        return true;
    }

    /**
     * Запись изображений из 1С
     *
     * @todo Добавить параметры в админ-панель
     * Запретить доступ к изображениям
     */
    public function addImage1c($path, $caption): bool
    {
        // Инициализация
        $i = 0;

        if (!file_exists(YII_PATH_PUBLIC . $catalog = '/img/supplies/' . $this->_key)) {
            // Директория для изображений продукта не найдена

            if (!mkdir(YII_PATH_PUBLIC . $catalog, 0775, true)) {
                // не удалось записать директорию

                return false;
            };
        }

        foreach ($this->imgs ?? [] as $image) {
            // Перебор имеющихся изображений

            if ($path === $image['sorc']) {
                // Изображение уже записано на сервер

                return true;
            }
        }

        // Инициализация
        $urn = basename($path);

        // Запись
        copy($path, $path_local = YII_PATH_PUBLIC . $catalog . '/' . $urn);

        // Запись свойства
        $this->imgs = array_merge(
            $this->imgs ?? [],
            [
                [
                    'dscr' => $caption,
                    'path' => $path_local,
                    'sorc' => $path
                ]
            ]
        );

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

    /**
     * Запись ребра (предложения от поставок к продуктам) из 1С
     *
     * @todo Разобраться зачем нужно возвращать SupplyEdgeProduct
     * Вернуть создание карточек, но только по условиям (загрузка от админа, например)
     */
    public function getOffer1c($offer): SupplyEdgeProduct
    {
        if (empty($this->catn)) {
            // Не передан каталожный номер

            // Разработчику библеотеки надо дать по жопе
            return new SupplyEdgeProduct;
        }

        if (
            !yii::$app->user->isGuest
            && yii::$app->user->identity->agnt
            && (yii::$app->user->identity->type === 'administrator'
                || yii::$app->user->identity->type === 'moderator')
        ) {
            // Пользователь аутентифицирован и авторизован

            // Инициализация п̸̨͇͑͋͠р̷̬̂́̀̊о̸̜̯̹̅͒͘͝д̴̨̨̨̟̈́̆у̴̨̭̮̠́͋̈́к̴̭͊̋̎т̵̛̣͈̔̐͆а̵̨͖͑
            $product = self::initEmpty($this->catn, $this->prod);

            if (!is_array($product)) {
                // Создался только один товар и вернулся в виде модели

                $product = [$product];
            }

            if (is_array($this->oemn)) {
                // Значение OEM было инициализировано

                foreach ($this->oemn as $oem) {
                    // Перебор артикулов из массива ОЕМ-номеров

                    // Инициализация и запись
                    $product[] = self::initEmpty($oem, $this->prod);
                }
            }

            foreach ($product as $product) {
                // Перебор всех инициализированных продуктов

                if ($this->catn !== $product->catn) {
                    // Каталожные номера не соответствуют друг другу

                    continue;
                }

                // Код ниже скорее всего устарел

                if (SupplyEdgeProduct::searchByVertex($this->readId(), $product->readId())) {
                    // Ребро уже существует

                    continue;
                }

                // Запись ребра: ПОСТАВКА -> ПРОДУКТ
                $return = (new SupplyEdgeProduct)->write(
                    $this->readId(),
                    $product->readId(),
                    'connect',
                    [
                        'onec' => self::xml2array($offer->xml)
                    ]
                );
            }
        }

        // Возвращает последнее сохранённое ребро
        // Надо будет с этим разобраться
        return $return ?? new SupplyEdgeProduct();
    }

    /**
     * Импорт Excel-файла
     *
     * @param int $warehouse Идентификатор склада (_key)
     * @param Account|int|null $account Аккаунт управляющий файлом и его данными
     * @param bool $load Загрузить в базу данных
     */
    public function importExcel(int $warehouse, Account|int|null $account = null): bool
    {
        // Инициализация
        $account = Account::initAccount($account);

        if ($this->validate()) {
            // Пройдена проверка

            if (!isset($this->file_excel)) {
                // Не найден файл

                // Запись ошибки
                $this->addError('errors', 'Файл для импорта не найден');

                return false;
            }

            // Инициализация хранилища файлов
            preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
            $timezone = $timezone[1][0];
            $path = YII_PATH_PUBLIC . "/files/$account->_key/$warehouse/" . (new DateTime('now', new DateTimeZone($timezone)))->getTimestamp();

            // Сохранение на диск
            if (!file_exists($path))
                if (!mkdir($path, 0775, true))
                    throw new Exception('Не удалось создать директорию', 500);
            $this->file_excel->saveAs($path = "$path/" . $this->file_excel->baseName . '.' . $this->file_excel->extension);

            // Инициализация инстанции файла
            $file = new File();

            // Запись настроек файла
            $file->type = 'supplies excel';
            $file->path = $path;
            $file->name = $this->file_excel->baseName . '.' . $this->file_excel->extension;
            $file->user = (int) $account->_key;
            $file->meta = [
                'warehouse' => $warehouse
            ];

            // Запись в базу данных
            $file->save();

            // Инициализация инстанции импорта
            $import = new Import;

            if ($import->save()) {
                // Записано в базу данных

                if (ImportEdgeFile::write($import->readId(), $file->readId(), data: ['type' => 'connected'])) {
                    // Записано ребро: "ИНСТАНЦИЯ ИМПОРТА" -> ФАЙЛ

                    // Запись в журнал инстанции импорта
                    $import->journal('connect_with_file', [
                        'target' => $file->readId()
                    ]);
                }

                if (WarehouseEdgeImport::write(Warehouse::collectionName() . '/' . $file->meta['warehouse'], $import->readId(), data: ['type' => 'loaded'])) {
                    // Записано ребро: СКЛАД -> ИНСТАНЦИЯ ПОСТАВОК

                    // Запись в журнал инстанции импорта
                    $import->journal('connect_with_warehouse', [
                        'target' => Warehouse::collectionName() . '/' . $file->meta['warehouse']
                    ]);
                }
            }

            // Макрос действий после импорта
            static::afterImportExcel($account, $warehouse);

            return true;
        }

        // Запись ошибки
        $this->addError('errors', 'Не пройдена проверка параметров');

        return false;
    }

    /**
     * Запись поставок из excel
     *
     * На данный момент обрабатывает только импорт из
     * файлов с расширением .excel
     *
     * @param File $file Инстанция файла
     *
     * @return bool
     */
    public static function loadExcel(File $file): bool {
        // Инициализация
        $created = 0;
        $updated = 0;
        $account = Account::initAccount((int) $file->user);
        $data = [];

        $supply = new Supply();
        $supply->scenario = $supply::SCENARIO_IMPORT_EXCEL;
        $supply->file_excel = $file->path;

        if ($supply->validate()) {
            // Пройдена проверка

            $data[] = Excel::import($supply->file_excel, [
                'setFirstRecordAsKeys' => true,
                'setIndexSheetByName' => true,
            ]);

            // Инициализация буфера импортированных товаров
            $imported = [];

            foreach ($data as $data) {
                // Перебор конвертированных файлов

                if (count($data) < 1) {
                    // Не найдены строки с товарами

                    $supply->addError('errors', 'Не удалось найти данные товаров');
                } else {
                    // Найдены строки с товарами

                    foreach ($data as $doc) {
                        // Перебор полученных документов

                        // Проверка на пустую страницу или документ (пустой массив)
                        if (empty($doc)) continue;

                        foreach ($doc as $row) {
                            // Перебор строк

                            if (!is_array($row)) {
                                // Строка является не массивом со значением колонок, а самим значением колонки (значит в $data и так хранился $doc - у файла всего одна страница)

                                // Универсализация
                                $row = $doc;
                            }

                            // Поиск артикула
                            $article = (string) ($row['Артикул'] ?? $row['артикул'] ?? $row['Article'] ?? $row['article'] ?? $row['catn'] ?? null);

                            // Поиск количества товаров
                            $amount = $row['Количество'] ?? $row['количество'] ?? $row['Amount'] ?? $row['amount'] ?? $row['amnt'] ?? null;

                            // Поиск производителя
                            $prod = (string) ($row['Производитель'] ?? $row['производитель'] ?? $row['Production'] ?? $row['production'] ?? $row['prod'] ?? null);

                            // Поиск аналогов
                            $analogs = explode(',', (string) ($row['Аналоги'] ?? $row['аналоги'] ?? $row['Analogs'] ?? $row['analogs'] ?? $row['ОЕМ'] ?? $row['eom'] ?? ''), 50);

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

                            // Дополнительная фильтрация аналогов
                            foreach ($analogs as $analog) foreach (explode('/', $analog, 50) as $value) $buffer[] = $value;

                            // Запись аналогов из буфера
                            $analogs = $buffer;

                            // Пропуск пустых строк (подразумевается)
                            if ($article === null || $amount === null || $prod === null) continue;

                            // Инициализация товара для инициализации группы
                            if (!$product = Product::searchByCatnAndProd($article, $prod)) $product = Product::writeEmpty($article, $prod, Account::isMinimalAuthorized($account));

                            // Инициализация группы товаров
                            // if (!$group = ProductGroup::searchByProduct($product)) $group = ProductGroup::writeEmpty(active: true);

                            // Инициализация функции создания поставки
                            // $create = function (string $_supply, int|null $amount = null) use ($group, $row, $prod, $analogs, &$created, &$updated, &$imported, $account): bool {
                            $create = function (string $_supply, int|null $amount = null) use ($row, $prod, $analogs, &$created, &$updated, &$imported, $account): bool {
                                // Очистка
                                $_supply = trim($_supply);

                                // Инициализация статуса ошибки
                                $error = false;

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

                                // Запись артикула (каталожного номера) в буфер
                                $_row['catn'] = $_supply;

                                // Запись цены в буфер
                                $_row['cost'] = (float) preg_replace('/[^\d\.]+/', '', preg_replace('/\,+/', ' ', (string) ($row['Стоимость'] ?? $row['стоимость'] ?? $row['Цена'] ?? $row['цена'] ?? $row['Cost'] ?? $row['cost'] ?? $row['Price'] ?? $row['price']))) ?? 0;

                                // Запись производителя в буфер
                                $_row['prod'] = match($prod = ucwords(strtolower(preg_replace('/[^A-zА-я\s]/', ' ', $prod, 300)))) {
                                    'Akebono' => 'AKEBONO',
                                    'Bosch' => 'BOSCH',
                                    'Brembo' => 'BREMBO',
                                    'Ctr' => 'CTR',
                                    'Delphi' => 'DELPHI',
                                    'Gates' => 'GATES',
                                    'Hella' => 'HELLA',
                                    'Kyb' => 'KYB',
                                    'Mando' => 'MANDO',
                                    'Mk Kachiyama', 'Mk-kachiyama', 'Mk-Kachiyama' => 'MK KACHIYAMA',
                                    'Narva' => 'NARVA',
                                    'Ngk' => 'NGK',
                                    'Nk' => 'NK',
                                    'Osram' => 'OSRAM',
                                    'Philips' => 'PHILIPS',
                                    'Rancho' => 'RANCHO',
                                    'Sangsin' => 'SANGSIN',
                                    'Swag' => 'SWAG',
                                    'Trw' => 'TRW',
                                    'Filtron' => 'FILTRON',
                                    'Ajusa' => 'AJUSA',
                                    'Denso' => 'DENSO',
                                    'Dolz' => 'DOLZ',
                                    'Era' => 'ERA',
                                    'Febest' => 'FEBEST',
                                    'Freccia' => 'FRECCIA',
                                    'Gmb' => 'GMB',
                                    'Gsp' => 'GSP',
                                    'Honda' => 'HONDA',
                                    'Hyundai/kia', 'Hyundai/Kia', 'Hyundai-kia', 'Hyundai-Kia'  => 'HYUNDAI/KIA',
                                    'Knecht/mahle', 'Knecht/Mahle', 'Knecht-mahle', 'Knecht-Mahle' => 'KNECHT/MAHLE',
                                    'Lynx' => 'LYNX',
                                    'Mann' => 'MANN',
                                    'Mitsubishi' => 'MITSUBISHI',
                                    'Nipparts' => 'NIPPARTS',
                                    'Nissan' => 'NISSAN',
                                    'Sasic' => 'SASIC',
                                    'Snr' => 'SNR',
                                    'Subaru' => 'SUBARU',
                                    'Toyota' => 'TOYOTA',
                                    'Trw_moto', 'Trw_Moto', 'Trw-moto', 'Trw-Moto' => 'TRW_MOTO',
                                    'Vag' => 'VAG',
                                    'Wynns' => 'WYNNS',
                                    'Ert' => 'ERT',
                                    'Dongil' => 'DONGIL',
                                    'Febi' => 'FEBI',
                                    'Ford' => 'FORD',
                                    'Injin' => 'INJIN',
                                    'Land Rover' => 'LAND ROVER',
                                    'Lpr' => 'LPR',
                                    'Luzar' => 'LUZAR',
                                    'MercedesBenz', 'Mercedesbenz', 'Mercedes Benz' => 'MERCEDES BENZ',
                                    'Suzuki' => 'SUZUKI',
                                    'Teikin' => 'TEIKIN',
                                    'Blue print' => 'BLUE PRINT',
                                    'Daihatsu' => 'DAIHATSU',
                                    'Goodwill' => 'GOODWILL',
                                    'Ina' => 'INA',
                                    'Iveco' => 'IVECO',
                                    'Jp Group' => 'JP GROUP',
                                    'Mfilter' => 'MFILTER',
                                    'Moog' => 'MOOG',
                                    'Nibk' => 'NIBK',
                                    'Asakashi', 'Js Asakashi' => 'JS Asakashi',
                                    'Lemforder' => 'LEMFORDER',
                                    'Man' => 'MAN',
                                    'Volvo' => 'VOLVO',
                                    'Ctp', 'Costex Tractor Parts', => 'CTP',
                                    'Czd' => 'CZD',
                                    'Lavr' => 'LAVR',
                                    'Wai' => 'WAI',
                                    'Asam' => 'ASAM',
                                    'Bmw' => 'BMW',
                                    'Brisk' => 'BRISK',
                                    'Fenox' => 'FENOX',
                                    'Gm', 'General Motors' => 'GENERAL MOTORS',
                                    'Idemitsu' => 'IDEMITSU',
                                    'Krauf' => 'KRAUF',
                                    'Liqui Moly', 'Lm' => 'LIQUI MOLY',
                                    'Mazda' => 'MAZDA',
                                    'Musashi' => 'MUSASHI',
                                    'Seiken' => 'SEIKEN',
                                    'Totachi' => 'TOTACHI',
                                    'Trialli' => 'TRIALLI',
                                    'Dong Feng', 'Dongfeng', 'Dongfeng Motors', 'Dfm' => 'DONGFENG',
                                    'Miles' => 'MILES',
                                    'Sidem' => 'SIDEM',
                                    'Stellox' => 'STELLOX',
                                    'Tokico' => 'TOKICO',
                                    'Phc Valeo', 'Valeo Phc' => 'Valeo PHC',
                                    'Точка Опоры' => 'ТОЧКА ОПОРЫ',
                                    'Jikiu' => 'JIKIU',
                                    'Lada' => 'LADA',
                                    'Mitasu' => 'MITASU',
                                    'Neolux' => 'NEOLUX',
                                    'Pilenga' => 'PILENGA',
                                    'Renault' => 'RENAULT',
                                    'Startvolt' => 'STARTVOLT',
                                    'Zic' => 'ZIC',
                                    'Автодело' => 'АВТОДЕЛО',
                                    'Berkut' => 'BERKUT',
                                    'Fiat' => 'FIAT',
                                    'Profix' => 'PROFIX',
                                    'Sampa' => 'SAMPA',
                                    'Topfils' => 'TOPFILS',
                                    'Uaz', 'Уаз' => 'UAZ',
                                    'Тольятти-тза', 'Тольятти-Тза', 'Тольятти Тза', 'Тольяттитза' => 'Тольятти-ТЗА',
                                    'Aywiparts' => 'AYWIPARTS',
                                    'Сatterpillar', 'Cat', 'Catterpillar Inc' => 'CAT',
                                    'Difa', 'Дифа' => 'DIFA',
                                    'Nisshimbo' => 'NISSHINBO',
                                    'Ruei' => 'RUEI',
                                    'Vic' => 'VIC',
                                    'Auto-gur', 'Auto-Gur', 'Auto Gur' => 'AUTO-GUR',
                                    'Daejin' => 'DAEJIN',
                                    'Gbrake' => 'G-brake',
                                    'Img' => 'IMG',
                                    'Kortex' => 'KORTEX',
                                    'Koyo' => 'KOYO',
                                    'Peugeot/citroen', 'Peugeot/Citroen', 'Peugeot-citroen', 'Peugeot-Citroen' => 'PEUGEOT-CITROEN',
                                    'Tangde' => 'TANGDE',
                                    'Double-force', 'Double-Force', 'Doubleforce', 'Double Force' => 'DOUBLE FORCE',
                                    'Just-drive', 'Just-Drive', 'Just Drive' => 'JUST DRIVE',
                                    'Quattro-freni', 'Quattro-Freni', 'Quattro Freni' => 'QUATTRO FRENI',
                                    'Roers Parts', 'Roers-parts', 'Roers-Parts' => 'ROTERS-PARTS',
                                    'Tixona' => 'TIXONA',
                                    'Zekkert' => 'ZEKKERT',
                                    'Horex' => 'HOREX',
                                    'Zevs', 'Зевс' => 'ZEVS',
                                    'Aiko' => 'AIKO',
                                    'Castrol' => 'CASTROL',
                                    'Deko' => 'DEKO',
                                    'Hyundai Xteer' => 'HYUNDAI XTEER',
                                    'Dyg' => 'DYG',
                                    'Nty' => 'NTY',
                                    'Skf' => 'SKF',
                                    'Alfi Parts' => 'ALFI PARTS',
                                    'Fiestroco' => 'FIESTROCO',
                                    'Golden Snail' => 'GOLDEN SNAIL',
                                    'Ti-guar', 'Ti-Guar' => 'Ti-Guar',
                                    'Cautex' => 'CAUTEX',
                                    'Bm-motorsport', 'Bm-Motorsport' => 'BM-Motorsport',
                                    'Маз', 'Maz' => 'MAZ',
                                    'Брт' => 'БРТ',
                                    'Гпз' => 'ГПЗ',
                                    'Rm Terex' => 'Terex',
                                    'Agm' => 'Agama',
                                    'Tsn' => 'TSN',
                                    'Igp', 'Isuzu Genuine Parts' => 'Isuzu',
                                    'Etp', 'Europe Tractor Parts' => 'ETP',
                                    'Cum' => 'Cummins',
                                    'Xcmg' => 'XCMG',
                                    'Sdlg' => 'SDLG',
                                    'Xcmg-sdlg', 'Xcmg-Sdlg' => 'XCMG-SDLG',
                                    'Vilitan' => 'ViliTan',
                                    'Kato' => 'KATO',
                                    'Bw' => 'Baldwin',
                                    'Itr' => 'ITR',
                                    'Ofm' => 'OFM',
                                    'Kz' => 'Kezhang Mechanic',
                                    'Dongfeng Cummins Engine Company', 'Dongfeng Cummins' => 'DCEC',
                                    'Bomag' => 'BOMAG',
                                    'Shehwa' => 'SHEHWA',
                                    'Kmp Brand', 'Kmp' => 'KMP',
                                    'Jd' => 'John Deere',
                                    'World Gasket', 'Wg', 'World-gasket', 'World-Gasket' => 'WG',
                                    'Original Equipment Manufacturer', 'Oem' => 'OEM',
                                    'Case' => 'CASE',
                                    'Jf Filtration', 'Jf' => 'JF Filtration',
                                    'Hp' => 'HP',
                                    'Ярославский Завод Дизельной Аппаратуры', 'Язда' => 'ЯЗДА',
                                    'Prc' => 'PRC',
                                    'Sf', 'Sf Filter' => 'SF Filter',
                                    'Ga' => 'GA',
                                    'Kentek' => 'KENTEK',
                                    'Cgr Ghinassi', 'Gb Group' => 'Ghinassi',
                                    'Doosan' => 'DOOSAN',
                                    'Dressta' => 'DRESSTA',
                                    'Ammann' => 'AMMANN',
                                    'Blumaq' => 'BLUMAQ',
                                    'Deutz' => 'DEUTZ',
                                    'Daewoo' => 'DAEWOO',
                                    'Furukawa' => 'FURUKAWA',
                                    'Niipon' => 'NIIPON',
                                    'Cargo' => 'CARGO',
                                    'Wps' => 'WPS',
                                    'Zaufer' => 'ZAUFER',
                                    'Handok' => 'HANDOK',
                                    'Fg Wilson', 'F.G.Wilson', 'F.g.wilson' => 'F.G.WILSON',
                                    'Racor' => 'RACOR',
                                    'Jcb' => 'JCB',
                                    'Auger' => 'AUGER',
                                    'Ekka' => 'EKKA',
                                    'Berco' => 'BERCO',
                                    'Bosch Rexroth' => 'BOSCH REXROTH',
                                    'Byg' => 'BYG',
                                    'Cgr' => 'CGR',
                                    'Dayco' => 'DAYCO',
                                    'Dcf' => 'DCF',
                                    'Dunlop' => 'DUNLOP',
                                    'Febi' => 'FeBi',
                                    'Fleetguard' => 'FLEETGUARD',
                                    'Foton' => 'FOTON',
                                    'Getlf' => 'GETLF',
                                    'Hidromek' => 'HIDROMEK',
                                    'Mann&Hummel', 'Mann&hummel' => 'MANN&HUMMEL',
                                    'Niitsu' => 'NIITSU',
                                    'Nok' => 'NOK',
                                    'Nsk' => 'NSK',
                                    'Ntn' => 'NTN',
                                    'Orme' => 'ORME',
                                    'Parker-racor', 'Parker-Racor' => 'PARKER-RACOR',
                                    'Sanz' => 'SANZ',
                                    'Sem' => 'SEM',
                                    'Separ' => 'SEPAR',
                                    'Shaanxi' => 'SHAANXI',
                                    'Shell' => 'SHELL',
                                    'Skf' => 'SKF',
                                    'Ssp' => 'SSP',
                                    'Sst' => 'SST',
                                    'Stal' => 'STAL',
                                    'Steyr' => 'STEYR',
                                    'Toyota' => 'TOYOTA',
                                    'Tt' => 'Tesla Technics',
                                    'Vag' => 'VAG',
                                    'Wix', 'Wix Filters' => 'WIX',
                                    'Yuchai' => 'Yuchai',
                                    'Zexel' => 'ZEXEL',
                                    'Dongil' => 'DONGIL',
                                    'Sanlux' => 'SANLUX',
                                    'Amt' => 'AMT',
                                    'Dt' => 'DT',
                                    'Pramo' => 'PRAMO',
                                    'Haffen' => 'HAFFEN',
                                    'Mfilter' => 'MFILTER',
                                    'Hengst' => 'HENGST',
                                    'Sofima' => 'SOFIMA',
                                    'Dolz' => 'DOLZ',
                                    'Element' => 'ELEMENT',
                                    'Sardes' => 'SARDES',
                                    'Seintex' => 'SEINTEX',
                                    'Patron' => 'PATRON',
                                    'Lpr' => 'LPR',
                                    'Kaichin' => 'KAICHIN',
                                    'Union' => 'UNION',
                                    'Rb-exide', 'Rb-Exide' => 'RB-EXIDE',
                                    'Kurin' => 'KURIN',
                                    'Topfils' => 'TOPFILS',
                                    'Knor' => 'KNOR',
                                    'Megapower' => 'MEGAPOWER',
                                    'Red Filter', 'Redfilter' => 'REDFilter',
                                    'Skania' => 'SKANIA',
                                    'Sct' => 'SCT',
                                    'Kolbenschmidt' => 'KOLBENSCHMIDT',
                                    'Big Filter' => 'BIG Filter',
                                    'Alco Filter' => 'ALCO Filter',
                                    default => $prod
                                };

                                // Запись аналогов в буфер
                                $_row['oemn'] = array_walk($analogs, 'trim');

                                // Инициализация буфера поставки
                                $supply = new static($_row);

                                $supply->scenario = $supply::SCENARIO_WRITE;

                                if ($supply->validate()) {
                                    // Проверка пройдена

                                    if ((($_supply = $supply->validateForUniqueness()) instanceof Supply)) {
                                        // Найдена поставка с такими параметрами (артикул и производитель)

                                        $supply = $_supply;
                                    } else {
                                        // Не найдена поставка с такими параметрами

                                        if ($supply->save()) {
                                            // Поставка записана в базу данных

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

                                            // Запись поставки в буфер
                                            $imported[] = $supply;
                                        };
                                    }

                                    if ($supply->cost !== $_row['cost']) {
                                        // Стоимость изменилась

                                        if ($product = Product::searchByCatnAndProd($supply->catn, $supply->prod)) {
                                            // Найден товар подходящий для привязки

                                            // Приведение типа (для анализатора)
                                            if (is_array($product)) $product = $product[0];
                                        } else {
                                            // Не найден товар подходящий для привязки с только что созданной поставкой

                                            if ($product = Product::writeEmpty($supply->catn, $supply->prod, Account::isMinimalAuthorized($account))) {
                                                // Удалось записать новый товар (НЕАКТИВНЫЙ)

                                                // Отправка уведомления
                                                // Notification::_write("Не найден товар подходящий для связи с поставкой: $supply->catn", account: '@authorized');
                                            } else {
                                                // Не удалось записать новый товар

                                                // Отправка уведомления
                                                Notification::_write("Не удалось создать новый товар: $supply->catn", account: '@authorized');

                                                // Запись статуса об ошибке
                                                $error = true;
                                            }
                                        }

                                        // Завершение выполнения при ошибке
                                        if ($error) return false;

                                        if ($product = Product::searchByCatnAndProd($supply->catn, $supply->prod)) {
                                            // Найден товар подходящий для привязки с этой поставкой

                                            // Поиск ребёр
                                            $edges = SupplyEdgeProduct::searchByVertex($supply->readId(), $product->readId(), limit: 999);

                                            if (isset($amount)) {
                                                // Передано количество
                                                if (count($edges) === 0) {
                                                    // Ребёр нет, но должны быть (если количество загружаемых поставок более нуля)

                                                    // Инициализация количества рёбер которые не удалось записать
                                                    $failed = 0;

                                                    for ($i = 0; $i++ < $amount;) {
                                                        // Перебор создаваемых рёбер (так работает обозначение количества товаров в наличии)

                                                        if (SupplyEdgeProduct::write($supply->readId(), $product->readId(), data: ['type' => 'connect'])) {
                                                            // Записано ребро
                                                        } else {
                                                            // Не записано ребро

                                                            // Обновление количества рёбер которые не удалось записать
                                                            ++$failed;
                                                        }
                                                    }

                                                    // Отправка уведомления
                                                    if ($failed > 0) Notification::_write("Не удалось записать $failed рёбер у поставки $supply->catn");
                                                } else if ($amount === count($edges)) {
                                                    // Количество товаров в поставке не изменилось

                                                    // Раз изменений нет, то обновлять ничего не нужно
                                                    // continue;
                                                } else if ($amount < count($edges)) {
                                                    // Количество товаров в поставке стало МЕНЬШЕ

                                                    // Расчёт разницы
                                                    $delete = count($edges) - $amount;

                                                    // Инициализация количества рёбер которые не удалось удалить
                                                    $failed = 0;

                                                    for ($i = 0; $i < $delete; $i++) {
                                                        // Перебор рёбер на удаление (синхронизация)

                                                        if ($edges[$i]->delete() >= 1) {
                                                            // Удалено ребро
                                                        } else {
                                                            // Не удалено ребро

                                                            // Обновление количества рёбер которые не удалось удалить
                                                            ++$failed;
                                                        }
                                                    }

                                                    // Отправка уведомления
                                                    Notification::_write("Не удалось удалить $failed рёбер у поставки $supply->catn");
                                                } else if ($amount > count($edges)) {
                                                    // Количество товаров в поставке стало БОЛЬШЕ

                                                    // Расчёт разницы
                                                    $write = $amount - count($edges);

                                                    // Инициализация количества рёбер которые не удалось записать
                                                    $failed = 0;

                                                    for ($i = 0; $i < $write; $i++) {
                                                        // Перебор рёбер на запись (синхронизация)

                                                        if (SupplyEdgeProduct::write($supply->readId(), $product->readId(), data: ['type' => 'connect'])) {
                                                            // Записано ребро
                                                        } else {
                                                            // Не записано ребро

                                                            // Обновление количества рёбер которые не удалось записать
                                                            ++$failed;
                                                        }
                                                    }

                                                    // Отправка уведомления
                                                    if ($failed > 0) Notification::_write("Не удалось записать $failed рёбер у поставки $supply->catn");
                                                }
                                            }
                                        }
                                    }

                                    // Инициализация буфера с параметрами загружаемого товара
                                    $vars = $supply->getAttributes();

                                    // Удаление _key, чтобы не перезаписать его при замене параметров документа в буфере
                                    unset($vars['_key']);

                                    // Перенос данных в буфер (существующий в базе данных дубликат)
                                    $supply->setAttributes($vars, false);

                                    // Запись ребра: АККАУНТ -> ПОСТАВКА
                                    (new AccountEdgeSupply)->write($account->readId(), $supply->readId(), 'import');

                                    // Перезапись существующего документа
                                    $supply->update();

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

                                    // Запись поставки в буфер
                                    $imported[] = $supply;
                                } else {
                                    // Проверка не пройдена

                                    // Добавление ошибок
                                    foreach ($supply->errors as $attribute => $error) $supply->addError($attribute, $error);

                                    // Запись статуса об ошибке
                                    $error = true;
                                }

                                if ((isset($product) || $product = Product::searchByCatnAndProd($supply->catn, $supply->prod)) && Account::isMinimalAuthorized($account)) {
                                    // Авторизованный пользователь и найден товар

                                    // Активация товара
                                    $product->activate();

                                    // foreach (Product::searchByCatn($product->catn) as $target) {
                                    //     // Перебор товаров по артикулу (все производители)
                                    // }

                                    // Добавление в группу аналогов
                                    // $group->writeProduct($product);
                                }

                                return !$error;
                            };

                            // Запись поставки
                            $create($article, (int) $amount);

                            // foreach ($analogs as $_supply) {
                            //     // Перебор аналогов (если найдены)

                            //     // Запись поставки
                            //     $create((string) $_supply);
                            // }
                        }
                    }
                }
            }

            if (count($imported) > 0) {
                // Успешно записана минимум 1 поставка

                foreach ($imported as $supply) {
                    // Перебор импортированных поставок

                    // Инициализация инстанции импорта
                    $import = Import::searchByFile($file);

                    if (isset($import)) {
                        // Найдена интанция импорта

                        if (ImportEdgeSupply::write($import->readId(), $supply->readId(), data: ['type' => 'imported', 'vrsn' => ImportEdgeSupply::searchMaxVersion($supply) + 1])) {
                            // Записано ребро: ИНСТАНЦИЯ ПОСТАВОК -> ПОСТАВКА

                            // Запись в журнал инстанции импорта
                            $import->journal('connect_with_supply', [
                                'target' => $supply->readId()
                            ]);
                        }
                    }
                }
            }

            // Запись статуса
            $file->stts = 'loaded';

            if ($file->update() > 0) {
                // Удалось записать в базу данных

                // Запись в журнал
                $file->journal('loaded');
            }

            // Макрос действий после загрузки
            static::afterLoadExcel($account, $created, $updated);

            // Удаление (важно именно задать null для формы в представлении)
            $supply->file_excel = null;
        }

        // Запись ошибки
        $supply->addError('errors', 'Не пройдена проверка параметров');

        return false;
    }

    /**
     * Запись поставок из 1С
     *
     * @todo Понять что может храниться внутри "$model->onec['ЗначенияСвойств']['ЗначенияСвойства']" и переписать
     * Разобраться и создать возможность загрузки от лица другого аккаунта
     */
    public static function createModel1c($supply): ?self
    {
        // Инициализация
        $_supply = self::searchByOcid($id = (string) $supply->Ид) ?? new self;
        $account ?? $account = yii::$app->user->identity;

        // Настройка
        $_supply->ocid = $id ?? null;
        $_supply->catn = (string) $supply->Артикул;
        $_supply->dscr = (string) $supply->Описание;
        $_supply->onec = self::xml2array($supply->xml);

        if (isset($_supply->onec['ЗначенияСвойств'])) {
            // Свойства инициализированы

            foreach ($_supply->onec['ЗначенияСвойств'] as $property) {
                // Перебор всех свойств

                if (is_array($property)) {
                    if (!empty($account->opts['import_sections_oem']) && $property['Ид'] === $account->opts['import_sections_oem']) {
                        // Если идентификатор свойства совпадает с указанным в настройках свойства хранящего OEM номера

                        // Настройка
                        $_supply->oemn = array_merge(self::searchOemn($property['Значение']), self::searchOemn((string) $supply->Артикул));
                    }
                }
            }
        }

        // Запись
        if ($_supply->write()) {
            // Поставка успешно сохранена

            return $supply;
        }

        // Запись
        if ($_supply->save()) {
            // Поставка успешно сохранена

            return $supply;
        }

        return null;
    }

    /**
     * @param mixed|null $context
     * @return array
     */
    public function getExportFields1c($context = null)
    {
        return $this->onec;
    }

    /**
     * Поиск OEM номеров
     *
     * @param string $oemn Необработанная строка с OEM-номерами
     * @param string $delimiters Разделители
     *
     * @todo НЕ ЗАБЫТЬ СДЕЛАТЬ НАСТРОЙКУ РАЗДЕЛИТЕЛЕЙ
     *
     * @return array OEM-номера
     */
    public static function searchOemn(string $oemn, string $delimiters = '\s\+\/,'): array
    {
        // Инициализация
        $catn = [];

        // Конвертация
        preg_match_all("/[^$delimiters]+/", $oemn, $catn);

        return $catn[0];
    }

    /**
     * Запись цены из 1С
     */
    public function setPrice1c($price)
    {
    }

    /**
     * @param $types
     * @return void
     */
    public static function createPriceTypes1c($types)
    {
    }

    /**
     * offers.xml > ПакетПредложений > Предложения > Предложение > ХарактеристикиТовара > ХарактеристикаТовара
     *
     * Характеристики товара
     * $name - Наименование
     * $value - Значение
     *
     * @param \Zenwalker\CommerceML\Model\Simple $specification
     * @return void
     */
    public function setSpecification1c($specification)
    {
    }

    /**
     * Запись данных на случай ошибки при экспорте из 1С
     */
    public function setRaw1cData($cml, $object): bool
    {
        return true;
    }

    /**
     * Поиск по идентификатору из 1С
     *
     * @param string $ocid Идентификатор из 1С
     *
     * @return Supply|null
     */
    public static function searchByOcid(string $ocid): ?Supply
    {
        return static::findOne([static::getIdFieldName1c() => $ocid]);
    }

    /**
     * Чтение группы из 1С
     */
    public function getGroup1c(): ?SupplyGroup
    {
        return new SupplyGroup();
    }

    /**
     * Чтение названия поля в котором хранится идентификатор из 1С
     */
    public static function getIdFieldName1c(): string
    {
        return 'ocid';
    }

    /**
     * Поиск по OEM-номерам
     *
     * @todo Реализовать с помощью LIKE
     */
    public static function searchByOemn(): array
    {
        return [];
    }

    /**
     * Прочитать стоимость
     *
     * @param Product|null $product Товар для поиска по вершинам
     *
     * @return array|null Данные о ценах
     */
    public function readCost(Product $product = null): ?array
    {
        return static::readCostById($this->readId(), $product);
    }

    /**
     * Прочитать стоимость по идентификатору поставки
     *
     * @param string $_id Идентификатор поставки
     * @param Product|null $product Товар для поиска по вершинам
     *
     * @return int|null Стоимость (руб)
     */
    public static function readCostById(string $_id, Product $product = null): ?int
    {
        return Supply::searchById($_id)->cost ?? null;
    }

    /**
     * Найти аккаунт владельца
     *
     * @return Account|null Аккаунт владельца
     */
    public function searchAccount(): ?array
    {
        return static::searchAccountById($this->readId());
    }

    public static function DeliveryToRussian(string $dlvr, int $type = 1): string
    {
        if ($type === 1) {
            return match ($dlvr) {
                'avia' => 'доставкой самолётом',
                default => 'автоматической доставкой'
            };
        } else {
            return match ($dlvr) {
                'avia' => 'Самолёт',
                default => 'Автоматическая'
            };
        }
    }

    /**
     * Вызывается после загрузки excel-документа на сервер
     *
     * @param static|null $account Аккаунт
     * @param string|int $warehouse Склад
     *
     * @return bool Статус выполнения
     */
    public static function afterImportExcel(Account|int $account = null, string|int $warehouse = 'неизвестен'): bool
    {
        // Инициализация аккаунта
        $account = Account::initAccount($account);

        // Отправка уведомления о загрузке
        $save = Notification::_write("Загружены товары для склада \"$warehouse\"", account: $account->_key);

        // Инициализация периода
        $period = new DatePeriod(new DateTime('@' . strtotime("00:00:00")), new DateInterval('PT5M'), new DateTime('@' . strtotime("next day 00:00:00")));

        foreach($period as $date){
            // Перебор периодов

            if (($converted = $date->format('U')) > $time = time()) {
                // Найден интервал из будущего времени (предполагается, что ближайший по причине остановки выполнения далее)

                // Запись даты
                $date = (new DateTime('@' . ($converted - $time)))->format('H:i:s');

                break;
            }
        }

        if ($date instanceof DateTime) $date = '5:00';

        // Отправка уведомления об обработке
        $handle = Notification::_write("Следующее обновление товаров начнётся через $date", account: $account->_key);

        return $save && $handle;
    }

    /**
     * Вызывается после загрузки поставок из excel-документа в базу данных
     *
     * @param static|null $account Аккаунт
     * @param int $created Количество созданных документов
     * @param int $updated Количество обновлённых документов
     *
     * @return bool Статус выполнения
     */
    public static function afterLoadExcel(Account|int $account = null, int $created = 0, int $updated = 0): bool
    {
        // Инициализация параметров
        $model = new Notification;
        $account = Account::initAccount($account);

        // Инициализация часового пояса
        preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
        $timezone = $timezone[1][0];

        // Инициализация даты
        $date = (new DateTime('now', new DateTimeZone($timezone)))->format('H:i d.m.Y');

        // Настройка
        $model->text = yii::$app->controller->renderPartial('@app/views/notification/system/afterLoadExcel', compact('created', 'updated', 'date'));
        $model->type = $model::TYPE_NOTICE;
        $model->account = $account->readId();

        // Отправка
        return (bool) $model->write();
    }

    /**
     * Вызывается после загрузки поставок из 1С
     */
    public static function afterImport1c(Account|int|null $account = null): bool
    {
        // Инициализация
        $model = new Notification;

        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;
                }
            }
        }

        // Инициализация часового пояса
        preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
        $timezone = $timezone[1][0];

        $date = (new DateTime('now', new DateTimeZone($timezone)))->format('H:i d.m.Y');

        // Настройка
        $model->text = yii::$app->controller->renderPartial('@app/views/notification/system/afterImport1c', compact('date'));
        $model->type = $model::TYPE_NOTICE;

        // Отправка
        return (bool) $model->write();
    }

    /**
     * Поиск связи поставки с товаром
     *
     * Если не передать товар, то ищется любой товар с которым есть связь
     *
     * @param Product|null $product Товар с которым проверяется связь
     * @param int $limit Ограничение по максимальному количеству
     */
    public function searchConnectWithProduct(Product|null $product = null, int $limit = 1): SupplyEdgeProduct|array|null
    {
        if (isset($product)) {
            // Передан товар

            $edge = SupplyEdgeProduct::searchByVertex($this->readId(), $product->readId(), filter: ['type' => 'connect'], limit: $limit);

            return $limit === 1 ? $edge[0] ?? null : $edge;
        } else {
            // Не передан товар

            $edge = SupplyEdgeProduct::searchBySupplyId($this->readId(), $limit);

            return $limit === 1 ? $edge[0] ?? null : $edge;
        }
    }

    /**
     * Отправить письмо модератору с просьбой обработать и загрузить прайс
     *
     * @param string $file Ссылка на скачивание файла с каталогом
     * @param Account|int|null $account Аккаунт на который загружать каталог
     *
     * @return bool
     */
    public static function sendMailForImport(string $file, Account|int|null $account = null): bool
    {
        return false;

        // Отправка письма
        yii::$app->mail_system->compose()
            ->setFrom(yii::$app->params['mail']['system'])
            ->setTo(yii::$app->params['mail']['info'])
            ->setSubject('Запрос на обработку каталога')
            ->setHtmlBody(yii::$app->controller->renderPartial('/mails/import', ['file' => $file, 'account' => Account::initAccount($account)]))
            ->send();

        return false;
    }
}
