<?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\ImportEdgeSupply;
use app\models\ImportEdgeAccount;

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

use moonland\phpexcel\Excel;

use DateTime;
use DateTimeZone;

use Exception;

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

    /**
     * Количество
     *
     * Используется при выводе в корзине
     */
    public int $amnt = 0;

    /**
     * Имя коллекции
     */
    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(),
            [
                // [
                //     [
                //         'oemn'
                //     ],
                //     'arrayValidator',
                //     'message' => '{attribute} должен быть массивом.'
                // ]
            ]
        );
    }

    /**
     * После сохранения
     */
    public function afterSave($data, $vars): void
    {
        if (AccountEdgeSupply::searchByVertex(yii::$app->user->id, $this->readId())) {
            // Ребро: "АККАУНТ -> ПОСТАВКА" уже существует

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

            // Запись ребра: АККАУНТ -> ПОСТАВКА
            (new AccountEdgeSupply)->write(yii::$app->user->id, $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
        );
    }

    /**
     * Запись данных свойств по 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);

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

                $product = [$product];
            }

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

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

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

            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
     *
     * На данный момент обрабатывает только импорт из
     * файлов с расширением .excel
     */
    public function importExcel(Account|int|null $account = null): bool
    {
        // Инициализация
        $data = [];
        $created = 0;
        $updated = 0;
        $account = Account::initAccount($account);

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

            if (isset($this->file_excel_1)) {
                // Найден файл в позиции 1

                // Инициализация позиции
                $position = 1;

                // Запись в буфер
                $file = $this->file_excel_1;
            } else if (isset($this->file_excel_2)) {
                // Найден файл в позиции 2

                // Инициализация позиции
                $position = 2;

                // Запись в буфер
                $file = $this->file_excel_2;
            } else if (isset($this->file_excel_3)) {
                // Найден файл в позиции 3

                // Инициализация позиции
                $position = 3;

                // Запись в буфер
                $file = $this->file_excel_3;
            } else {
                // Не найден ни один файл

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

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

            // Сохранение на диск
            if (!file_exists($path))
                if (!mkdir($path, 0775, true))
                    throw new Exception('Не удалось создать директорию', 500);

            $file->saveAs($path = "$path/" . $filename = $file->baseName . '.' . $file->extension);

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

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

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

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

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

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

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

                        // Поиск всех артикулов (каталожных номеров)
                        $supplies = explode(',', $doc['catn'], 300);

                        // Поиск количества товаров
                        $amount = $doc['amnt'] ?? 1;

                        foreach ($supplies as $_supply) {
                            // Перебор продуктов (если catn перечислены через запятую)

                            $_supply = trim($_supply);

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

                            // Инициализация продукта
                            $supply = new static($_doc);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                $import->file = $path;
                $import->name = $filename;
                $import->pstn = $position;

                if ($import->save()) {
                    // Инстанция импорта успешно загружена

                    if (ImportEdgeAccount::write(yii::$app->user->identity->collectionName() . '/' . yii::$app->user->identity->_key, $import->collectionName() . "/$import->_key", data: ['type' => 'loaded'])) {
                        // Записано ребро: АККАУНТ -> ИНСТАНЦИЯ ПОСТАВОК

                        // Запись в журнал инстанции импорта
                        $import->journal('connect_with_account', [
                            'target' => yii::$app->user->identity->collectionName() . '/' . yii::$app->user->identity->_key
                        ]);
                    }

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

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

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

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

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

            return true;
        }

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

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

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

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

    /**
     *
     */
    public function write($context = null)
    {
        return $this->onec;
    }

    /**
     * @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 array|null Данные о ценах
    //  */
    // public static function readCostById(string $_id, Product $product = null): ?array
    // {
    //     if (isset($product)) {
    //         return SupplyEdgeProduct::searchByVertex($_id, $product->readId(), type: 'connect', limit: 1)['onec']['Цены']['Цена'];
    //     }

    //     return SupplyEdgeProduct::searchByDirection($_id, type: 'connect', limit: 1)['onec']['Цены']['Цена'];
    // }

    /**
     * Найти аккаунт владельца
     *
     * @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 int $created Количество созданных документов
     * @param int $updated Количество обновлённых документов
     */
    public static function afterImportExcel(int $created = 0, int $updated = 0): bool
    {
        // Инициализация параметров
        $model = new Notification;
        $account = yii::$app->user->identity;

        // Инициализация часового пояса
        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/afterImportExcel', compact('created', 'updated', 'date'));
        $model->type = $model::TYPE_NOTICE;

        // Отправка
        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();
    }
}
