<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Migration\Step\UrlRewrite;

use Migration\App\ProgressBar;
use Migration\App\Step\RollbackInterface;
use Migration\ResourceModel\Destination;
use Migration\ResourceModel\Document;
use Migration\ResourceModel\Record;
use Migration\ResourceModel\RecordFactory;
use Migration\ResourceModel\Source;
use Migration\Reader\MapInterface;
use Migration\Step\UrlRewrite\Model\Version191to2000\Transformer;
use Migration\Step\UrlRewrite\Model\Version191to2000\CmsPageRewrites;

/**
 * Class Version191to2000
 */
class Version191to2000 extends \Migration\Step\DatabaseStage implements RollbackInterface
{
    const SOURCE = 'core_url_rewrite';

    const DESTINATION = 'url_rewrite';
    const DESTINATION_PRODUCT_CATEGORY = 'catalog_url_rewrite_product_category';

    /**
     * @var Source
     */
    protected $source;

    /**
     * @var Destination
     */
    protected $destination;

    /**
     * @var ProgressBar\LogLevelProcessor
     */
    protected $progress;

    /**
     * @var RecordFactory
     */
    protected $recordFactory;

    /**
     * @var \Migration\Logger\Logger
     */
    protected $logger;

    /**
     * @var string
     */
    protected $stage;

    /**
     * @var array
     */
    protected $redirectTypesMapping = [
        '' => 0,
        'R' => 302,
        'RP' => 301
    ];

    /**
     * @var Transformer
     */
    private $transformer;

    /**
     * @var CmsPageRewrites
     */
    private $cmsPageRewrites;

    /**
     * Expected table structure
     * @var array
     */
    protected $structure = [
        MapInterface::TYPE_SOURCE => [
            'core_url_rewrite' => [
                'url_rewrite_id' ,
                'store_id',
                'id_path',
                'request_path',
                'target_path',
                'is_system',
                'options',
                'description',
                'category_id',
                'product_id',
            ],
        ],
        MapInterface::TYPE_DEST => [
            'url_rewrite' => [
                'url_rewrite_id',
                'entity_type',
                'entity_id',
                'request_path',
                'target_path',
                'redirect_type',
                'store_id',
                'description',
                'is_autogenerated',
                'metadata'
            ],
        ]
    ];

    /**
     * @param \Migration\Config $config
     * @param Source $source
     * @param Destination $destination
     * @param ProgressBar\LogLevelProcessor $progress
     * @param RecordFactory $factory
     * @param \Migration\Logger\Logger $logger
     * @param Transformer $transformer
     * @param CmsPageRewrites $cmsPageRewrites
     * @param $stage
     * @throws \Migration\Exception
     */
    public function __construct(
        \Migration\Config $config,
        Source $source,
        Destination $destination,
        ProgressBar\LogLevelProcessor $progress,
        RecordFactory $factory,
        \Migration\Logger\Logger $logger,
        Transformer $transformer,
        CmsPageRewrites $cmsPageRewrites,
        $stage
    ) {
        parent::__construct($config);
        $this->source = $source;
        $this->destination = $destination;
        $this->progress = $progress;
        $this->recordFactory = $factory;
        $this->stage = $stage;
        $this->logger = $logger;
        $this->transformer = $transformer;
        $this->cmsPageRewrites = $cmsPageRewrites;
    }

    /**
     * Integrity check
     *
     * @return bool
     */
    protected function integrity()
    {
        $result = true;
        $this->progress->start(1);
        $this->progress->advance();
        $sourceFieldsDiff = array_diff(
            $this->structure[MapInterface::TYPE_SOURCE][self::SOURCE],
            array_keys($this->source->getStructure(self::SOURCE)->getFields())
        );
        $destinationFieldsDiff= array_diff(
            $this->structure[MapInterface::TYPE_DEST][self::DESTINATION],
            array_keys($this->destination->getStructure(self::DESTINATION)->getFields())
        );
        if ($sourceFieldsDiff) {
            $this->logger->error(sprintf(
                'Source fields are missing. Document: %s. Fields: %s',
                self::SOURCE,
                implode(',', $sourceFieldsDiff)
            ));
            $result = false;
        }
        if ($destinationFieldsDiff) {
            $this->logger->error(sprintf(
                'Destination fields are missing. Document: %s. Fields: %s',
                self::DESTINATION,
                implode(',', $destinationFieldsDiff)
            ));
            $result = false;
        }
        if ($result) {
            $this->progress->finish();
        }
        return (bool)$result;
    }

