<?php

namespace auctionforge\inc\import;

use auctionforge\inc\abs\InsertLotsAbstract;
use auctionforge\inc\abs\Model;
use auctionforge\inc\controllers\BidspiritLots;
use auctionforge\inc\controllers\CachePurge;
use auctionforge\inc\controllers\HummingbirdSupport;
use auctionforge\inc\controllers\MultiLanguageSupport;
use auctionforge\inc\controllers\PolylangSupport;
use auctionforge\inc\controllers\Settings;
use auctionforge\inc\Debug;
use auctionforge\inc\helpers\Conditions;
use auctionforge\inc\helpers\LotHelper;
use auctionforge\inc\helpers\LotLogging;
use auctionforge\inc\helpers\SettingsHelper;
use auctionforge\inc\InstanceDB;
use auctionforge\inc\model\Auction;
use auctionforge\inc\model\Lot;
use auctionforge\inc\model\LotMetaData;
use WP_Post;

class InsertLotsBS extends InsertLotsAbstract
{
    /**
     * @var $logger LotLogging
     */
    private $logger;
    private $priority;
    private $forceSync;
    private $mlSupport;

    private $auctionIds;

    /**
     * InsertLots constructor.
     * @param int $priority
     */
    public function __construct($priority = 1)
    {
        $this->setPriority($priority);
        $mlSupport = new MultiLanguageSupport();
        $this->setMlSupport($mlSupport);
        if ($mlSupport->isLotSupport()) {
            $mlSupport->init();
        }
    }

    public function init($args = [])
    {
        $args = wp_parse_args($args, array(
            "limit" => RAW_LOT_LIMIT
        ));
        $limit = (int)$args["limit"];
        if ($limit == 0) {
            $limit = RAW_LOT_LIMIT;
        }
        
        $db = InstanceDB::getInstanceDB();
        $autoCommit = SettingsHelper::isChecked(Settings::AUTO_COMMIT_DISABLED, 'off');
        wp_defer_comment_counting(true);

        InstanceDB::unlockTable();

        $db->query('START TRANSACTION;');
        $rawLots = Lot::getRawLots($limit);
        $rawRecordId = wp_list_pluck($rawLots, 'id');
        $this->blockRawRecordsLots($rawRecordId);
        $db->query('COMMIT;');

        if (empty($rawLots)) {
            if (defined('WP_DEBUG') && WP_DEBUG) {
                Debug::logData("Raw lot list empty", "log", [__FILE__, __LINE__]);
            }

            return;
        }

        $rawLotsId = wp_list_pluck($rawLots, 'lotId');

        $this->setForceSync(SettingsHelper::isChecked(Settings::SYNC_FORCE_NAME, "off"));

        if ($autoCommit) {
            $db->query('SET autocommit = 0;');
        }

        $postData = Lot::findDataLots($rawLotsId, $limit);

        if (!$this->isForceSync()) {
            $filteredData = $this->rawDataFilter($rawLots, $postData);
            $rawLots = $filteredData['new'];
            $oldLots = $filteredData['old'];
            $oldIds = wp_list_pluck($oldLots, 'id');
            $this->disableUnUpdatedLots($oldIds);

            $this->setLoggedInfo(array(
                "unupdated lots" => count($oldLots)
            ));
        }

        $rawLots = array_slice($rawLots, 0, $limit);
        $rawIds = wp_list_pluck($rawLots, 'id');

        if (!empty($rawLots) && $this->blockRawLots($rawIds)) {
            $waSuspended = wp_suspend_cache_addition();
            wp_suspend_cache_addition(true);

            $_SERVER['AUCTIONFORGE_IMPORT_LOT'] = true;
            $_SERVER['HTTP_HOST'] = preg_replace( '|(?:https?:\/\/)|', '', get_option('siteurl'));

            $hummingbird = HummingbirdSupport::getInstance();
            $hummingbird->run();

            $instancesLot = array();
            foreach ($rawLots as $rawLot) {
                /**
                 * @var Lot $lot
                 * @var LotMetaData $lotMetaData
                 */
                $lot = apply_filters('auctionforge_image_lot_initial', new Lot());
                $lot->loadAttributes($rawLot);

                $instancesLot[] = $lot;
            }

            $updatedLots = 0;
            foreach ($instancesLot as $lot) {
                $this->setLogger();
                $datum = array_filter($postData, function ($item) use ($lot) {
                    return ($item->lotId == $lot->getLotId());
                });
                $datum = array_values($datum);
                if (count($datum) <= 1) {
                    if (isset($datum[0])) {
                        $datum = $datum[0];
                    } else {
                        $datum = array();
                    }
                    $postId = $this->lotProcessing($lot, $datum);

                    $cacheKey = 'auctionforge_meta_fields_' . $postId;
                    wp_cache_delete($cacheKey, Settings::OPTIONS_GROUP);

                    if (!empty($postId)) {
                        do_action('auctionforge_before_fixed_result_lot_import', $lot, $postId);
                    }
                    $this->fixResult($lot, $postId);
                    $updatedLots++;
                } else {
                    Debug::logData($datum, "Double", [__FILE__, __LINE__]);
                }
            }

            $this->setLoggedInfo(array(
                "updated lots" => $updatedLots
            ));

            wp_suspend_cache_addition($waSuspended);
        }

        $this->unblockRawRecordsLots($rawRecordId);

        wp_defer_comment_counting(false);
        if ($autoCommit) {
            $db->query('COMMIT;');
            // Restore autocommit before returning. Without this the connection is
            // left in autocommit=0 mode, so the CALLER'S next write — notably the
            // sync worker's SyncQueue::complete() status UPDATE — sits in an
            // uncommitted transaction that rolls back at request end. That made
            // catalog_sync / item_update jobs never mark 'completed' (the watchdog
            // then failed them) on sites with "Auto commit mysql disabled" ON, even
            // though the lot data itself committed in the per-batch COMMIT above.
            $db->query('SET autocommit = 1;');
        }
    }

