<?php

declare(strict_types=1);

namespace app\models;

use yii;

use app\models\Account;
use app\models\Product;
use app\models\SupplyEdgeProduct;
use app\models\traits\Xml2Array;

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

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' => 'Стоимость (cost)',
                'onec' => 'Данные 1С',
                'oemn' => 'OEM-номера',
                'ocid' => 'Идентификатор 1C (ocid)'
            ]
        );
    }

    /**
     * Правила
     */
    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();
    }

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

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

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

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

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

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

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

            return $model;
        }

        return null;
    }

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

    /**
     * Инициализация продукта
     *
     * @param string $catn Артикул, каталожный номер
     */
    public static function initEmpty(string $catn): Product|array
    {
        $oemn = self::searchOemn($catn);

        if (count($oemn) === 1) {
            // Передан только один артикул

            if ($model = Product::searchByCatn($catn)) {
                // Продукт уже существует

                return $model;
            }

            // Запись пустого продукта
            return Product::writeEmpty($catn);
        }

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

        foreach ($oemn as $catn) {
            // Перебор всех найденных артикулов

            if ($model = Product::searchByCatn($catn)) {
                // Продукт уже существует

                continue;
            }

            // Запись
            if ($model = Product::writeEmpty($catn)) {
                // Записано

                // Запись в массив сохранённых моделей
                $models[] = $model;
            }
        }

        return $models;
    }

    /**
     * Поиск 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::search($_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 => 'Автоматическая'
            };
        }
    }
}