    /**
     * Run step
     *
     * @return bool
     */
    protected function data()
    {
        $this->progress->start(
            ceil($this->source->getRecordsCount(self::SOURCE) / $this->source->getPageSize(self::SOURCE))
        );

        $sourceDocument = $this->source->getDocument(self::SOURCE);
        $destDocument = $this->destination->getDocument(self::DESTINATION);
        $destProductCategory = $this->destination->getDocument(self::DESTINATION_PRODUCT_CATEGORY);

        $this->destination->clearDocument(self::DESTINATION);
        $this->destination->clearDocument(self::DESTINATION_PRODUCT_CATEGORY);

        $pageNumber = 0;
        while (!empty($bulk = $this->source->getRecords(self::SOURCE, $pageNumber))) {
            $pageNumber++;
            $destinationRecords = $destDocument->getRecords();
            $destProductCategoryRecords = $destProductCategory->getRecords();
            foreach ($bulk as $recordData) {
                /** @var Record $record */
                $record = $this->recordFactory->create(['document' => $sourceDocument, 'data' => $recordData]);
                /** @var Record $destRecord */
                $destRecord = $this->recordFactory->create(['document' => $destDocument]);
                $this->transformer->transform($record, $destRecord);
                if ($record->getValue('is_system')
                    && $record->getValue('product_id')
                    && $record->getValue('category_id')
                    && $record->getValue('request_path') !== null
                ) {
                    $destProductCategoryRecord = $this->recordFactory->create(['document' => $destProductCategory]);
                    $destProductCategoryRecord->setValue('url_rewrite_id', $record->getValue('url_rewrite_id'));
                    $destProductCategoryRecord->setValue('category_id', $record->getValue('category_id'));
                    $destProductCategoryRecord->setValue('product_id', $record->getValue('product_id'));
                    $destProductCategoryRecords->addRecord($destProductCategoryRecord);
                }

                $destinationRecords->addRecord($destRecord);
            }

            $this->source->setLastLoadedRecord(self::SOURCE, end($bulk));
            $this->progress->advance();
            $this->destination->saveRecords(self::DESTINATION, $destinationRecords);
            $this->destination->saveRecords(self::DESTINATION_PRODUCT_CATEGORY, $destProductCategoryRecords);
        }
        $this->saveCmsPageRewrites();
        $this->progress->finish();
        return true;
    }

    /**
     * @inheritdoc
     */
    public function perform()
    {
        if (!method_exists($this, $this->stage)) {
            throw new \Migration\Exception('Invalid step configuration');
        }

        return call_user_func([$this, $this->stage]);
    }

    /**
     * Volume check
     *
     * @return bool
     */
    protected function volume()
    {
        $result = true;
        $this->progress->start(1);
        $result &= $this->source->getRecordsCount(self::SOURCE) + $this->countCmsPageRewrites() ==
            ($this->destination->getRecordsCount(self::DESTINATION));
        if (!$result) {
            $this->logger->error('Mismatch of entities in the document: url_rewrite');
        }
        $this->progress->advance();
        $this->progress->finish();
        return (bool)$result;
    }

    /**
     * @inheritdoc
     */
    public function rollback()
    {
        return true;
    }

    /**
     * Save cms page rewrites
     *
     * @return void
     */
    protected function saveCmsPageRewrites()
    {
        $this->progress->advance();
        $select = $this->cmsPageRewrites->getSelect();
        $urlRewrites = $this->source->getAdapter()->loadDataFromSelect($select);
        $this->destination->saveRecords(self::DESTINATION, $urlRewrites, ['request_path' => 'request_path']);
    }

    /**
     * Count cms page rewrites
     *
     * @return int
     */
    protected function countCmsPageRewrites()
    {
        $select = $this->cmsPageRewrites->getSelect();
        $urlRewrites = $this->source->getAdapter()->loadDataFromSelect($select);
        return count($urlRewrites);
    }
}