    public static function syncLotPrecess($lot, $datum)
    {
        $syncLot = new InsertLotsBS();
        $syncLot->setAuctionIds(7532);
        $postId = $syncLot->lotProcessing($lot, $datum);

        $cacheKey = 'auctionforge_meta_fields_' . $postId;
        wp_cache_delete($cacheKey, Settings::OPTIONS_GROUP);

        if (!empty($postId)) {
            do_action('auctionforge_before_fixed_result_lot_import', $lot, $postId);
        }

        return $postId;
    }

    /**
     * Preparing data and inserting / updating post and related meta data
     * @param $lot Lot
     * @param $postMeta object
     * @return integer
     */
    private function lotProcessing($lot, $postMeta) {
        $auction = Auction::findAuctionById($lot->getAuctionId());
        if ($auction instanceof \WP_Term) {
            $auctionId = $auction->term_id;
        } else {
            $this->instanceLogger()->setErrors(["Auction term not found for auction ID: " . $lot->getAuctionId()]);
            return 0;
        }

        $this->setAuctionIds($auctionId);
        $post = null;
        if (!empty($postMeta)) {
            $post = get_post($postMeta->ID);
        }

        $lastUpdate = (int)$lot->getLastUpdate();

        $metaLoadImages = 'false';
        if (!empty($postMeta->ID)) {
//            $metaLoadImages = get_post_meta($postMeta->ID, 'load_images', true);
        }

        $postData = $this->postDataBuild($post, $lot, $auctionId);
        $postId = $this->lotInsertOrUpdate($postData, $auctionId);
        $metaModel = $this->postMetaDataBuild(get_post($postId), $lot, $auctionId);

        if (!$this->lotInsertOrUpdateMetaData($metaModel, $postId)) {
            $postId = new \WP_Error(400, "Meta data not saved");
        }

        if (!($postId instanceof \WP_Error)) {
            wp_set_object_terms($postId, array((int) $auctionId), BidspiritLots::TAX_AUCTION_TYPE, false);
            if (Conditions::isSyncImages($lot, $metaLoadImages, $postId, $lastUpdate)) {
                $this->uploadImages($lot, $postId);
            }
        } else {
            $this->instanceLogger()->setErrors([$postId->get_error_message()]);
        }

        if ($this->getMlSupport()->isLotSupport()) {
            $supportedLanguages = PolylangSupport::getSupportedLanguages();

            $lot->setLang(MultiLanguageSupport::getSecondLangCode());

            $secondPostData = apply_filters('auctionforge_multi_language_post_data', $postData, $postId, $lot);
            $secondAuctionId = pll_get_term($auctionId, MultiLanguageSupport::getSecondLangCode());

            $secondPostId = $this->lotInsertOrUpdate($secondPostData, $secondAuctionId);
            $secondPost = get_post($secondPostId);

            $metaModelSecond = $this->postMetaDataBuild($secondPost, $lot, $secondAuctionId);
            $metaModelSecond->setName($metaModelSecond->getNameL2());
            $metaModelSecond->setDescription($metaModelSecond->getDescriptionL2());
            $metaModelSecond->setAuthor($metaModelSecond->getAuthorL2());

            if (!$this->lotInsertOrUpdateMetaData($metaModelSecond, $secondPostId)) {
                $secondPostId = new \WP_Error(400, "Meta data not saved");
            }

            if (Conditions::isSyncImages($lot, $metaLoadImages, $secondPostId, $lastUpdate) && !PolylangSupport::isThumbnailSync()) {
                $this->uploadImages($lot, $secondPostId);
            }

            $langGroupPostIds[] = $secondPostId;
            $langGroupAuctionIds[] = $secondAuctionId;

            $otherLots = [];
            if (!empty($supportedLanguages['other'])) {
                foreach ($supportedLanguages['other'] as $otherLang) {
                    $lot->setLang($otherLang);

                    $otherPostData = apply_filters('auctionforge_multi_other_language_post_data', $postData, $postId, $lot, $otherLang);
                    $otherAuctionId = pll_get_term($auctionId, $otherLang);

                    $otherPostId = $this->lotInsertOrUpdate($otherPostData, $otherAuctionId);
                    $otherPost = get_post($otherPostId);

                    $metaModel = $this->postMetaDataBuild($otherPost, $lot, $otherAuctionId);

                    if (SettingsHelper::getOption(Settings::OPTIONS_OTHER_LANG, 'default') == 'second') {
                        $metaModel->setName($metaModel->getNameL2());
                        $metaModel->setDescription($metaModel->getDescriptionL2());
                        $metaModel->setAuthor($metaModel->getAuthorL2());
                    }

                    if (!$this->lotInsertOrUpdateMetaData($metaModel, $otherPostId)) {
                        $otherPostId = new \WP_Error(400, "Meta data not saved");
                    }

                    if (!($otherPostId instanceof \WP_Error)) {
                        $langGroupPostIds[] = $otherPostId;
                        $langGroupAuctionIds[] = $otherAuctionId;
                    }
                    $otherLots[$otherLang] = $otherPostId;
                }
            }

            do_action('auctionforge_second_lot_links', $postId, $secondPostId, $otherLots);

            if (!empty($langGroupPostIds) && !empty($langGroupAuctionIds) && (count($langGroupPostIds) == count($langGroupAuctionIds))) {
                foreach ($langGroupPostIds as $key => $langGroupPostId) {
                    wp_set_object_terms($langGroupPostId, array((int)$langGroupAuctionIds[$key]), BidspiritLots::TAX_AUCTION_TYPE, false);
                }
            }
        }

        return $postId;
    }

