<?php namespace Lovata\Toolbox\Classes\Helper;

use Event;
use Queue;
use System\Models\File;
use Lovata\Toolbox\Models\Settings;
use Lovata\Toolbox\Classes\Queue\ImportItemQueue;
use Lovata\Toolbox\Traits\Helpers\TraitInitActiveLang;

/**
 * Class AbstractImportModel
 * @package Lovata\Toolbox\Classes\Helper
 * @author  Andrey Kharanenka, a.khoronenko@lovata.com, LOVATA Group
 */
abstract class AbstractImportModel
{
    use TraitInitActiveLang;

    const EVENT_BEFORE_IMPORT = 'model.beforeImport';
    const EVENT_AFTER_IMPORT = 'model.afterImport';

    const MODEL_CLASS = '';

    /** @var array */
    protected $arImportData = [];

    /** @var \Model */
    protected $obModel;

    /** @var null|string */
    protected $sExternalID = null;

    protected $bWithTrashed = false;

    /** @var array */
    protected $arImageList = [];

    protected $sPreviewImage;
    protected $bNeedUpdateImageList = false;
    protected $bNeedUpdatePreviewImage = false;

    /** @var bool */
    protected $bDeactivate = true;

    /** @var array */
    protected $arExistIDList = [];

    /** @var array */
    protected $arProcessedIDList = [];

    /**
     * ImportBrandModelFromCSV constructor.
     */
    public function __construct()
    {
        $this->initActiveLang();
    }

    /**
     * Deactivate active elements
     */
    public function deactivateElements()
    {
        if (!$this->bDeactivate) {
            return;
        }

        $arDeactivateIDList = array_diff((array) $this->arExistIDList, (array) $this->arProcessedIDList);
        if (empty($arDeactivateIDList)) {
            return;
        }

        //Get element list
        $sModelClass = static::MODEL_CLASS;
        $obElementList = $sModelClass::whereIn('external_id', $arDeactivateIDList)->get();
        foreach ($obElementList as $obElement) {
            $obElement->active = false;
            $obElement->save();
        }
    }

    /**
     * Create new item
     */
    abstract protected function createItem();

    /**
     * Update item
     */
    abstract protected function updateItem();

    /**
     * Run import item
     */
    protected function run()
    {
        $this->prepareImportData();
        $this->fireBeforeImportEvent();
        $this->prepareImportDataBeforeSave();

        $this->findByExternalID();
        if (!empty($this->obModel)) {
            $this->updateItem();
        } else {
            $this->createItem();
        }

        if (empty($this->obModel)) {
            return;
        }

        $this->processModelObject();
        Event::fire(self::EVENT_AFTER_IMPORT, [$this->obModel, $this->arImportData]);
    }

    /**
     * Find item by external ID
     */
    protected function findByExternalID()
    {
        $sModelClass = static::MODEL_CLASS;
        if ($this->bWithTrashed) {
            $this->obModel = $sModelClass::withTrashed()->getByExternalID($this->sExternalID)->first();
        } else {
            $this->obModel = $sModelClass::getByExternalID($this->sExternalID)->first();
        }
    }

    /**
     * Prepare array of import data
     */
    protected function prepareImportData()
    {
    }

    /**
     * Prepare array of import data
     */
    protected function prepareImportDataBeforeSave()
    {
        if (empty($this->arImportData)) {
            return;
        }

        $arResult = [];
        foreach ($this->arImportData as $sKey => $sValue) {
            if (is_string($sValue)) {
                $sValue = trim($sValue);
            } elseif (is_array($sValue)) {
                $sValue = array_filter($sValue);
            }

            array_set($arResult, $sKey, $sValue);
        }

        $this->arImportData = $arResult;
    }

    /**
     * Process model object after creation/updating
     */
    protected function processModelObject()
    {
        $arActiveLangList = $this->getActiveLangList();
        if (empty($arActiveLangList) || empty($this->obModel)) {
            return;
        }

        foreach ($arActiveLangList as $sLangCode) {
            if (!array_key_exists($sLangCode, $this->arImportData)) {
                continue;
            }

            foreach ($this->arImportData[$sLangCode] as $sField => $sValue) {
                $this->obModel->setAttributeTranslated($sField, $sValue, $sLangCode);
            }
        }

        $this->obModel->save();
    }

