<?php

namespace Mtc\Shop\Item;

use Illuminate\Database\Capsule\Manager as DB;
use Mtc\Plugins\SeoSchema\Classes\SchemaItem;
use Mtc\Shop\Brand;
use Mtc\Shop\Category;
use Mtc\Shop\CustomField;
use Mtc\Shop\CustomFieldSet;
use Mtc\Shop\Item;
use Mtc\Shop\Warning;

class CsvImport
{
    private bool $withCreate;
    private string $fileName;
    private int $createdCount = 0;
    private int $updatedCount = 0;
    private int $createdSizesCount = 0;

    /**
     * Initiates Product import
     *
     * @param bool $withCreate
     * @param string $fileName
     */
    public function __construct(bool $withCreate = false, string $fileName = 'import')
    {
        $this->withCreate = $withCreate;
        $this->fileName = $fileName;
    }

    /**
     * Runs the CSV import
     *
     * @return array
     */
    public function runImport(): array
    {
        set_time_limit(600);
        ini_set('memory_limit', '1G');

        $result = validate_csv_upload($this->fileName);
        $errors = $result['errors'];
        $handle = $result['handle'];
        $success = '';
        if (!empty($errors)) {
            return [
                'errors' => $errors,
                'success' => '',
            ];
        }
        // Perform import
        $first = true;
        $columns = [];
        $images_updated_count = 0;
        $updatedLog = '';

        while (($data = fgetcsv($handle, null, ',')) !== false) {
            if ($first) {
                $columns = $data;
                $first = false;
                continue;
            }
            $row = [];
            // Map column names with data
            foreach ($columns as $k => $columnName) {
                $row[$columnName] = trim($data[$k]);
            }

            // From here on all data is available by column name.

            $productName = $row['Product Name'];
            $eposCode = $row['Pip Code'];
            $quantity = in_array('Quantity', $columns, true) ?
                $row['Quantity'] :
                false;
            $strength = in_array('Strength', $columns, true) ?
                $row['Strength'] :
                false;
            $price = in_array('Retail Price', $columns, true) ?
                $row['Retail Price'] :
                false;
            $salePrice = in_array('Sale Price', $columns, true) && $row['Sale Price'] !== '' ?
                $row['Sale Price'] :
                false;
            $itemId = $row['Product ID'] ?? '';
            $sizeId = $row['Variation ID'] ?? '';
            $stock = in_array('Stock', $columns, true) ?
                $row['Stock'] :
                false;
            $size = null;
            $productType = in_array('Product Type', $columns, true) ?
                $row['Product Type'] :
                false;

            $product = $this->identifyProduct($itemId, $eposCode, $productName);

            $hasSize = !empty($strength) || !empty($quantity) || !empty($sizeId) || !empty($sizeName);

            if (empty($product)) {
                // Do not create a new product unless instructed to do so
                if (!$this->withCreate) {
                    continue;
                }

                $vatRate = !empty($row['VAT Code']) ? Item::$vat_codes[$row['VAT Code']] : 20;

                $product = new Item();

                $product->sort_price = $price ?? 0;
                $product->price = $price ?? 0;
                $product->price_exvat = !empty($price) ? $price / (1 + $vatRate / 100) : 0;
                $product->sale_price = $salePrice ?? 0;
                $product->sale_price_exvat = !empty($salePrice) ? $salePrice / (1 + $vatRate / 100) : 0;
                $product->vat_rate = $vatRate;

                ++$this->createdCount;
            } else {
                ++$this->updatedCount;
            }

            if (in_array('Product Name', $columns, true)) {
                $product->name = $productName;
            }

            if (in_array('Product Description', $columns, true)) {
                $product->description = $row['Product Description'];
            }

            if (in_array('Pip Code', $columns, true)) {
                $product->epos_code = $eposCode;
            }

            if (in_array('Product Type', $columns, true)) {
                $product->product_type = $productType;
            }

            if (isset($row['[Meta] title'])) {
                $product->seo_title = $row['[Meta] title'];
            }
            if (isset($row['[Meta] keywords'])) {
                $product->seo_keywords = $row['[Meta] keywords'];
            }
            if (isset($row['[Meta] description'])) {
                $product->seo_description = $row['[Meta] description'];
            }

            $product->disable_assessment_forms = !empty($row['Disable Assessment Forms']);

            $product->save();
            $updatedLog .= 'Product: ' . $product->id . ' ' . $product->name;

            // Save categories
            $categoryIds = $this->prepareCategoriesForImport($row);
            if (!empty($categoryIds)) {
                $product->categories()->detach();
                foreach ($categoryIds as $categoryId) {
                    $product->categories()->attach($categoryId);
                }
            }

            // Brands
            if (in_array('Brand name', $columns, true)) {
                $brandName = $row['Brand name'];
                $this->importBrandData($brandName, $product);
            }

            // Custom fields
            $customData = [];

            $cfSet = CustomFieldSet::getDefault();
            if ($cfSet) {
                foreach ($cfSet->fields as $cf_holder) {
                    if (isset($row["[Custom] {$cf_holder->field->name}"])) {
                        $customData[$cf_holder->field->database_field] = $row["[Custom] {$cf_holder->field->name}"];
                    }
                }
            }

            $schemaFields = CustomField::query()
                ->where('database_field', 'LIKE', 'schema_%')
                ->get();

            foreach ($schemaFields as $schemaField) {
                if (isset($row["[SEO] {$schemaField->name}"])) {
                    $customData[$schemaField->database_field] = $row["[SEO] {$schemaField->name}"];
                }
            }

            if (empty($product->custom)) {
                $product->custom()->create($customData);
            } else {
                if (!empty($customData)) {
                    $product->custom()->update($customData);
                }
            }

            if (in_array('Alt Tag Value', $columns, true)) {
                $product->images->each(function ($image) use ($row) {
                    $image->alt = $row['Alt Tag Value'];
                    $image->save();
                });
            }

            if ($price !== false) {
                $product->price = $price;
                $product->price_exvat = (!empty($price)) ? $price / (1 + $product->vat_rate / 100) : 0;
            }
            if ($salePrice !== false) {
                $product->sale_price = $salePrice;
                $product->sale_price_exvat = (!empty($salePrice)) ? $salePrice / (1 + $product->vat_rate / 100) : 0;
            }

            if (!$hasSize) {
                // Product has no variations
                if ($stock !== false) {
                    $product->stock = $stock;
                }

                $product->save();

            } else {

                $sizeName = $row['Variation Name'];
                $labelCount = $row['No of Labels'];
                $packSize = $row['Pack Size'];
                $labelTitle = $row['Label title'];

                // Product has variations
                $newSize = false;
                // Size can be found previously by PIP code. If not then see if we have size found by Size ID column
                if (empty($size) && (empty($sizeId) || !$size = Size::query()->find($sizeId))) {
                    if (empty($eposCode) || !$size = Size::query()->where('PLU', $eposCode)->first()) {
                        $size = Size::query()
                            ->where('item_id', $product->id)
                            ->where('strength', $strength)
                            ->where('quantity', $quantity)
                            ->first();
                        if (empty($size)) {
                            $size = Size::query()
                                ->create([
                                    'item_id' => $product->id,
                                    'strength' => $strength,
                                    'quantity' => $quantity,
                                    'size' => $sizeName ?: $strength . ' ' . $quantity,
                                    'pack_size' => $packSize,
                                    'label_count' => $labelCount ?: '1',
                                ]);
                            $newSize = true;
                        }
                    }
                }

                // New size will be created
                if ($newSize) {
                    ++$this->createdSizesCount;
                }

                if (in_array('No of Labels', $columns, true)) {
                    $size->label_count = $labelCount;
                }
                if (in_array('Pack Size', $columns, true)) {
                    $size->pack_size = $packSize;
                }

                if ($price !== false) {
                    $size->price = $price;
                    $size->price_exvat = (!empty($price)) ? $price / (1 + $product->vat_rate / 100) : 0;
                } elseif ($newSize) {
                    $size->price = $product->price;
                    $size->price_exvat = $product->price_exvat;
                }
                if ($salePrice !== false) {
                    $size->sale_price = $salePrice;
                    $size->sale_price_exvat = (!empty($salePrice)) ? $salePrice / (1 + $product->vat_rate / 100) : 0;
                } elseif ($newSize) {
                    $size->sale_price = $product->sale_price;
                    $size->sale_price_exvat = $product->sale_price_exvat;
                }
                if (!empty($eposCode)) {
                    $size->PLU = $eposCode;
                }
                if ($stock !== false) {
                    $size->stock = $stock;
                } elseif ($newSize) {
                    $size->stock = $product->stock;
                }

                if ($strength !== false) {
                    $size->strength = $strength;
                }

                if ($quantity !== false) {
                    $size->quantity = $quantity;
                }

                if (!empty($sizeName)) {
                    $size->size = $sizeName;
                } else {
                    $size->size = $size->strength . (!empty($size->strength) && !empty($size->quantity) ? ' x ' : '') . $size->quantity;
                }

                $size->save();

                if ($newSize) {
                    $customData['label_title'] = $labelTitle ?: '';
                } else {
                    if (in_array('Label title', $columns, true)) {
                        $customData['label_title'] = $labelTitle;
                    }
                }

                if (DB::table('items_sizes_custom')->where('size_id', $size->id)->count() > 0) {
                    if (!empty($customData)) {
                        DB::table('items_sizes_custom')
                            ->where('size_id', $size->id)
                            ->update($customData);
                    }
                } else {
                    $customData['item_id'] = $product->id;
                    $customData['size_id'] = $size->id;
                    DB::table('items_sizes_custom')
                        ->insert($customData);
                }

                // Update product stock

                $product->stock = Size::query()
                    ->where('item_id', $product->id)
                    ->sum('stock');
                $product->save();

                $updatedLog .= ' | Variation: ' . $size->id . ' ' . $size->size . '<br />';
            }

            if (in_array('Published', $columns, true)) {
                // 'hidden' is reserved Laravel attribute so need to update straight in DB, otherwise glitches
                $updateHidden = null;
                if (strtolower($row['Published']) === 'yes') {
                    $updateHidden = 0;
                } elseif (strtolower($row['Published']) === 'no') {
                    $updateHidden = 1;
                }
                if ($updateHidden !== null) {
                    if ($hasSize) {
                        if (!empty($size)) {
                            DB::table('items_sizes')
                                ->where('id', $size->id)
                                ->update([
                                    'hide' => $updateHidden
                                ]);
                        }
                    } else {
                        DB::table('items')
                            ->where('id', $product->id)
                            ->update([
                                'hidden' => $updateHidden
                            ]);
                    }
                }
            }

            SchemaItem::buildCache(new \Item($product->id));

            $updatedLog .= '<br />';

            if (in_array('Warnings', $columns, true)) {
                $warningIds = Warning::importWarnings($row['Warnings']);
                $product->warnings()
                    ->syncWithoutDetaching($warningIds);
            }

            if (in_array('Product GTIN-13', $columns, true)) {
                if ($product->importImageFromPath('uploads/images/import', str_pad($customData['gtin_13'], 13, '0', STR_PAD_LEFT), false)) {
                    ++$images_updated_count;
                }
            }
        }
        if (file_exists($_FILES[$this->fileName]['tmp_name'])) {
            unlink($_FILES[$this->fileName]['tmp_name']);
        }
        fclose($handle);
        $success .= 'Import successful!<br />';
        $success .= 'Updated products: ' . $this->updatedCount . '<br />' . $updatedLog;
        $success .= 'Created products: ' . $this->createdCount . '<br />';
        $success .= 'Created variations: ' . $this->createdSizesCount . '<br />';
        $success .= 'Images created / updated: ' . $images_updated_count . '<br />';

        return [
            'errors' => [],
            'success' => $success,
        ];
    }