    /**
     * @param $post WP_Post
     * @param $lot Lot
     * @param $auctionId int
     * @return array
     */
    private function postDataBuild($post, $lot, $auctionId)
    {
        $postData = array();
        if (!empty($post->ID)) {
            $postData = array(
                'ID' => $post->ID
            );
        }

        $isSecondLang = false;
        $lotTitle = apply_filters('auctionforge_lot_title_field', $lot->getName(), $lot, $isSecondLang);

        if ($lotTitle != "") {
            $lotTitle = "Lot " . wp_strip_all_tags($lot->getItemIndex()) . " - " . wp_strip_all_tags($lotTitle);
        } else if ($lot->getAuthor() != "") {
            $lotTitle = "Lot " . wp_strip_all_tags($lot->getItemIndex()) . " - " . wp_strip_all_tags($lot->getAuthor());
        } else {
            $lotTitle = "Lot " . wp_strip_all_tags($lot->getItemIndex());
        }

        $lotSlug = LotHelper::chunkSlug(sanitize_title($lotTitle));
        $lotSlug = apply_filters('auctionforge_lot_name_field', $lotSlug);

        $postData = array_merge($postData, array(
            'post_title' => $lotTitle,
            'post_name' => $lotSlug
        ));
        if (Conditions::isNotSyncTitle($post)) {
            $postData = array_merge($postData, array(
                'post_title' => $post->post_title,
                'post_name' => $post->post_name
            ));
        }

        $postData = array_merge($postData, array(
            'post_content' => $lot->getDescription()
        ));
        if (Conditions::isNotSyncDesc($post)) {
            $postData = array_merge($postData, array(
                'post_content' => $post->post_content
            ));
        }

        $dateTime = $this->getDatetimeValue(intval($lot->getLastUpdate()/1000), "+00:00");

        $postStatus = SettingsHelper::getPostStatus(intval($lot->getHidden()));

        $postDataContent = [
            'post_type' => BidspiritLots::POST_TYPE,
            'post_status' => $postStatus,
            'post_author' => $this->getUserIdAdmin(),
            'post_date' => $dateTime,
            'post_date_gmt' => $dateTime,
            'meta_input' => []
        ];

        $params = json_decode($lot->getParams());
        if (!empty($params->artDocuments)) {
            $postDataContent['meta_input']['lot_artDocuments'] = is_string($params->artDocuments) ? $params->artDocuments : wp_json_encode($params->artDocuments);
        } else {
            $postDataContent['meta_input']['lot_artDocuments'] = '';
        }

        $postDataMeta = apply_filters('lot_data_meta_build', $postData, $post, $lot);
        $postDataContent = apply_filters('lot_data_content_build', $postDataContent, $post, $lot);

        return array_merge($postDataMeta, $postDataContent);
    }

