<?php

namespace auctionforge\inc\import;

use DateTime;
use Exception;
use Polylang;
use auctionforge\inc\abs\Importer;
use auctionforge\inc\BidSpiritRequest;
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\SettingsHelper;
use auctionforge\inc\InstanceDB;
use auctionforge\inc\model\Auction;
use auctionforge\inc\model\Lot;
use auctionforge\inc\model\LotMetaData;
use WP_Post;

class LotList extends Importer
{
    /**
     * Id current auction
     * @var int
     */
    private $auctionId;
    private $auctionTerm;
    private $active;
    private $items;
    private $categories;
    private $priority;

    private $auctionSlugs;
    private $createDatetime;

    /**
     * Initialize SessionId to Bidspirit API and run process import
     * @param $active
     * @return void
     */
    public function init($active = true) {
        $this->setActive($active);
        $this->deleteOld();
        $this->setAuctions();

        /**
         * Slow place
         */
        $this->run();
    }

    /**
     * Run processing auctions
     * @return void
     */
    private function run() {
        if (!empty($this->getAuctions())) {
            if ($this->isActive()) {
                $auctions = Auction::getActiveAuction();
                $auctions = $this->mergeWithShops($auctions);
            } else {
                $auctions = Auction::getArchiveAuction();
            }

            $auctions = Auction::getTermFields($auctions, 'auction_id');
            $hasSyncLogger = class_exists('\\auctionforge\\inc\\sync\\SyncLogger');
            $auctionList = array_reverse($this->getAuctions());
            $total = count($auctionList);
            $current = 0;

            foreach ($auctionList as $auction) {
                $current++;
                $excludeAuction = SettingsHelper::getAuctionExclude();
                $this->setAuctionId($auction);

                if (in_array($this->getAuctionId(), $auctions) && !in_array($this->getAuctionId(), $excludeAuction)) {
                    if ($hasSyncLogger) {
                        \auctionforge\inc\sync\SyncLogger::log('worker', "  Processing auction {$current}/{$total}: {$this->getAuctionId()}", 'info', $this->getAuctionId());
                    }
                    if ($this->auctionProcessing()) {
                        $this->autoExcluderForAuction($excludeAuction);
                    }
                } else {
                    if ($hasSyncLogger) {
                        \auctionforge\inc\sync\SyncLogger::log('worker', "  Skipping auction {$this->getAuctionId()} (excluded or not matched)", 'info', $this->getAuctionId());
                    }
                    Debug::logData([
                        "auctionId" => $this->getAuctionId(),
                        "isExcluded" => in_array($this->getAuctionId(), $excludeAuction)
                    ]);
                }
            }

            $this->deleteOldestRecords(
                $this->getAuctionSlugs(),
                $this->getCreateDatetime()
            );
        }
    }

    public function singleRun($auctionId) {
        $auctions = [$auctionId];

        foreach ($auctions as $auction) {
            $this->setAuctionId($auction);
            $this->auctionProcessing();
        }

        $this->deleteOldestRecords(
            $this->getAuctionSlugs(),
            $this->getCreateDatetime()
        );
    }

    /**
     * @param  mixed  $responseData
     * @param  array  $additionalData
     */
    public function setResponseData($responseData, $additionalData = array(), $secondLanguageData = array())
    {
        $this->responseData = new \stdClass();
        $responseData = json_decode($responseData['response']);

        if (!empty($additionalData['response']) && !empty($responseData->response->items)) {
            $additionalData = json_decode($additionalData['response']);

            $this->setItems($responseData->response->items, false);
            $this->setCategories($responseData->response->categories, false);
            foreach ($responseData->response->items as $key => $item) {
                $item->leadingBid = $additionalData->response->itemsInfo->{$item->itemIndex};
                $item->soldBid = $additionalData->response->itemsInfo->{$item->itemIndex};
                $responseData->response->items[$key] = $item;
            }
        }

        if (!empty($secondLanguageData['response']) && !empty($responseData->response->items)) {
            $secondLanguageData = json_decode($secondLanguageData['response']);

            $this->setItems($secondLanguageData->response->items, true);
            $this->setCategories($secondLanguageData->response->categories, true);
            foreach ($responseData->response->items as $key => $item) {
                $itemSecond = $secondLanguageData->response->items[$key];

                if (isset($itemSecond->name)) {
                    $item->nameL2 = $itemSecond->name;
                }
                if (isset($itemSecond->description)) {
                    $item->descriptionL2 = $itemSecond->description;
                }
                if (isset($itemSecond->author)) {
                    $item->authorL2 = $itemSecond->author;
                }

                $responseData->response->items[$key] = $item;
            }
        }

        if (!empty($responseData->response)) {
            $this->responseData = $responseData->response;
        }
    }

