<?php

namespace App\Console\Commands;

use App\CatalogImport;
use App\WarehouseStock;
use Illuminate\Console\Command;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Mtc\Modules\Members\Classes\MemberManager;
use Mtc\Modules\Members\Models\Member;
use Mtc\Shop\Brand;
use Mtc\Shop\Category;
use Mtc\Shop\Item;
use Mtc\Shop\Item\Size;

class CatalogImportCommand extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'catalog:import {action : What to import ?}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Import Catalog From CSV';

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        switch ($this->argument('action')) {
            case 'stock':
                $this->handleStock();
                $this->info('Stock updated successfully!');
                break;
            case 'stock-migrate':
                $this->handleStock2();
                $this->info('Stock migrated successfully!');
                break;
            case 'product-size-migration':
                $this->handleProductSizeMigration();
                $this->info('Migration completed');
                break;
            case 'products':
                $this->handleProducts();
                $this->info('Products updated successfully!');
                break;
            case 'customers':
                if($this->confirm('This command will import customers and send a password reset email. Are you sure you want to do this ?')) {
                    $this->handleCustomers();
                    $this->info('Customers updated successfully!');
                }
                break;
            default:
                $this->error('Unknow action');
                break;
        }
    }

    protected function handleProducts()
    {
        $images = $this->getImages();
        $prices = $this->getPrices();
        $rows = $this->getProducts();

        foreach($rows as $rowIndex => $row) {
            DB::transaction(function() use($row, $prices, $images, $rowIndex) {
                if(empty($row['epos_code'])) return;

                $price = number_format((float) ($prices[$row['epos_code']] ?? NULL ?: $row['price']), 2);
                $imgs = $images["{$row['epos_code']}-{$row['epos_code']}"] ?? [];

                if(empty($price)) return;

                var_dump("{$rowIndex} > Importing {$row['epos_code']}!");

                $item = $this->handleProduct([
                    'epos_code'     => $row['epos_code'],
                    'name'          => $row['name'],
                    'search_name'   => $row['name'],
                    'description'   => $row['description'],
                    'price'         => $price,
                    'sort_price'    => $price,
                    'price_exvat'   => number_format($price / (1 + ($row['vat_rate'] / 100)), 2),
                    'sale_price'    => 0,
                    'product_type'  => '',
                    'weight'        => $row['weight'],
                    'vat_rate'                  => $row['vat_rate'],
                    'vat_deductable'            => (bool) $row['vat_rate'],
                    'deleted'                   => $row['deleted'],
                    'hidden'                    => $row['hidden'],
                    'can_be_sold_seperately'    => true,
                    'search_description'        => $row['excerpt'],
                ], "MAIN-{$row['epos_code']}");

                $item->custom()->updateOrCreate([
                    'item_id'       => $item->id,
                ], [
                    'slug'          => Str::slug($item->name),
                ]);

                foreach(($row['sizes']) as $s) {
                    foreach($images["{$row['epos_code']}-{$s['epos_code']}"] ?? [] as $img) {
                        $imgs[] = $img;
                    }

                    $price = (float) ($prices[$s['epos_code']] ?? NULL ?: $s['price']);
                    $size = $this->handleProductVariant([
                        'size'          => $s['name'],
                        'price'         => $price,
                        'price_exvat'   => number_format($price / (1 + $row['vat_rate'] / 100), 2),
                        'sale_price'    => 0,
                        'item_id'       => $item->id,
                        'PLU'      => $s['epos_code'],
                    ], "MAIN-{$s['epos_code']}");

                    $size->custom()->updateOrCreate([
                        'item_id'       => $item->id,
                        'size_id'       => $size->id,
                    ], []);
                }

                foreach(array_unique($imgs) as $key => $img) {
                    $this->handleImage([
                        'item_id'       => $item->id,
                        'default'       => empty($key),
                        'order'         => $key,
                        'zoom'          => true,
                    ], $img);
                }

                if(!empty($row['category']['ref'])) {
                    $category  = $this->handleCategory([
                        'name'              => $row['category']['name'],
                        'sub_id'            => 0,
                        'slug'              => Str::slug($row['category']['name']),
                        'display_name'      => $row['category']['name'],
                    ], "MAIN-{$row['category']['ref']}");

                    foreach($row['category']['children'] as $sub) {
                        $category  = $this->handleCategory([
                            'name'              => $sub['name'],
                            'sub_id'            => $category->id,
                            'slug'              => Str::slug($sub['name']),
                            'display_name'      => $sub['name'],
                        ], "MAIN-{$sub['ref']}");
                    }

                    $item->categories()->sync($category->id);
                }

                if(!empty($brand = $row['brand'])) {
                    $brand = Brand::query()->updateOrCreate([
                        'name'          => $brand['name'],
                    ], [
                        'hide'          => true,
                    ]);

                    $item->brands()->sync([$brand->id]);
                }

                var_dump("Item {$item->id} Updated");
            });
        }
    }

    /**
     * Undocumented function
     *
     * @param array $data
     * @param string $ref
     * @return Category
     */
    protected function handleCategory(array $data, string $ref)
    {
        return $this->handleNode(CatalogImport::TYPE_CATEGORY, [
            'name'          => $data['name'],
            'sub_id'        => $data['sub_id'],
        ], $data, $ref);
    }

    /**
     * Undocumented function
     *
     * @param array $data
     * @param string $ref
     * @return Item
     */
    protected function handleProduct(array $data, string $ref)
    {
        return $this->handleNode(CatalogImport::TYPE_ITEM, [
            'epos_code'          => $data['epos_code'],
        ], $data, $ref);
    }

    protected function handleImage(array $data, string $ref)
    {
        $node = CatalogImport::query()->firstOrNew([
            'nodeable_type'         => CatalogImport::TYPE_ITEM_IMAGE,
            'ref'                   => $ref,
        ]);

        if(!empty($node->nodeable)) {
            $nodeable = $node->nodeable;
        } else {
            $class = CatalogImport::getClass($node->nodeable_type);
            $nodeable = new $class();

            if(empty($data['name'] = $this->uploadImage($ref, $data['item_id']))) {
                return false;
            }
        }

        $nodeable->fill($data)->save();
        $node->fill([
            'nodeable_id'        => $nodeable->id,
        ])->save();

        return $nodeable;
    }

    /**
     * Undocumented function
     *
     * @param array $data
     * @param string $ref
     * @return Size
     */
    protected function handleProductVariant(array $data, string $ref)
    {
        return $this->handleNode(CatalogImport::TYPE_ITEM_SIZE, [
            'item_id'       => $data['item_id'],
            'PLU'           => $data['PLU'],
        ], $data, $ref);
    }

    protected function handleNode(string $type, array $attributes, array $values, string $ref)
    {
        $node = CatalogImport::query()->firstOrNew([
            'nodeable_type'         => $type,
            'ref'                   => $ref,
        ]);

        if(!empty($node->nodeable)) {
            $nodeable = $node->nodeable;
        } else {
            $class = CatalogImport::getClass($type);
            $nodeable = $class::query()->firstOrNew($attributes);

            if(!empty($nodeable->id)) {
                return $nodeable;
            }
        }

        $nodeable->fill($values)->save();
        $node->fill([
            'nodeable_id'        => $nodeable->id,
        ])->save();

        return $nodeable;
    }

    protected function handleStock()
    {
        $path = 'catalog/warehouses/ilkeston.csv';
        $res = [];
        $warehouse = \App\Models\Inventory\Warehouse::query()
            ->orderBy('priority', 'ASC')
            ->value('code');

        foreach($this->csvToArray(storage_path($path)) as $data) {
            $row = [
                'epos_code' => clean_page($data['Pip Code']),
                'qty' => (int) $data['Stock Quantity'],
            ];

            if(empty($row['epos_code'])) continue;

            foreach(range(0, 3) as $i) {
                $epos_code = str_pad('', $i, '0', STR_PAD_LEFT) . $row['epos_code'];

                if($i > 0
                    && (isset($res[$epos_code])
                        || !is_numeric($row['epos_code']))) continue;

                $node = CatalogImport::query()
                    ->whereHasMorph(
                        'nodeable',
                        [
                            CatalogImport::TYPE_ITEM, CatalogImport::TYPE_ITEM_SIZE
                        ]
                    )
                    ->firstWhere('ref', '=', "MAIN-{$epos_code}");

                if(empty($node)) continue;

                \App\Models\Inventory\Inventory::updateStock($node->nodeable->PLU ?? $node->nodeable->epos_code, [
                    $warehouse => $row['qty']
                ]);

                // $res[$epos_code] = $row['qty'];
                // $node->nodeable->stock = $row['qty'];
                // $node->nodeable->save();
                break;
            }
        }

        return $res;
    }

    protected function handleStock2()
    {
        $res = [];
        $warehouse = \App\Models\Inventory\Warehouse::query()
            ->orderBy('priority', 'ASC')
            ->value('code');

        Item::query()
            ->active()
            ->get()
            ->each(function ($v) use($warehouse) {
                if(empty($v->epos_code)) return;

                \App\Models\Inventory\Inventory::updateStock([$v->epos_code, 'product'], [
                    $warehouse => $v->stock
                ]);
            });

        Size::query()
            ->active()
            ->get()
            ->each(function ($v) use($warehouse) {
                if(empty($v->PLU)) return;

                \App\Models\Inventory\Inventory::updateStock([$v->PLU, 'variant'], [
                    $warehouse => $v->stock
                ]);
            });
        return $res;
    }

    protected function handleProductSizeMigration()
    {
        DB::transaction(function() {
            Item::query()
                ->active()
                ->whereDoesntHave('sizes', function($q) {
                    $q->where('hide', false);
                })
                ->each(function($v) {
                    $size = \Mtc\Shop\Item\Size::query()
                        ->create([
                            'item_id' => $v->id,
                            'size' => 'Default',
                            'form_id' => $v->form_id,
                            'stock' => $v->stock,
                            'price' => $v->price,
                            'price_exvat' => $v->price_exvat,
                            'sale_price' => $v->sale_price,
                            'sale_price_exvat' => $v->sale_price_exvat,
                            'weight' => $v->weight,
                            'quantity' => 1,
                            'pack_size' => 1,
                            'label_count' => 0,
                            'strength' => 1,
                            'colour' => $v->colour,
                            'PLU' => $v->epos_code
                        ]);

                    \Mtc\Shop\Item\SizeCustom::query()
                        ->create([
                            'item_id' => $size->item_id,
                            'size_id' => $size->id,
                            'dosage' => $v->custom->dosage ?? NULL,
                            'product_weight' => $v->custom->product_weight,
                            'dispense_quantity' => $v->custom->dispense_quantity,
                            'barcode' => $v->custom->barcode,
                            'description__product_card' => $v->custom->description__product_card,
                            'label_warnings' => $v->custom->label_warnings,
                            'cog_cost' => $v->custom->cog_cost
                        ]);

                    $v->epos_code = NULL;
                    $v->save();
                });
        });

        return [];
    }

    protected function _handleStock()
    {
        return;
        $path = 'catalog/warehouses/ilkeston.csv';

        foreach($this->csvToArray(storage_path($path)) as $data) {
            WarehouseStock::query()
                ->updateOrCreate([
                    'epos_code'         => clean_page($data['']),
                    'warehouse'         => 'ilkeston',
                ], [
                    'stock'             => (int) $data['Available '],
                ]);
        }
    }

    protected function handleCustomers()
    {
        require_once base_path('core/includes/twig.php');
        require_once base_path('core/includes/functions.twig.php');

        $path = 'catalog/customers/customers.csv';
        \Illuminate\Support\Facades\App::bind('members_auth', function ($app) {
            return new \Mtc\Modules\Members\Classes\Authenticator();
        });
        (new \Mtcmedia\Mailer\MailerServiceProvider(app()))->register();

        foreach($this->csvToArray(storage_path($path)) as $row) {
            if(is_null($data = $this->sanitizeCustomer($row))) continue;

            DB::transaction(function() use($data) {
                $manager = new MemberManager();
                /** @var Member $member */
                $member = $manager->save($data, new Member());

                if($member) {
                    \Mtc\Modules\Members\Classes\Auth::sendNewWebsiteEmail($member);
                    $this->info("User with the email {$data['email']} created!");
                } else {
                    $this->warn("An user with the email {$data['email']} already exists!");
                }
            });
        }
    }

    /**
     * Undocumented function
     *
     * @return array
     */
    protected function getPrices()
    {
        $prices = [];

        foreach($this->csvToArray(storage_path('catalog/prices.csv')) as $data) {
            $epos_code = clean_page($data['Stock Code']);
            $prices[$epos_code] = number_format((float) $data['PRICE'], 2);
        }

        return $prices;
    }

    /**
     * Undocumented function
     *
     * @return array
     */
    protected function getImages()
    {
        $images = [];

        foreach($this->csvToArray(storage_path('catalog/images.csv')) as $data) {
            $itemRef = clean_page($data['Parent Reference']);
            $sizeRef = clean_page($data['Child Reference']);
            $key = "{$itemRef}-{$sizeRef}";
            $images[$key] = array_reduce(range(1, 3), function($r, $c) use($data) {
                if(!empty($img = trim($data["Parent Product Images.{$c}"]))
                    && filter_var($img, FILTER_VALIDATE_URL)) {
                    $r[] = $img;
                }

                return $r;
            }, []);
        }

        return $images;
    }

    /**
     * Undocumented function
     *
     * @return \Iterator
     */
    protected function getProducts()
    {
        foreach(glob(storage_path('catalog/products/*')) as $path) {
            if(!preg_match('/\.xml$/i', $path)) continue;

            $xml = file_get_contents($path);
            $xml = new \SimpleXmlElement($xml);
            $rows = ($this->xmlToArray($xml)['STOCK_ITEMS']['STOCK_ITEM'] ?? []);

            if(isset($rows['STOCK_CODE'])) {
                $rows = [$rows];
            }

            foreach($rows as $row) {
                if(!empty($product = $this->sanitizeProduct($row))) {
                    yield $product;
                }
            }
        }
    }

    protected function sanitizeProduct($row)
    {
        $data = [
            'epos_code' => clean_page($row['STOCK_CODE'] ?? NULL ?: ''),
            'name' => htmlspecialchars_decode(clean_page($row['STOCK_DESC'] ?? NULL ?: '', true, true)),
            'description' => htmlspecialchars_decode(clean_page($row['LONG_DESC'] ?? NULL ?: '', true, true)),
            'excerpt' => htmlspecialchars_decode(clean_page($row['WEB_TEASER'] ?? NULL ?: '', true, true)),
            'price' => (int) ($row['SELL_PRICE']['text'] ?? NULL),
            'weight' => (float) ($row['WEIGHT'] ?? 0),
            'vat_rate' => (int) ($row['VAT_RATE'] ?? 0),
            'deleted' => (bool) ($row['DELETED'] ?? NULL),
            'hidden' => (bool) ($row['DISCONTINUED'] ?? NULL),
            'category' => NULL,
            'brand' => NULL,
            'sizes' => [],
        ];

        if(empty($data['epos_code'])
            || empty($data['name'])) {
            return [];
        }

        $category = [
            'ref' => $row['STOCK_TYPE_ID'] ?? NULL,
            'name' => htmlspecialchars_decode(clean_page($row['STOCK_TYPE']['text'] ?? NULL ?: '', true, true)),
            'children' => [],
        ];
        
        if(!empty($category['ref'])
            && !empty($category['name'])) {
            $subCategory = [
                'name' => htmlspecialchars_decode(clean_page($row['STOCK_SUB_TYPE']['text'] ?? NULL ?: '')),
                'ref' => $row['SUB_TYPE_ID'] ?? NULL,
                'children' => [],
            ];

            if(!empty($subCategory['ref'])
                && !empty($subCategory['name'])) {
                $category['children'][] = $subCategory;
            }

            $data['category'] = $category;
        }

        $brand = [
            'name' => htmlspecialchars_decode(clean_page($row['MANUFACTURER'] ?? NULL ?: '', true, true)),
        ];

        if(!empty($brand['name'])) {
            $data['brand'] = $brand;
        }

        foreach(($row['SCS']['STYLE'] ?? []) as $s) {
            $size = [
                'epos_code' => clean_page($s['SCS_ITEM']['STOCK_CODE'] ?? NULL ?: ''),
                'name' => htmlspecialchars_decode(clean_page($s['DESCRIPTION'] ?? NULL ?: '', true, true)),
                'price' => (float) ($s['SCS_ITEM']['SELL_PRICE'] ?? NULL),
            ];

            if(empty($size['epos_code'])
                || empty($size['name'])) {
                continue;
            }

            $data['sizes'][] = $size;
        }

        return $data;
    }

    protected function sanitizeCustomer($row)
    {
        $genders = [
            'mr' => 'Male',
            'mrs' => 'Female',
        ];
        $countries = [
            'UK - Mainland' => 'GB'
        ];
        $address = [
            'title' => clean_page($row['Title']),
            'firstname' => clean_page($row['Firstname']),
            'middle_name' => '',
            'lastname' => clean_page($row['Surname']),
            'address1' => clean_page($row['Address1']),
            'address2' => clean_page($row['Address2']),
            'city' => clean_page($row['Town']),
            'state' => clean_page($row['County']),
            'postcode' => clean_page($row['Post Code']),
            'country' => $countries[$row['Country']] ?? ''
        ];
        $data = [
            'company' => clean_page($row['Company Name']),
            'email' => clean_page($row['Email']),
            'phone_prefix' => '+44',
            'contact_no' => clean_page($row['Phone']),
            'gender' => $genders[strtolower($row['Title'])] ?? '',
            'first_login' => date_create_from_format('j/n/Y h:i', $row['first Registered']),
            'resetpw' => true,
            'no_password' => true,
            'password' => '',
            'address' => $address,
        ];

        if(empty($data['email'])
            || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)
            || empty($data['contact_no'])
            || empty($data['address']['firstname'])
            || empty($data['address']['lastname'])
            || empty($data['address']['postcode'])
            || empty($data['address']['country'])) {
            return NULL;
        }

        foreach($address as $key => $value) {
            $data["billing_{$key}"] = $value;
        }

        unset($data['address']);

        return $data;
    }

    public static function uploadImage($url, $id = 0)
    {
        $image_folders = [];
        include __DIR__ . '/../../../shop/includes/image_folders.php';

        $name = substr($url, strrpos($url, '/') + 1);

        if(empty($folders = $image_folders['product_folders'] ?? [])) {
            return false;
        }

        try {
            $tmpfile = tempnam(sys_get_temp_dir(), $name);
            file_put_contents($tmpfile, file_get_contents($url));

            $uploader = \Mtc\Core\Images\ImageUploader::newInstance($folders);
            $image = $uploader->uploadImage(new UploadedFile($tmpfile, $name), $id);

            if(!empty($filename = $image['name'] ?? '')) {
                return $filename;
            }
        } catch (\Throwable $th) {
        }

        return false;
    }

    /**
     * @param SimpleXMLElement $xml XML code to convert to JSON
     * @param array $options Options of the API to change JSON output
     *
     * @return array The JSON response
     */
    public static function xmlToArray($xml, $options = [])
    {
        $defaults = [
            'namespaceRecursive' => false, // Get XML doc namespaces recursively
            'removeNamespace' => true, // Remove namespace from resulting keys
            'namespaceSeparator' => ':', // Change separator to something other than a colon
            'attributePrefix' => '', // Distinguish between attributes and nodes with the same name
            'alwaysArray' => [], // Array of XML tag names which should always become arrays
            'autoArray' => true, // Create arrays for tags which appear more than once
            'textContent' => 'text', // Key used for the text content of elements
            'autoText' => true, // Skip textContent key if node has no attributes or child nodes
            'keySearch' => false, // (Optional) search and replace on tag and attribute names
            'keyReplace' => false, // (Optional) replace values for above search values
        ];
        $options = array_merge($defaults, $options);
        $namespaces = $xml->getDocNamespaces($options['namespaceRecursive']);
        $namespaces[''] = null; // Add empty base namespace

        // Get attributes from all namespaces
        $attributesArray = [];
        foreach ($namespaces as $prefix => $namespace) {
            if ($options['removeNamespace']) {
                $prefix = '';
            }
            foreach ($xml->attributes($namespace) as $attributeName => $attribute) {
                // (Optional) replace characters in attribute name
                if ($options['keySearch']) {
                    $attributeName = str_replace($options['keySearch'], $options['keyReplace'], $attributeName);
                }
                $attributeKey = $options['attributePrefix'] . ($prefix ? $prefix . $options['namespaceSeparator'] : '') . $attributeName;
                $attributesArray[$attributeKey] = (string) $attribute;
            }
        }

        // Get child nodes from all namespaces
        $tagsArray = [];
        foreach ($namespaces as $prefix => $namespace) {
            if ($options['removeNamespace']) {
                $prefix = '';
            }

            foreach ($xml->children($namespace) as $childXml) {
                // Recurse into child nodes
                $childArray = self::xmlToArray($childXml, $options);
                $childTagName = key($childArray);
                $childProperties = current($childArray);

                // Replace characters in tag name
                if ($options['keySearch']) {
                    $childTagName = str_replace($options['keySearch'], $options['keyReplace'], $childTagName);
                }

                // Add namespace prefix, if any
                if ($prefix) {
                    $childTagName = $prefix . $options['namespaceSeparator'] . $childTagName;
                }

                if (!isset($tagsArray[$childTagName])) {
                    // Only entry with this key
                    // Test if tags of this type should always be arrays, no matter the element count
                    $tagsArray[$childTagName] = in_array($childTagName, $options['alwaysArray'], true) || !$options['autoArray'] ? [$childProperties] : $childProperties;
                } elseif (is_array($tagsArray[$childTagName]) && array_keys($tagsArray[$childTagName]) === range(0, count($tagsArray[$childTagName]) - 1)) {
                    // Key already exists and is integer indexed array
                    $tagsArray[$childTagName][] = $childProperties;
                } else {
                    // Key exists so convert to integer indexed array with previous value in position 0
                    $tagsArray[$childTagName] = [$tagsArray[$childTagName], $childProperties];
                }
            }
        }

        // Get text content of node
        $textContentArray = [];
        $plainText = trim((string) $xml);
        if ($plainText !== '') {
            $textContentArray[$options['textContent']] = $plainText;
        }

        // Stick it all together
        $propertiesArray = !$options['autoText'] || $attributesArray || $tagsArray || $plainText === '' ? array_merge($attributesArray, $tagsArray, $textContentArray) : $plainText;

        // Return node as array
        return [
            $xml->getName() => $propertiesArray,
        ];
    }

    public static function csvToArray(string $path)
    {
        $handle = fopen($path,'r');
        $headers = [];

        while ( ($row = fgetcsv($handle) ) !== FALSE ) {
            if(empty($headers)) {
                $headers = $row;
                continue;
            }

            yield array_combine($headers, $row);
        }
    }
}
