<?php
    
    namespace common\modules\product\models;
    
    use common\modules\language\models\Language;
    use common\modules\rubrication\models\TaxGroup;
    use common\modules\rubrication\models\TaxOption;
    use Yii;
    use yii\base\Model;
    use yii\helpers\ArrayHelper;
    
    class Import extends Model
    {
        
        public $file;
        
        public $type;
        
        public $lang;
        
        public $errors = [];
        
        public $output = [];
        
        /**
         * @inheritdoc
         */
        public function rules()
        {
            return [
                [
                    [
                        'type',
                        'lang',
                    ],
                    'required',
                ],
                [
                    [ 'lang' ],
                    'integer',
                ],
                [
                    [ 'type' ],
                    'string',
                ],
                [
                    [ 'file' ],
                    'file',
                    'extensions' => 'csv',
                ],
            ];
        }
        
        /**
         * @inheritdoc
         */
        public function attributeLabels()
        {
            return [
                'file' => Yii::t('product', 'File'),
            ];
        }
        
        public function getType()
        {
            if(!$this->type) {
                $this->type = 'products';
            }
            return $this->type;
        }
        
        public function goPrices($from = 0, $limit = NULL)
        {
            set_time_limit(0);
            
            if(!( $handle = $this->getProductsFile('uploadFilePrices') )) {
                $this->errors[] = 'File not found';
                return false;
            }
            
            $filesize = filesize(Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@uploadFilePrices'));
            if($from) {
                fseek($handle, $from);
            }
            
            $j = 0;
            
            $is_utf = ( preg_match('//u', file_get_contents(Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@uploadFilePrices'), NULL, NULL, NULL, 1000000)) );
            
            while(( empty( $limit ) || $j++ < $limit ) && ( $data = fgetcsv($handle, 10000, ";") ) !== false) {
                foreach($data as &$value) {
                    if(!$is_utf) {
                        $value = iconv('windows-1251', "UTF-8//TRANSLIT//IGNORE", $value);
                    }
                    $value = trim($value);
                }
                
                // данные строк
                $modification_code = @$data[ 0 ];
                $price = floatval(@$data[ 1 ]);
                $price_promo = floatval(@$data[ 2 ]);
                $count = intval(@$data[ 3 ]);
                $city_name = @$data[ 4 ];
                $product_title = @$data[ 5 ];
                
                if(empty ( $modification_code )) {
                    continue;
                }
                // товары в пути
                if(empty ( $city_name )) {
                    $this->output[] = 'Товар ' . $product_title . ' в пути';
                    continue;
                }
                
                if(( $productVariant = ProductVariant::find()
                                                     ->filterWhere([ 'sku' => $modification_code ])
                                                     ->one() ) === NULL
                ) {
                    $this->output[] = 'Для товара ' . $product_title . ' не найдено соотвествие';
                    continue;
                }
                // ===== Set stock ====
                if($city_name) {
                    if(( $stock = Stock::find()
                                       ->filterWhere([ 'name' => trim($city_name) ])
                                       ->one() ) === NULL
                    ) {
                        // Create stock
                        $stock = new Stock();
                        $stock->name = trim($city_name);
                        $stock->save(false);
                    }
                    
                    $productStock = ProductStock::find()
                                                ->where([
                                                    'product_variant_id' => $productVariant->product_variant_id,
                                                    'stock_id'           => $stock->stock_id,
                                                ])
                                                ->one();
                    if(!$productStock instanceof ProductStock) {
                        $productStock = new ProductStock;
                        $productStock->product_variant_id = $productVariant->product_variant_id;
                        $productStock->stock_id = $stock->stock_id;
                        $productStock->product_id = $productVariant->product_id;
                    }
                    $productStock->quantity = $count;
                    
                    $productStock->save(false);
                    $productStocks = ProductStock::find()
                                                 ->where([ 'product_variant_id' => $productVariant->product_variant_id ])
                                                 ->andWhere([
                                                     '<>',
                                                     'stock_id',
                                                     $stock->stock_id,
                                                 ])
                                                 ->all();
                    
                    $quantity = array_sum(ArrayHelper::getColumn($productStocks, 'quantity')) + $count;
                } else {
                    
                    $productStocks = ProductStock::find()
                                                 ->where([ 'product_variant_id' => $productVariant->product_variant_id ])
                                                 ->all();
                    
                    if($productStocks instanceof ProductStock) {
                        $quantity = array_sum(ArrayHelper::getColumn($productStocks, 'quantity')) + $count;
                    } else {
                        $quantity = 0;
                    }
                    
                }
                
                if($price_promo) {
                    $productVariant->price_old = $price;
                    $productVariant->price = $price_promo;
                } else {
                    $productVariant->price = $price;
                    $productVariant->price_old = $price_promo;
                }
                
                $productVariant->stock = $quantity;
                
                $productVariant->save(false);
                
                $this->output[] = '<font style="color:blue">Товар ' . $product_title . ' успешно сохранен</font>';
            }
            
            $result = [
                'end'       => feof($handle),
                'from'      => ftell($handle),
                'totalsize' => $filesize,
                'items'     => $this->output,
            
            ];
            
            fclose($handle);
            
            if($result[ 'end' ]) {
                unlink(Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@uploadFilePrices'));
            }
            
            return $result;
        }
        
        /**
         * @param string $name
         *
         * @return array
         */
        private function parseName(string $name):array
        {
            $pattern = '/^(?P<name>.*)(?:\(#(?P<remote_id>\w+)#\))?$/U';
            $name = trim($name);
            $matches = [];
            if(preg_match($pattern, $name, $matches)) {
                if(!isset( $matches[ 'remote_id' ] )) {
                    $matches[ 'remote_id' ] = '';
                }
                return $matches;
            }
            return [
                'name'      => $name,
                'remote_id' => '',
            ];
        }
        
        /**
         * @param array $catalog_names
         *
         * @return array
         * @throws \Exception
         */
        private function saveCatalog(array $catalog_names):array
        {
            $category_id = [];
            
            foreach($catalog_names as $catalog_name) {
                // ==== Set category ====
                $parsed_name = $this->parseName($catalog_name);
                if(!empty( $parsed_name[ 'remote_id' ] ) && ( $category = Category::find()
                                                                                  ->joinWith('lang')
                                                                                  ->andFilterWhere([ 'remote_id' => $parsed_name[ 'remote_id' ] ])
                                                                                  ->one() ) !== NULL
                ) {
                    if(!empty( $category->lang )) {
                        $category->lang->name = $parsed_name[ 'name' ];
                        $category->lang->save(false);
                    } else {
                        throw new \Exception('Category with ID ' . $category->category_id . ' and lang ' . Language::getCurrent()->language_id . ' doesn\'t exist');
                    }
                    
                } else {
                    // Create category
                    $category = new Category();
                    $category->generateLangs();
                    $category_langs = $category->model_langs;
                    foreach($category_langs as $category_lang) {
                        $category_lang->name = $parsed_name[ 'name' ];
                    }
                    $category->remote_id = $parsed_name[ 'remote_id' ];
                    $category->save(false);
                }
                $category_id[] = $category->category_id;
            }
            return $category_id;
        }
        
        /**
         * @param string|NULL $brand_name
         *
         * @return int|null
         * @throws \Exception
         */
        private function saveBrand(string $brand_name = NULL)
        {
            
            $parsed_name = $this->parseName($brand_name);
            if(!empty( $brand_name )) {
                /**
                 * @var Brand $brand
                 */
                if(!empty( $parsed_name[ 'remote_id' ] ) && ( $brand = Brand::find()
                                                                            ->joinWith('lang')
                                                                            ->andFilterWhere([ 'remote_id' => $parsed_name[ 'remote_id' ] ])
                                                                            ->one() ) !== NULL
                ) {
                    if(!empty( $brand->lang )) {
                        $brand->lang->name = $parsed_name[ 'name' ];
                        $brand->lang->save(false);
                    } else {
                        throw new \Exception('Brand with ID ' . $brand->brand_id . ' and lang ' . Language::getCurrent()->language_id . ' doesn\'t exist');
                    }
                    return $brand->brand_id;
                } else {
                    // Create brand
                    $brand = new Brand();
                    $brand->generateLangs();
                    $brand_langs = $brand->model_langs;
                    foreach($brand_langs as $brand_lang) {
                        $brand_lang->name = $parsed_name[ 'name' ];
                    }
                    $brand->remote_id = $parsed_name[ 'remote_id' ];
                    $brand->save(false);
                    return $brand->brand_id;
                }
            }
            return NULL;
        }
        
        /**
         * @param array $fotos
         * @param int   $product_id
         * @param int   $product_variant_id
         */
        private function saveFotos(array $fotos, int $product_id, int $product_variant_id = NULL)
        {
            if(!empty( $fotos )) {
                foreach($fotos as $foto) {
                    
                    if(empty($foto)){
                        continue;
                    }
                    $source_image = Yii::getAlias('@uploadDir') . '/product_images/' . urlencode($foto);
                    if(file_exists($source_image)) {
                        if(( $productImage = ProductImage::find()
                                                         ->andWhere([ 'image' => $foto ])
                                                         ->andWhere([ 'product_id' => $product_id ])
                                                         ->andFilterWhere([ 'product_variant_id' => $product_variant_id ])
                                                         ->one() ) === NULL
                        ) {
                            copy($source_image, Yii::getAlias('@productsDir') . "/" . $foto);
                            $productImage = new ProductImage();
                            $productImage->product_id = $product_id;
                            $productImage->product_variant_id = $product_variant_id;
                            $productImage->image = $foto;
                            $productImage->save(false);
                        }
                    }
                }
            }
        }
        
        /**
         * @param array $data
         * @param float $product_cost_old
         * @param float $product_cost
         * @param int   $product_id
         * @param array $category_id
         *
         * @return array
         */
        private function saveVariants(array $data, float $product_cost_old, int $product_id, array $category_id, float $product_cost = NULL):array
        {
            $MOD_ARRAY = [];
            for($i = 13; $i < count($data); $i++) {
                if(!empty ( $data[ $i ] )) {
                    $mod_arr = explode('=', $data[ $i ]);
                    $mod_art = $mod_arr[ 0 ];
                    $mod_art_parsed = $this->parseName($mod_art);
                    $variant_filters = explode('*', $mod_arr[ 1 ]);
                    $mod_name = $mod_arr[ 2 ];
                    if(empty( $mod_name )) {
                        $mod_name = $mod_art_parsed['name'];
                    }
                    $mod_image = $mod_arr[ 3 ];
                    $mod_stock = isset( $mod_arr[ 4 ] ) ? $mod_arr[ 4 ] : 1;
                    $mod_cost = isset( $product_cost ) ? floatval($product_cost) : 0;
                    $mod_old_cost = floatval($product_cost_old);
                    // Check product variant
                    /**
                     * @var ProductVariant $_productVariant
                     */
                    if(( $_productVariant = ProductVariant::find()
                                                          ->joinWith('lang')
                                                          ->andFilterWhere([ 'remote_id' => $mod_art_parsed['remote_id'] ])
                                                          ->andFilterWhere([ 'product_variant.product_id' => $product_id ])
                                                          ->one() ) === NULL
                    ) {
                        $_productVariant = new ProductVariant();
                        $_productVariant->product_id = $product_id;
                        if(!empty($mod_art_parsed['remote_id'])) {
                            $_productVariant->remote_id = $mod_art_parsed['remote_id'];
                        }
                        $_productVariant->generateLangs();
                        $product_variant_langs = $_productVariant->model_langs;
                        foreach($product_variant_langs as $product_variant_lang) {
                            $product_variant_lang->name = $mod_name;
                        }
                    } else {
                        if(!empty( $_productVariant->lang )) {
                            $_productVariant->lang->name = $mod_name;
                            $_productVariant->lang->save(false);
                        } else {
                            throw new \Exception('Product variant with ID ' . $_productVariant->product_variant_id . ' and lang ' . Language::getCurrent()->language_id . ' doesn\'t exist');
                        }
                    }
                    $_productVariant->product_unit_id = 1;
                    $_productVariant->sku = $mod_art_parsed['name'];
                    $_productVariant->price = $mod_cost;
                    $_productVariant->price_old = $mod_old_cost;
                    $_productVariant->stock = $mod_stock;
                    
                    if(!empty ( $variant_filters )) {
                        $variants_options = $this->saveFilters($variant_filters, 1, $category_id);
                    }
                    
                    if(isset( $variants_options ) && !empty( $variants_options )) {
                        $_productVariant->options = $variants_options;
                    }
                    
                    /**
                     * @todo set to false
                     */
                    $_productVariant->save(false);
                    
                    $MOD_ARRAY[] = $_productVariant->product_variant_id;
                    $this->saveFotos([ $mod_image ], $product_id, $_productVariant->product_variant_id);
                }
            }
            return $MOD_ARRAY;
        }
        
        //        private function debug($start_time, $message) {
        //            echo $message.': '.(time()-$start_time).'s passed';
        //        }
        
        public function goProducts($from = 0, $limit = NULL)
        {
            set_time_limit(0);
            
            if(!( $handle = $this->getProductsFile('uploadFileProducts') )) {
                $this->errors[] = 'File not found';
                return false;
            }
            
            $filesize = filesize(Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@uploadFileProducts'));
            
            if($from) {
                fseek($handle, $from);
            }
            
            $j = 0;
            
            $is_utf = ( preg_match('//u', file_get_contents(Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@uploadFileProducts'), NULL, NULL, NULL, 1000000)) );
            
            $result_items = [];
            
            while(( empty( $limit ) || $j++ < $limit ) && ( $data = fgetcsv($handle, 0, ";") ) !== false) {
                try {
                    
     
                    foreach($data as &$value) {
                        if(!$is_utf) {
                            $value = iconv('windows-1251', "UTF-8//TRANSLIT//IGNORE", $value);
                        }
                        $value = trim($value);
                    }
                    // будет всегда 19 элементов
                    for($i = 0; $i <= 18; $i++) {
                        if(!isset ( $data[ $i ] )) {
                            $data[ $i ] = NULL;
                        }
                    }
                    // 1  Группа (категория)
                    $catalog_names = explode(',', $data[ 0 ]);
                    if(empty ( $catalog_names )) {
                        $result_items[] = "Не указана категория (строка $j)";
                        continue;
                    }
                    
                    // 2  Бренд
                    $brand_name = $data[ 1 ];
                    //                if(empty ( $brand_name )) {
                    //                    $result_items[] = "Не указан бренд (строка $j)";
                    //                    continue;
                    //                }
                    
                    // 3  Название товара
                    $product_name = $data[ 2 ];
                    if(empty ( $product_name )) {
                        $result_items[] = "Не указано наименование товара (строка $j)";
                        continue;
                    }
                    
                    // 5  Описание товара
                    $product_body = $data[ 3 ];
                    
                    // 6  Фильтр
                    $filters = explode('*', $data[ 4 ]);
                    
                    // 11 Цена акция
                    $product_cost_old = floatval($data[ 6 ]);
                    
                    $product_cost = NULL;
                    // 10 Цена
                    if($product_cost_old) {
                        $product_cost_old = floatval($data[ 5 ]);
                        $product_cost = floatval($data[ 6 ]);
                    }
                    
                    // 12 Акция
                    $product_akciya = (bool) $data[ 7 ];
                    
                    // 13 Сопуд. Тов.
                    $similar = explode(',', $data[ 8 ]);
                    
                    // 14 Новинки
                    $product_new = (bool) $data[ 9 ];
                    
                    // 15 Топ продаж
                    $product_top = (bool) $data[ 10 ];
                    
                    // 17 ВИДЕО КОД
                    $product_video = $data[ 11 ];
                    
                    // 18 Галлерея фото
                    $fotos = [];
                    if(trim($data[ 12 ])) {
                        $fotos = explode(',', trim($data[ 12 ]));
                    }
                    
                    //                    $lang = \Yii::$app->session->get('export_lang', Language::getDefaultLanguage()->language_id);
                    //                    /**
                    //                     * @var Language $language
                    //                     */
                    //                    $language = Language::find()
                    //                                        ->where([ 'language_id' => $lang ])
                    //                                        ->one();
                    //                    Language::setCurrent($language->url);
                    $categories = $this->saveCatalog($catalog_names);
    
                    $brand_id = $this->saveBrand($brand_name);
                    
                    $options = [];
                    if(!empty ( $filters )) {
                        $options = $this->saveFilters($filters, 0, $categories);
                    }
                    $parsed_name = $this->parseName($product_name);
                    /**
                     * @var Product $_product
                     */
                    if(!empty( $parsed_name[ 'remote_id' ] ) && ( $_product = Product::find()
                                                                                     ->joinWith('lang')
                                                                                     ->andFilterWhere([ 'remote_id' => $parsed_name[ 'remote_id' ] ])
                                                                                     ->one() ) !== NULL
                    ) {
                        if(!empty( $_product->lang )) {
                            $_product->lang->name = $parsed_name[ 'name' ];
                            $_product->lang->description = $product_body;
                            $_product->lang->save(false);
                        } else {
                            throw new \Exception('Product with ID ' . $_product->product_id . ' and lang ' . Language::getCurrent()->language_id . ' doesn\'t exist');
                        }
                    } else {
                        $_product = new Product();
                        if(!empty($parsed_name['remote_id'])) {
                            $_product->remote_id = $parsed_name['remote_id'];
                        }
                        $_product->generateLangs();
                        $product_langs = $_product->model_langs;
                        foreach($product_langs as $product_lang) {
                            $product_lang->name = $parsed_name[ 'name' ];
                            $product_lang->description = $product_body;
                        }
                    }
                    
                    $is_new_product = empty( $_product->product_id );
                    
                    $_product->categories = $categories;
                    
                    $_product->brand_id = $brand_id;
                    
                    $_product->video = $product_video;
                    $_product->is_top = $product_top;
                    $_product->akciya = $product_akciya;
                    $_product->is_new = $product_new;
                    if(!empty( $options )) {
                        $_product->options = $options;
                    }

                    if(!empty( $_product->lang )) {
                        $product_name_inserted = $_product->lang->name;
                    } else {
                        $product_name_inserted = $_product->model_langs[ Language::$current->language_id ]->name;
                    }
                    
                    if(($_product->save(false) === false) || !$_product->transactionStatus) {
                        $result_items[] = 'Product #' . $product_name_inserted . ' not saved' . " (line $j)";
                        continue;
                    }
                    
                    $this->saveFotos($fotos, $_product->product_id);
                    // нужно для проставления характеристик относящихся к модификациям
                    
                    $vars = $this->saveVariants($data, $product_cost_old, $_product->product_id, $_product->categories, $product_cost);
//                    $_product->save(false);
                    
                    $result_items[] = "Product {$product_name_inserted} #{$_product->product_id} saved (" . ( $is_new_product ? 'new product' : 'exists product' ) . ")" . " (line $j)";
                    
                } catch(\Exception $e) {
                    $result_items[] = $e->getMessage() . '(line ' . $j . ')';
                }
                
            }
            
            $result = [
                'end'       => feof($handle),
                'from'      => ftell($handle),
                'totalsize' => $filesize,
                'items'     => $result_items,
            ];
            
            fclose($handle);
            
            if($result[ 'end' ]) {
                //                unlink(Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@uploadFileProducts'));
            }
            
            return $result;
        }
        
        private function getProductsFile($file_type)
        {
            $filename = Yii::getAlias('@uploadDir') . '/' . Yii::getAlias('@' . $file_type);
            if(!is_file($filename)) {
                $this->errors[] = "File $filename not found";
                return false;
            }
            return fopen($filename, 'r');
        }
        
        /**
         * @param     $filters       array of filters like [['pol'='мужской'],['god' =
         *                           '2013'],['volume'='25 л']*['size'='49 x 30 x
         *                           20см'],['composition'='600D полиэстер']]
         * @param int $level         0 for products and 1 for product variant
         * @param     $catalog_names array catalogs id
         *
         * @return array
         * @throws \Exception
         */
        private function saveFilters(array $filters, int $level, array $catalog_names):array
        {
            $options = [];
            foreach($filters as $filter) {

                preg_match_all('/\[(.*):(.*)\]/', $filter, $filter);

                if(empty( $filter[ 1 ][ 0 ] )) {
                    continue;
                }
                $filter_name = trim($filter[ 1 ][ 0 ]);
                $parsed_group_name = $this->parseName($filter_name);

                /**
                 * @var TaxGroup $taxGroup
                 */
                if(!empty( $parsed_group_name[ 'remote_id' ] ) && ( $taxGroup = TaxGroup::find()
                                                                                        ->joinWith('lang')
                                                                                        ->andFilterWhere([ 'remote_id' => $parsed_group_name[ 'remote_id' ] ])
                                                                                        ->one() ) !== NULL
                ) {
                    if(!empty( $taxGroup->lang )) {
                        $taxGroup->lang->name = $parsed_group_name[ 'name' ];
                        $taxGroup->lang->save(false);
                    } else {
                        throw new \Exception('Tax group with ID ' . $taxGroup->tax_group_id . ' and lang ' . Language::getCurrent()->language_id . ' doesn\'t exist');
                    }
                } else {
                    $taxGroup = new TaxGroup();
                    $taxGroup->generateLangs();
                    $tax_group_langs = $taxGroup->model_langs;
                    foreach($tax_group_langs as $tax_group_lang) {
                        $tax_group_lang->name = $parsed_group_name[ 'name' ];
                    }
                    $taxGroup->level = $level;
                    $taxGroup->categories = $catalog_names;
                    $taxGroup->is_filter = false;
                    if(!empty($parsed_group_name['remote_id'])) {
                        $taxGroup->remote_id = $parsed_group_name['remote_id'];
                    }
                    $taxGroup->save(false);
                }
                $filters_options = explode('~', $filter[ 2 ][ 0 ]);
                foreach($filters_options as $filter_options) {
                    $parsed_option_name = $this->parseName($filter_options);
                    /**
                     * @var TaxOption $option
                     */

                    if(!empty( $parsed_option_name[ 'remote_id' ] ) && ( $option = TaxOption::find()
                                                                                            ->joinWith('lang')
                                                                                            ->andFilterWhere([ 'remote_id' => $parsed_option_name[ 'remote_id' ] ])
                                                                                            ->andFilterWhere([ 'tax_group_id' => $taxGroup->tax_group_id ])
                                                                                            ->one() ) !== NULL
                    ) {
                        if(!empty( $option->lang )) {
                            $option->lang->value = $parsed_option_name[ 'name' ];
                            $option->lang->save(false);
                        } else {
                            throw new \Exception('Tax option with ID ' . $option->tax_option_id . ' and lang ' . Language::getCurrent()->language_id . ' doesn\'t exist');
                        }
                    } else {
                        // Create option
                        $option = new TaxOption();
                        $option->generateLangs();
                        $option_langs = $option->model_langs;
                        foreach($option_langs as $option_lang) {
                            $option_lang->value = $parsed_option_name[ 'name' ];
                        }
                        $option->tax_group_id = $taxGroup->tax_group_id;
                        if(!empty($parsed_option_name['remote_id'])) {
                            $option->remote_id = $parsed_option_name['remote_id'];
                        }
                        $option->save(false);
                    }
                    $options[] = $option->tax_option_id;
                }
            }
            return $options;
        }
    }