    /**
     * Submit a request to bidspirit, process the response and save the result
     * @return bool
     */
    private function auctionProcessing() {
        // Heartbeat the active bulk-sync job so the safety cron's stale-job
        // timeout (default 600s) doesn't auto-fail us during long re-syncs.
        if (!empty($GLOBALS['af_bulk_sync_job_id'])
            && class_exists('\\auctionforge\\inc\\sync\\SyncQueue')) {
            try {
                \auctionforge\inc\sync\SyncQueue::heartbeat((int) $GLOBALS['af_bulk_sync_job_id']);
            } catch (\Throwable $t) {
                // Heartbeat failures are non-fatal — keep going.
            }
        }

        $auctionId = $this->getAuctionId();
        $hasSyncLogger = class_exists('\\auctionforge\\inc\\sync\\SyncLogger');

        $mlSupport = new MultiLanguageSupport();

        $action = "items/getAuctionItems.api";
        $action2 = "items/getAllItemsInfoForAuction.api";
        $data = array(
            "auctionId" => $auctionId,
            self::FIELD_LANGUAGE => MultiLanguageSupport::getDefaultLangCode()
        );

        if ($hasSyncLogger) {
            \auctionforge\inc\sync\SyncLogger::log('worker', "  Fetching lots from BidSpirit API for auction {$auctionId}...", 'info', $auctionId);
        }
        $apiStart = microtime(true);
        $response = BidSpiritRequest::send($action, $data);
        $response2 = BidSpiritRequest::send($action2, $data);

        $this->setErrors($response["response"]);
        $this->setErrors($response2["response"]);

        if (defined("AUCTIONFORGE_RESPONSE_LOG")) {
            Debug::logData($response["response"], "{$action}: response");
            Debug::logData($response2["response"], "{$action}: response");
        }

        if ($mlSupport->isLotSupport()) {
            $dataSecond = array(
                "auctionId" => $auctionId,
                self::FIELD_LANGUAGE => MultiLanguageSupport::getSecondLangCode()
            );
            $responseSecond = BidSpiritRequest::send($action, $dataSecond);
        } else {
            $responseSecond = array();
        }

        $apiDuration = round(microtime(true) - $apiStart, 1);

        if (empty($this->getErrors())) {
            if ($hasSyncLogger) {
                \auctionforge\inc\sync\SyncLogger::log('worker', "  API response received for auction {$auctionId} ({$apiDuration}s) — processing data...", 'info', $auctionId);
            }
            $this->setResponseData($response, $response2, $responseSecond);

            /**
             * Slow place debug
             */
            $this->prepareResponse();

            if ($hasSyncLogger) {
                \auctionforge\inc\sync\SyncLogger::log('worker', "  Data staged to import_history for auction {$auctionId}", 'info', $auctionId);
            }
            return true;
        } else {
            $data = $this->getErrors();
            if ($hasSyncLogger) {
                \auctionforge\inc\sync\SyncLogger::log('worker', "  API errors for auction {$auctionId} ({$apiDuration}s): " . wp_json_encode($data), 'warning', $auctionId);
            }
            $this->setLoggedInfo(array(
                "auction_id_" . $auctionId => array(
                    "items/getAuctionItems" => $data
                )
            ));

            $auctionErrors = new AuctionErrors($auctionId, $data);
            $auctionErrors->run();
            $this->clearErrorData();
            return false;
        }
    }

    private function prepareResponse()
    {
        $db = InstanceDB::getInstanceDB();
        $autoCommit = SettingsHelper::isChecked(Settings::AUTO_COMMIT_DISABLED, 'off');
        wp_defer_comment_counting(true);

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

        $this->readResponse();

        wp_defer_comment_counting(false);
        if ($autoCommit) {
            $db->query('COMMIT;');
        }
    }

    /**
     * @param $excludeAuction
     * @return void
     */
    private function autoExcluderForAuction($excludeAuction)
    {
        $auction = $this->getAuctionTerm();
        if (!($auction instanceof \WP_Term)) {
            return;
        }

        $auctionDays = SettingsHelper::getTermOption($auction->term_id, 'auction_days');
        if (is_array($auctionDays) && !empty($auctionDays[count($auctionDays) - 1])) {
            $auctionDay = $auctionDays[0];
            $dateTime = $auctionDay->date . 'T' . $auctionDay->time;
            $oldestDays = SettingsHelper::getOption(Settings::OLDEST_DAYS_NAME, 30);
            try {
                $auctionStart = new DateTime($dateTime);
                $nowTime = new DateTime();
            } catch (Exception $e) {
                $auctionStart = new DateTime();
                $nowTime = new DateTime();
            }

            $auctionStart->modify('+' . $oldestDays . ' days');
            if ($nowTime->getTimestamp() > $auctionStart->getTimestamp()) {
                if ($excludeAuction[0] == "0") {
                    unset($excludeAuction[0]);
                }
                
                if (!Auction::isAuctionShop($auction->term_id)) {
                    SettingsHelper::updateAuctionExclude($this->getAuctionId(), $excludeAuction);
                }
            }
        }
    }