    /**
     * Fire beforeImport event and update import data array
     */
    protected function fireBeforeImportEvent()
    {
        $arEventData = [static::MODEL_CLASS, $this->arImportData];

        $arEventData = Event::fire(self::EVENT_BEFORE_IMPORT, $arEventData);
        if (empty($arEventData)) {
            return;
        }

        foreach ($arEventData as $arModelData) {
            if (empty($arModelData)) {
                continue;
            }

            foreach ($arModelData as $sKey => $sValue) {
                $this->arImportData[$sKey] = $sValue;
            }
        }
    }

    /**
     * Import obProductModel images
     */
    protected function importImageList()
    {
        if (!$this->bNeedUpdateImageList) {
            return;
        }

        if (empty($this->arImageList)) {
            $this->removeAllImages();

            return;
        }

        //Update old images
        $obImageList = $this->obModel->images;
        if (!$obImageList->isEmpty()) {
            /** @var File $obImage */
            foreach ($obImageList as $obImage) {
                $sFilePath = array_shift($this->arImageList);

                //Check image
                if (!empty($sFilePath) && (!file_exists($obImage->getLocalPath()) || md5_file($sFilePath) != md5_file($obImage->getLocalPath()))) {
                    try {
                        $obImage->deleteThumbs();
                    } catch (\Exception $obException) {}
                    $obImage->fromFile($sFilePath);
                    $obImage->save();
                } elseif (empty($sFilePath)) {
                    try {
                        $obImage->deleteThumbs();
                        $obImage->delete();
                    } catch (\Exception $obException) {}
                }
            }
        }

        //Create new images
        if (!empty($this->arImageList)) {
            foreach ($this->arImageList as $sFilePath) {
                $obImage = new File();
                $obImage->fromFile($sFilePath);

                $this->obModel->images()->add($obImage);
            }
        }
    }

    /**
     * Import preview image
     */
    protected function importPreviewImage()
    {
        if (!$this->bNeedUpdatePreviewImage) {
            return;
        }

        $obPreviewImage = $this->obModel->preview_image;
        if (empty($obPreviewImage) && empty($this->sPreviewImage)) {
            return;
        }

        if (empty($obPreviewImage) && !empty($this->sPreviewImage)) {
            //Create new preview
            $obPreviewImage = new File();
            $obPreviewImage->fromFile($this->sPreviewImage);
            $this->obModel->preview_image()->add($obPreviewImage);

            return;
        }

        if (!file_exists($obPreviewImage->getLocalPath())) {
            $obPreviewImage->fromFile($this->sPreviewImage);
            $obPreviewImage->save();
        } elseif (!empty($this->sPreviewImage) && file_exists($obPreviewImage->getLocalPath()) && md5_file($this->sPreviewImage) != md5_file($obPreviewImage->getLocalPath())) {
            //Update preview image
            $obPreviewImage->deleteThumbs();
            $obPreviewImage->fromFile($this->sPreviewImage);
            $obPreviewImage->save();
        } elseif (!empty($obPreviewImage) && empty($this->sPreviewImage)) {
            try {
                $obPreviewImage->deleteThumbs();
                $obPreviewImage->delete();
            } catch (\Exception $obException) {}
        }
    }

    /**
     * Remove all images
     */
    protected function removeAllImages()
    {
        //Delete old images
        $obImageList = $this->obModel->images;
        if ($obImageList->isEmpty()) {
            return;
        }

        /** @var \System\Models\File $obFile */
        foreach ($obImageList as $obFile) {
            try {
                $obFile->deleteThumbs();
                $obFile->delete();
            } catch (\Exception $obException) {}
        }
    }

    /**
     * Set active filed value, if active value is not null
     */
    protected function setActiveField()
    {
        $bActive = array_get($this->arImportData, 'active');
        if ($bActive === null) {
            $this->arImportData['active'] = true;
        } else {
            $this->arImportData['active'] = $this->processBooleanValue($bActive);
        }
    }

    /**
     * @param string $sValue
     * @return bool
     */
    protected function processBooleanValue($sValue) : bool
    {
        if (is_string($sValue) && $sValue == 'false') {
            return false;
        } else {
            return (bool) $sValue;
        }
    }

    /**
     * Create queue job with import single item
     * @throws \Throwable
     */
    protected function createJob()
    {
        $sQueueName = Settings::getValue('import_queue_name');

        $arQueueData = [
            'class' => static::class,
            'data'  => $this->arImportData,
        ];

        if (empty($sQueueName)) {
            Queue::push(ImportItemQueue::class, $arQueueData);
        } else {
            Queue::pushOn($sQueueName, ImportItemQueue::class, $arQueueData);
        }
    }
}