    /**
     * @param $post WP_Post
     * @param $lot Lot
     * @param $auctionId int
     * @return LotMetaData
     */
    private function postMetaDataBuild($post, $lot, $auctionId)
    {
        $metaModel = apply_filters('auctionforge_lot_meta_data_instance', new LotMetaData());
        $metaModel->loadAttributes($lot->buildFields());
        $metaModel->init($post);
        $metaModel->setId($lot->getLotId());
        $metaModel->setEstimatedPriceInt();
        $metaModel->setTermId($auctionId);
        $metaModel->setName($lot->getName());
        $metaModel->setLang($lot->getLang());

        return $metaModel;
    }

    /**
     * @param $postData array
     * @return int|\WP_Error
     */
    private function lotInsertOrUpdate($postData, $auctionId)
    {
        // No 'ID' in $postData => wp_insert_post() CREATES a lot (vs. updates
        // an existing one). A brand-new lot means this auction's catalog just
        // gained its first/next item -- the transition that flips listing cards
        // from "Catalog available soon" to "View catalog" and changes counts.
        $isNewLot = empty($postData['ID']);

        $postId = wp_insert_post($postData);

        // Invalidate this lot's page + its parent catalog across every cache
        // layer (Cloudflare, Hummingbird, LiteSpeed). During a sync this queues
        // the lot and flushes once on shutdown (after the auction term is set +
        // meta written); manual edits purge immediately. Fires for new AND
        // updated lots — the importer only reaches here for lots the
        // change-detector flagged as changed this run.
        if ($postId && !is_wp_error($postId)) {
            CachePurge::purgeLot($postId, $auctionId);

            // A NEW lot also changes template-rendered auction LISTING pages
            // (home/archive/house pages) that purgeLot's targeted lot+catalog
            // URLs don't cover; otherwise a freshly-populated catalog stays
            // "available soon" on cached listings until the TTL expires. During
            // a sync purgeAuctionListings() only sets a flag -- the shutdown
            // flush coalesces every new lot in the run into ONE purgeEverything()
            // (no per-lot thrash). Plain lot updates skip it.
            if ($isNewLot) {
                CachePurge::purgeAuctionListings();
            }
        }

        return $postId;
    }

    /**
     * @param $lotMetaData LotMetaData
     * @param $postId int
     * @return bool|\WP_Error
     */
    private function lotInsertOrUpdateMetaData($lotMetaData, $postId)
    {
        $db = InstanceDB::getInstance();
        if ($lotMetaData->issetRow($postId)) {
            $result = $lotMetaData->update();
        } else {
            $row[] = $lotMetaData;
            $result = $db->wp_insert_rows_custom($row, LotMetaData::TABLE_LOT_META, LotMetaData::getMasks());
        }

        if ($result) {
            return $result;
        } else {
            return new \WP_Error(400, "Meta data not saved");
        }
    }