    private function readResponse()
    {
//        $auctions = Auction::getActiveAuction();
//        $auctions = Auction::getTermFields($auctions, 'auction_id');
        $data = $this->getResponseData();

        if (PolylangSupport::isPolylangActive() && PolylangSupport::isFullSupport()) {
            $defaultLang = MultiLanguageSupport::getDefaultLangCode();
        } else {
            $defaultLang = false;
        }
        $term = Auction::findAuctionById($this->getAuctionId(), $defaultLang);

        $this->setAuctionTerm($term);

        $priority = $this->getPriority();

        $allLots = Auction::getAllPosts($term->term_id, array(
            SettingsHelper::STATUS_PUBLISH,
            SettingsHelper::STATUS_FUTURE
        ));
        $itemIndexes = $this->collectionLotIndexes($allLots);
        if (!empty($data->items)) {
            $lots = array();

            $this->setCreateDatetime(date(Importer::DATE_FORMAT));
            foreach ($data->items as $item) {
                $item->auctionSlug = $this->getAuctionId();
                $this->setAuctionSlugs($item->auctionSlug);
                $item->syncTimes = 0;
                $item->priority = $priority;
                $item->createDatetime = date(Importer::DATE_FORMAT);

                $lot = apply_filters('auctionforge_lot_instance', new Lot());
                $lot->loadAttributes($item);
                $lots[] = $lot;
                if ($key = array_search($lot->getItemIndex(), $itemIndexes)) {
                    unset($itemIndexes[$key]);
                }
            }
            $result = Lot::bulkSave($lots);
            if ($result) {
                $this->setLoggedInfo(array(
                    "auction_id_" . $this->getAuctionId() => array(
                        "new_records" => count($lots)
                    )
                ));
            }

            $this->movedToDraft($itemIndexes);
        } else {
            if (empty($data->items)) {
                Debug::logData($this->getAuctionId(), "Auction is skipped. Auction is empty");
            } else {
                Debug::logData($this->getAuctionId(), "Auction is skipped. pdf-replace");
            }
        }

        if (!empty($data->categories)) {
            $this->recordTags($term->term_id, $data->categories);
            if (PolylangSupport::isPolylangActive() && PolylangSupport::isFullSupport()) {
                $auctionTermTranslations = pll_get_term_translations($term->term_id);
                $secondLang = MultiLanguageSupport::getSecondLangCode();

                if (!empty($auctionTermTranslations[$secondLang])) {
                    $this->recordTags($auctionTermTranslations[$secondLang], $this->getCategories($secondLang));
                }

                $otherLanguages = PolylangSupport::getSupportedLanguages();
                if (!empty($otherLanguages['other'])) {
                    foreach ($otherLanguages['other'] as $otherLanguage) {
                        if (isset($auctionTermTranslations[$otherLanguage])) {
                            $this->recordTags($auctionTermTranslations[$otherLanguage], $data->categories);
                        }
                    }
                }
            }

            do_action('auctionforge_after_tag_processing', $data, $this->getAuctionId());
        }
    }

