<?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->call('multisite:ensure_all_products_are_multisite_ready');
                $this->call('multisite:ensure_all_cms_pages_are_multisite_ready');
                $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): void {
                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'] = static::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 (static::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(static function ($v) use ($warehouse): void {
                if (empty($v->epos_code))
                    return;

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

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

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

        return $res;
    }

    protected function handleProductSizeMigration()
    {
        DB::transaction(static function (): void {
            Item::query()
                ->active()
                ->whereDoesntHave('sizes', static function ($q): void {
                    $q->where('hide', false);
                })
                ->each(static function ($v): void {
                    $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 (static::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', static fn($app) => new \Mtc\Modules\Members\Classes\Authenticator);
        new \Mtcmedia\Mailer\MailerServiceProvider(app())->register();

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

            DB::transaction(function () use ($data): void {
                $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 (static::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 (static::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),
                static function ($r, $c) use ($data) {
                    if (
                        ! empty($img = trim((string) $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 = static::xmlToArray($xml)['STOCK_ITEMS']['STOCK_ITEM'] ?? [];

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

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

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((string) $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((string) $url, strrpos((string) $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) {
        }

        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, escape: '\\')) !== false) {
            if (empty($headers)) {
                $headers = $row;
                continue;
            }

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