    /**
     * Get meta fields for the lot
     * @param $lot Lot
     * @return array
     */
    private function getMetaFields($lot) {
        $metaFields = array(
            'lot_name' => $lot->getName(),
            'lot_itemIndex' => $lot->getItemIndex(),
            'lot_paddedIndex' => $lot->getPaddedIndex(),
            'lot_author' => $lot->getAuthor(),
            'lot_categoryId' => $lot->getCategoryId(),
            'lot_details' => $lot->getDetails(),
            'lot_start_price' => $lot->getStartPrice(),
            'lot_leadingBid' => $lot->getLeadingBid(),
            'lot_soldBid' => $lot->getSoldBid(),
            'lot_auctionId' => $lot->getAuctionId(),
            'lot_estimated_price' => $lot->getEstimatedPrice(),
            'lot_estimated_price_int' => preg_replace("/^(\W)+|[\,\.]/", "", $lot->getEstimatedPrice()),
            'lot_tags' => $lot->getTags(),
            'lot_videoUrl' => $lot->getVideoUrl(),
            'lot_extraInfo' => $lot->getExtraInfo(),
            'lot_postSaleDisabled' => Model::toString($lot->getPostSaleDisabled()),
            'lot_imagesBase' => $lot->getImagesBase(),
            'lot_imagesCloned' => Model::toString($lot->getImagesCloned()),
            'lot_buyoutPrice' => $lot->getBuyoutPrice(),
            'lot_fullPriceVat' => $lot->getFullPriceVat(),
            'lot_largeDisplay' => Model::toString($lot->getLargeDisplay()),
            'lot_viewOnly' => Model::toString($lot->getViewOnly()),
            'lot_hidden' => Model::toString($lot->getHidden()),
            'lot_hiddenInPortal' => Model::toString($lot->getHiddenInPortal()),
            'lot_removedFromAuction' => Model::toString($lot->getRemovedFromAuction()),
            'lot_eroticContent' => Model::toString($lot->getEroticContent()),
            'lot_criminalContent' => Model::toString($lot->getEroticContent()),
            'lot_isGem' => Model::toString($lot->getIsGem()),
            'lot_id' => $lot->getLotId(),
            'lot_imagesListStr' => $lot->getImagesListStr(),
            'lot_params' => $lot->getParams()
        );

        // Extract artDocuments from params if present
        $params = json_decode($lot->getParams());
        if (!empty($params->artDocuments)) {
            $metaFields['lot_artDocuments'] = $params->artDocuments;
        }

        $metaFields = apply_filters('auctionforge_build_meta_fields_2', $metaFields, $lot);
        return apply_filters('auctionforge_build_meta_fields', $metaFields, $lot);
    }

    /**
     * Get site admin id
     * @return int
     */
    protected function getUserIdAdmin()
    {
        $users = get_users([
            'role' => 'administrator',
        ]);
        if (!empty($users)) {
            $users = array_values(array_reverse($users));
            return $users[0]->ID;
        } else {
            return 0;
        }
    }

    /**
     * @return LotLogging
     */
    public function instanceLogger()
    {
        return $this->logger;
    }

    public function setLogger()
    {
        $this->logger = new LotLogging();
    }

    /**
     * @return mixed
     */
    public function getPriority()
    {
        return $this->priority;
    }

    /**
     * @param mixed $priority
     */
    public function setPriority($priority)
    {
        $this->priority = $priority;
    }

    /**
     * @return boolean
     */
    public function isForceSync()
    {
        return $this->forceSync;
    }

    /**
     * @param  boolean  $forceSync
     */
    public function setForceSync($forceSync)
    {
        $this->forceSync = $forceSync;
    }

    /**
     * @return mixed
     */
    public function getMlSupport()
    {
        return $this->mlSupport;
    }

    /**
     * @param  mixed  $mlSupport
     */
    public function setMlSupport($mlSupport)
    {
        $this->mlSupport = $mlSupport;
    }

    /**
     * @return mixed
     */
    public function getAuctionIds()
    {
        if (!empty($this->auctionIds)) {
            return array_keys($this->auctionIds);
        } else {
            return array();
        }
    }

    /**
     * @param mixed $auctionId
     */
    public function setAuctionIds($auctionId)
    {
        if (!empty($auctionId)) {
            $this->auctionIds[$auctionId] = 1;
        }
    }
}