    private function recordTags($termId, $categories)
    {
        if (!empty($categories)) {
            $data = array();
            foreach ($categories as $category) {
                $data[$category->id] = htmlspecialchars(addslashes($category->name));
            }
            $res = update_term_meta($termId, Auction::META_FIELD_TAGS, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_HEX_QUOT));

            if ($res instanceof \WP_Error) {
                Debug::logData($data, 'categories-records-crash');
            }
        }
    }

    /**
     * @param $allLots WP_Post[]
     */
    private function collectionLotIndexes($allLots)
    {
        $indexes = array();
        if (!empty($allLots)) {
            foreach ($allLots as $lot) {
                $lotId = LotMetaData::getMetaField('itemIndex', $lot->ID);
                $indexes[$lot->ID] = $lotId;
            }
        }
        return $indexes;
    }

    /**
     * Delete old records on table
     * @return void
     */
    private function deleteOld() {
        $db = InstanceDB::getInstanceDB();
        $date = new DateTime();
        $date->modify('- ' . RECORDS_EXPIRE . ' day');
        $strTime = $date->format(self::DATE_FORMAT);

        InstanceDB::unlockTable();

        $sql = "DELETE FROM " . $db->prefix . $this->getTableName() . " WHERE
        ((`syncDatetime` <= '" . $strTime . "' AND `syncDatetime` != '') AND `syncStart` = 0)
        OR (`postId` = 0 AND `syncDatetime` = '')
        OR (`createDatetime` <= '" . $strTime . "' AND `syncTimes` = 3)";
        $db->query($sql);
    }

    private function deleteOldestRecords($auctionSlugs, $datetime)
    {
        $autoCommit = SettingsHelper::isChecked(Settings::AUTO_COMMIT_DISABLED, 'off');
        $db = InstanceDB::getInstanceDB();

        $slugsGroup = '';
        if (!empty($auctionSlugs)) {
            foreach ($auctionSlugs as $auctionSlug) {
                $slugsGroup .= ",'$auctionSlug'";
            }
            $slugsGroup = ltrim($slugsGroup, ',');
            $sql = "DELETE FROM " . $db->prefix . Importer::TABLE_NAME . " WHERE
            ((`auctionSlug` IN (" . $slugsGroup . ") AND `postId` IS NULL)
            AND `syncStart` = 0 AND `createDatetime` < '%s')";
            $sql = $db->prepare($sql, $datetime);
            $res = $db->query($sql);

            if ($autoCommit) {
                $db->query('COMMIT;');
            }
        }
    }

    /**
     * Get current auction id
     * @return mixed
     */
    public function getAuctionId()
    {
        return $this->auctionId;
    }

    /**
     * Set Id for current auction
     * @param mixed $auctionId
     */
    private function setAuctionId($auctionId)
    {
        $this->auctionId = $auctionId;
    }

    /**
     * @return bool
     */
    public function isActive()
    {
        return $this->active;
    }

    /**
     * @param bool $active
     */
    public function setActive($active)
    {
        $this->active = $active;
    }

    private function movedToDraft($items)
    {
        if (!empty($items)) {
            foreach ($items as $key => $item) {
                if (PolylangSupport::isPolylangActive() && PolylangSupport::isFullSupport()) {
                    $secondLangCode = MultiLanguageSupport::getSecondLangCode();
                    $secondPostId = pll_get_post($key, $secondLangCode);

                    $data = array(
                        'ID' => $secondPostId,
                        'post_status' => 'draft'
                    );
                    wp_update_post($data);
                }
                $data = array(
                    'ID' => $key,
                    'post_status' => 'draft'
                );
                wp_update_post($data);
            }

            HummingbirdSupport::clearAllCache();
        }
    }

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

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

    /**
     * @return mixed
     */
    public function getItems($lang)
    {
        return $this->items[$lang];
    }

    /**
     * @param  mixed  $items
     */
    public function setItems($items, $isSecondLanguage)
    {
        $langCode = SettingsHelper::getOption('lang', 'en');
        if (PolylangSupport::isPolylangActive() && PolylangSupport::isFullSupport()) {
            $langCode = pll_default_language();
            $secondLangCode = MultiLanguageSupport::getSecondLangCode();

            if ($isSecondLanguage) {
                $this->items[$secondLangCode] = $items;
            }
        }

        if (!$isSecondLanguage) {
            $this->items[$langCode] = $items;
        }
    }

    /**
     * @return mixed
     */
    public function getCategories($lang)
    {
        return $this->categories[$lang];
    }

    /**
     * @param  mixed  $categories
     */
    public function setCategories($categories, $isSecondLanguage)
    {
        $langCode = SettingsHelper::getOption('lang', 'en');
        if (PolylangSupport::isPolylangActive() && PolylangSupport::isFullSupport()) {
            $langCode = pll_default_language();
            $secondLangCode = MultiLanguageSupport::getSecondLangCode();

            if ($isSecondLanguage) {
                $this->categories[$secondLangCode] = $this->prepareKey($categories);
            }
        }

        if (!$isSecondLanguage) {
            $this->categories[$langCode] = $this->prepareKey($categories);
        }
    }

    /**
     * @return mixed
     */
    public function getAuctionSlugs()
    {
        return (!empty($this->auctionSlugs) && is_array($this->auctionSlugs)) ? array_unique($this->auctionSlugs) : $this->auctionSlugs;
    }

    /**
     * @param  mixed  $auctionSlugs
     */
    public function setAuctionSlugs($auctionSlugs)
    {
        $this->auctionSlugs[] = (string)$auctionSlugs;
    }

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

    /**
     * @param  mixed  $createDatetime
     */
    public function setCreateDatetime($createDatetime)
    {
        if (is_null($this->createDatetime) || $this->createDatetime > $createDatetime) {
            $this->createDatetime = $createDatetime;
        }
    }

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

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

    private function prepareKey($data)
    {
        $newData = array();
        if (!empty($data)) {
            foreach ($data as $datum) {
                if (!empty($datum->id)) {
                    $newData[$datum->id] = $datum;
                }
            }
        }
        return $newData;
    }
}