    /**
     * Tries to identify existing product
     *
     * @param string $itemId
     * @param string $eposCode
     * @param string $productName
     * @return mixed
     */
    private function identifyProduct(string $itemId, string $eposCode, string $productName): mixed
    {

        // Try locating product by:
        // 1. Product ID
        // 2. Pip Code
        //      2.1. First try finding size
        //      2.2. Then try finding product
        // 3. Product name

        if (!empty($itemId) && $product = Item::query()->where('deleted', '0')->find($itemId)) {
            return $product;
        }

        if (!empty($eposCode) && $size = Size::query()->where('PLU', $eposCode)->first()) {
            return $size->item;
        }

        if (!empty($eposCode) && $product = Item::query()->where('epos_code', $eposCode)->where('deleted', '0')->first()) {
            return $product;
        }

        if (!empty($productName) && $product = Item::query()->where('name', $productName)->where('deleted', '0')->first()) {
            return $product;
        }
        return null;
    }

    /**
     * Gets category IDs
     *
     * @param array $row
     * @return array
     */
    private function prepareCategoriesForImport(array $row): array
    {
        $categoryIds = [];

        $categoryNames = [];
        $categoryNames[] = $row['Main Category'];
        $categoryNames[] = $row['Category 1'];
        $categoryNames[] = $row['Category 2'];
        $categoryNames[] = $row['Category 3'];

        foreach ($categoryNames as $categoryName) {
            if (empty($categoryName)) {
                continue;
            }

            $tempCategory = Category::query()
                ->where('name', $categoryName)
                ->first();

            if (empty($tempCategory)) {
                $tempCategory = Category::query()
                    ->create([
                        'name' => $categoryName,
                        'sub_id' => 0,
                    ]);
            }
            $categoryIds[] = $tempCategory->id;
        }

        return $categoryIds;
    }

    /**
     * Imports brands
     *
     * @param string $brandName
     * @param Item $product
     */
    private function importBrandData(string $brandName, Item $product)
    {
        if ($brandName) {
            $brand = Brand::query()
                ->firstOrCreate([
                    'name' => $brandName
                ]);
            $product->brands()->attach($brand->id);
            return;
        }
        $product->brands()->detach();
    }
}