<?php

namespace YSquare\GiftCert\Model;

use OxidEsales\Eshop\Core\Registry;
use OxidEdales\Eshop\Application\Model\Article;
use YSquare\GiftCert\Core\DebugLogger;
use YSquare\GiftCert\Core\GiftCertHandler;
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
use OxidEsales\EshopCommunity\Internal\Framework\Database\QueryBuilderFactoryInterface;

class Order extends Order_parent
{


    /**
     * Order gift certificates marked as used
     *
     * @var array
     */
    protected $_aGiftcertList = null;

    /**
     * @var array
     */
    protected $_aGiftcertLogList = null;

    /**
     * @var array
     */
    protected $_aGiftcertToDirectlyMailList = null;


    /****************************************
     * This part handles the redeem process
     ****************************************/

    /**
     * Marks voucher as used (oxvoucher::markAsUsed())
     * and sets them to $this->_aVoucherList.
     *
     * @param oxBasket $oBasket basket object
     * @param oxUser $oUser user object
     */
    protected function _markVouchers($oBasket, $oUser)
    {

        // Do nothing if there is no Basket or no User - cause that makes no sense, right?
        if (!$oBasket || !$oUser) {
            return;
        }

        // Mark the vouchers as normal
        $returnValue = parent::_markVouchers($oBasket, $oUser);

        // Now mark our gift certificates
        $giftcertHandler = oxNew(GiftCertHandler::class);
        $this->_aGiftcertList = $oBasket->getGiftCertList();

        // Mark each voucher as used and add a Log-Entry iff there is no other in this order already (as some payment
        // providers tend to call this function twice)
        if (is_array($this->_aGiftcertList)) {
            foreach ($this->_aGiftcertList as $giftcertId => $oGiftcert) {
                if (!$giftcertHandler->isGiftCertRedeemedInOrder($oGiftcert->ysgiftcert__oxid->value, $this->oxorder__oxid->value)) {
                    $realDiscount = $oBasket->getGiftCertRealDiscount($giftcertId)->getPrice();
                    $oGiftcert->markAsUsed($this->oxorder__oxid->value, $oUser->oxuser__oxid->value, $realDiscount);
                    // TODO: Maybe some storage of Discount to use in Mail later
                } else {
                    $logger = Registry::getLogger();
                    $logger->error('This giftcert is already used: ' . $giftcertId, [__CLASS__, __FUNCTION__]);
                }
            }
        }

        // Finally decrease the pool if it has been used AND if we have not already logged a redeem event
        // (can happen if a payment provider thinks he has to call this function twice or more)
        if (!$giftcertHandler->isPoolRedeemedInOrder($this->oxorder__oxid->value)) {
            $poolMoney = $oBasket->getGiftCertUsedPoolAmount()->getPrice();
            if ($poolMoney > 0) {
                $oUser->subtractYSGiftcertPoolAmount($poolMoney);
                $giftcertHandler->logPoolRedeem($this->oxorder__oxid->value, $oUser->oxuser__oxid->value, $poolMoney);
            }
        }

        return $returnValue;
    }

    /**
     * Creates OrderArticle objects and assigns to them basket articles.
     * Updates quantity of sold articles (\OxidEsales\Eshop\Application\Model\Article::updateSoldAmount()).
     *
     * @param array $aArticleList article list
     * @deprecated underscore prefix violates PSR12, will be renamed to "setOrderArticles" in next major
     */
    protected function _setOrderArticles($aArticleList) // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
    {
        parent::_setOrderArticles($aArticleList);

        // Fix oxprice for gift certificates
        foreach ($this->_oArticles as $oOrderArticle) {
            // Find the corresponding basket item
            foreach ($aArticleList as $oBasketItem) {
                if ($oBasketItem->getProductId() === $oOrderArticle->oxorderarticles__oxartid->value) {
                    $oArticle = $oBasketItem->getArticle();
                    if ($oArticle && $oArticle->isGiftCertArticle()) {
                        // Set oxprice from the basket item's unit price
                        $oUnitPrice = $oBasketItem->getUnitPrice();
                        $oOrderArticle->oxorderarticles__oxprice = new \OxidEsales\Eshop\Core\Field(
                            $oUnitPrice->getBruttoPrice(),
                            \OxidEsales\Eshop\Core\Field::T_RAW
                        );
                    }
                    break;
                }
            }
        }
    }

    public function getGiftcertLogs()
    {
        if (null === $this->_aGiftcertLogList) {
            $oDb = \OxidEsales\Eshop\Core\DatabaseProvider::getDb(\OxidEsales\Eshop\Core\DatabaseProvider::FETCH_MODE_ASSOC);
            $sQ = "SELECT oxid from ysgiftcertlog where ysorderid=?";

            $rs = $oDb->select($sQ, [$this->oxorder__oxid->value]);
            if ($rs != false && $rs->count() > 0) {
                while (!$rs->EOF) {
                    $giftcertlog = oxNew(\YSquare\GiftCert\Model\GiftCertLog::class);
                    $giftcertlog->load($rs->fields['oxid']);
                    $this->_aGiftcertLogList[$rs->fields['oxid']] = $giftcertlog;
                    $rs->fetchRow();
                }
            }
        }
        return $this->_aGiftcertLogList;
    }

    public function getSumTotalBeforeGiftcert()
    {
        $sum = 0;
        $logs = $this->getGiftcertLogs();
        if (is_array($logs)) {
            foreach ($this->getGiftcertLogs() as $giftcertlog) {
                $sum += $giftcertlog->ysgiftcertlog__ysamountused->value;
            }
        }
        $sum += $this->oxorder__oxtotalordersum->value;
        $price = oxNew(\OxidEsales\Eshop\Core\Price::class);
        $price->setBruttoPriceMode();
        $price->setPrice($sum);
        return $price;
    }


    /***********************************************************
     * This part handles the purchase process and the money pool
     ***********************************************************/
    public function finalizeOrder(\OxidEsales\Eshop\Application\Model\Basket $oBasket, $oUser, $blRecalculatingOrder = false)
    {

        $giftCertHandler = oxNew(GiftCertHandler::class);
        $orderId = Registry::getSession()->getVariable('sess_challenge');

        // Get all gift certs and check how they should be handled,
        // email now or later?
        // Print Option?
//        $aCodes = array();
        $aBasketItems = $oBasket->getContents();

        $session = Registry::getSession();
        DebugLogger::debug('Order::finalizeOrder', 'enter', [
            'orderId_sess_challenge' => $orderId,
            'orderId_this_getId'     => $this->getId(),
            'orderId_oxid_field'     => isset($this->oxorder__oxid) ? $this->oxorder__oxid->value : null,
            'session_id'             => $session ? $session->getId() : null,
            'request_uri'            => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : null,
            'request_cl'             => Registry::getRequest()->getRequestParameter('cl'),
            'request_fnc'            => Registry::getRequest()->getRequestParameter('fnc'),
            'basketItemCount'        => is_array($aBasketItems) ? count($aBasketItems) : 0,
            'isRecalculatingOrd'     => (bool) $blRecalculatingOrder,
            'callers'                => DebugLogger::callers(8),
        ]);

        $giftCertArticlesSeen = 0;
        $giftCertsGenerated   = 0;
        $createdCertOxids     = [];

        if ($aBasketItems) {
            foreach ($aBasketItems as $oItem) {
                $oArticle = $oItem->getArticle();
                $isGiftCert = $oArticle->isGiftCertArticle();

                if ($oArticle && $isGiftCert) {
                    $amount = $oItem->getAmount();
                    $sValue = $oItem->getUnitPrice()->getPrice();
                    $persParams = $oItem->getPersParams();
                    if (isset($persParams["additionalInfo"])) {
                        $options = json_decode(html_entity_decode($persParams["additionalInfo"]), true);
                    }
                    // Maybe it's a good idea to have a reference to the article for later
                    $options["originArticleId"] = $oArticle->oxarticles__oxid->value;
                    $options["isBusinessGiftcert"] = $oArticle->oxarticles__ys_giftcert_business->value;

                    $giftCertArticlesSeen++;
                    DebugLogger::debug('Order::finalizeOrder', 'gift cert basket item', [
                        'articleId'         => $oArticle->oxarticles__oxid->value,
                        'amount'            => $amount,
                        'unitPrice'         => $sValue,
                        'deliveryOption'    => isset($options['deliveryOption']) ? $options['deliveryOption'] : null,
                        'recipientMail'     => !empty($options['recipientMail']) ? 'set' : 'empty',
                        'preferredSendDate' => isset($options['preferredSendDate']) ? $options['preferredSendDate'] : null,
                        'isBusiness'        => $options['isBusinessGiftcert'],
                    ]);

                    // Create Giftcert for each single item
                    $aCodes = [];
                    $aGiftcerts = [];
                    for ($i = 0; $i < $amount; $i++) {
                        $oGiftCert = $giftCertHandler->generateGiftCert($oArticle, $sValue, $this->getUser(), $orderId, $options);
                        if (!$oGiftCert || !isset($oGiftCert->ysgiftcert__ysgiftcertnr)) {
                            DebugLogger::error('Order::finalizeOrder', 'generateGiftCert returned no usable cert', [
                                'articleId' => $oArticle->oxarticles__oxid->value,
                                'price'     => $sValue,
                            ]);
                            continue;
                        }
                        $sCode = $oGiftCert->ysgiftcert__ysgiftcertnr->value;
                        $aCodes[$i] = $sCode;
                        $aGiftcerts[$i] = $oGiftCert;
                        $giftCertsGenerated++;
                        $createdCertOxids[] = $oGiftCert->getId();
                        DebugLogger::debug('Order::finalizeOrder', 'gift cert generated', [
                            'certOxid'       => $oGiftCert->getId(),
                            'certNr'         => $sCode,
                            'restDiscount'   => $oGiftCert->ysgiftcert__ysrestdiscount->value,
                            'deliveryOption' => $oGiftCert->ysgiftcert__ysdeliveryoption->value,
                            'deliveryDate'   => $oGiftCert->ysgiftcert__ysdeliverydate->value,
                        ]);
                    }

                    // Add code to pers params for later use (maybe in ERP)
                    $options["code"] = $aCodes;
                    $persParams["additionalInfo"] = json_encode($options);
                    $oItem->setPersParams($persParams);

                    // If
                    // - the giftcert is an "online" giftcert (deliveryOption == 0)
                    // - the giftCert should be mailed
                    // - no individual data is given or preferred date is not in the future
                    // add it to the direct mail pool
                    // otherwise it will be processed later
                    if (isset($oGiftCert) && $oGiftCert && $oGiftCert->ysgiftcert__ysdeliveryoption->value === 0) {
                        if (!$options["recipientMail"] || !$options["preferredSendDate"] || $options["preferredSendDate"] <= date("Y-m-d")) {
                            if (null === $this->_aGiftcertToDirectlyMailList) {
                                $this->_aGiftcertToDirectlyMailList = [];
                            }
                            foreach ($aCodes as $myKey => $myCode) {
                                $this->_aGiftcertToDirectlyMailList[$myCode] = [
                                    'dValue'   => $sValue,
                                    'sImage'   => $oArticle->oxarticles__ys_giftcert_image->value,
                                    // new Parameters
                                    'giftcert' => $aGiftcerts[$myKey]
                                ];
                            }
                            DebugLogger::debug('Order::finalizeOrder', 'queued for immediate mail', [
                                'count'  => count($aCodes),
                                'reason' => !$options['recipientMail'] ? 'no recipient mail'
                                    : (!$options['preferredSendDate'] ? 'no preferred date' : 'preferred date in past or today'),
                            ]);
                        } else {
                            DebugLogger::debug('Order::finalizeOrder', 'scheduled for cron delivery', [
                                'count'             => count($aCodes),
                                'preferredSendDate' => $options['preferredSendDate'],
                            ]);
                        }
                    } elseif (isset($oGiftCert) && $oGiftCert) {
                        DebugLogger::debug('Order::finalizeOrder', 'offline gift cert — no direct email', [
                            'count'          => count($aCodes),
                            'deliveryOption' => $oGiftCert->ysgiftcert__ysdeliveryoption->value,
                        ]);
                    }
                }
            }
        }

        $directListSize = is_array($this->_aGiftcertToDirectlyMailList) ? count($this->_aGiftcertToDirectlyMailList) : 0;
        if ($giftCertArticlesSeen > 0 && $giftCertsGenerated === 0) {
            DebugLogger::warning('Order::finalizeOrder', 'gift cert articles in basket but no certs generated', [
                'giftCertArticlesSeen' => $giftCertArticlesSeen,
                'orderId'              => $orderId,
            ]);
        }
        DebugLogger::debug('Order::finalizeOrder', 'pre parent::finalizeOrder', [
            'giftCertArticlesSeen' => $giftCertArticlesSeen,
            'giftCertsGenerated'   => $giftCertsGenerated,
            'directMailListSize'   => $directListSize,
        ]);

        $ret = parent::finalizeOrder($oBasket, $oUser, $blRecalculatingOrder);

        DebugLogger::debug('Order::finalizeOrder', 'post parent::finalizeOrder', [
            'returnValue'        => $ret,
            'this_getId'         => $this->getId(),
            'oxorder__oxid'      => isset($this->oxorder__oxid) ? $this->oxorder__oxid->value : null,
            'sess_challenge'     => Registry::getSession()->getVariable('sess_challenge'),
            'directMailListSize' => is_array($this->_aGiftcertToDirectlyMailList) ? count($this->_aGiftcertToDirectlyMailList) : 0,
        ]);

        // After parent::finalizeOrder the order has its final oxid even when payment modules
        // (e.g. oxid-solution-catalysts/paypal-module 2.x) generate the UUID internally without
        // writing it to sess_challenge first. Back-fill the gift certs we created above so the
        // later mail-send request (cron, PayPal capture subscriber, …) can locate them via
        // ysgiftcert.oxorderid.
        $finalOrderId = $this->getId();
        if (!empty($createdCertOxids) && !empty($finalOrderId)) {
            $assigned = 0;
            foreach ($createdCertOxids as $certOxid) {
                $cert = oxNew(GiftCert::class);
                if (!$cert->load($certOxid)) {
                    DebugLogger::warning('Order::finalizeOrder', 'could not reload cert for oxorderid back-fill', [
                        'certOxid' => $certOxid,
                    ]);
                    continue;
                }
                $storedOrderId = (string) $cert->ysgiftcert__oxorderid->value;
                if ($storedOrderId === $finalOrderId) {
                    continue;
                }
                if ($storedOrderId !== '' && $storedOrderId !== $finalOrderId) {
                    DebugLogger::warning('Order::finalizeOrder', 'cert already has a different oxorderid — leaving it untouched', [
                        'certOxid'      => $certOxid,
                        'storedOrderId' => $storedOrderId,
                        'finalOrderId'  => $finalOrderId,
                    ]);
                    continue;
                }
                $cert->ysgiftcert__oxorderid = new \OxidEsales\Eshop\Core\Field($finalOrderId);
                $cert->save();
                $assigned++;
            }
            DebugLogger::debug('Order::finalizeOrder', 'cert oxorderid back-fill done', [
                'finalOrderId'        => $finalOrderId,
                'certsCreatedInRun'   => count($createdCertOxids),
                'certsBackFilled'     => $assigned,
            ]);
        } elseif (!empty($createdCertOxids) && empty($finalOrderId)) {
            DebugLogger::error('Order::finalizeOrder', 'parent::finalizeOrder left no order id — cert oxorderid cannot be back-filled', [
                'certsCreatedInRun' => count($createdCertOxids),
            ]);
        }

        return $ret;
    }

    public function getGiftCertsToDirectlySend()
    {
        // If variable is still NULL we possibly come back from Paypal&Co and
        // have to reload giftcerts for this order from Database
        if (null === $this->_aGiftcertToDirectlyMailList) {
            $this->_aGiftcertToDirectlyMailList = [];
            // Get order ID from session (normally paypal uses this) or get the already loaded one
            $sessOrderId = Registry::getSession()->getVariable('sess_challenge');
            $orderId = $sessOrderId;
            if (trim($orderId) == "") {
                $orderId = $this->getId();
            }
            DebugLogger::debug('Order::getGiftCertsToDirectlySend', 'list is null — DB reload', [
                'orderId_used'      => $orderId,
                'orderId_sess'      => $sessOrderId,
                'orderId_this_get'  => $this->getId(),
                'orderId_oxid_field' => isset($this->oxorder__oxid) ? $this->oxorder__oxid->value : null,
                'callers'           => DebugLogger::callers(8),
            ]);
            $container = ContainerFactory::getInstance()->getContainer();
            $this->queryBuilderFactory = $container->get(QueryBuilderFactoryInterface::class);
            $queryBuilder = $this->queryBuilderFactory->create();
            $queryBuilder
                ->select('*')
                ->from("ysgiftcert")
                ->where('ysdelivered = 0')
                ->andWhere('ysdeliverydate <= :deliverydate')
                ->andWhere('oxorderid = :orderid')
                ->setParameters([
                    "deliverydate" => date("Y-m-d"),
                    "orderid"      => $orderId
                ]);
            $blocksData = $queryBuilder->execute();
            $blocksData = $blocksData->fetchAll();

            DebugLogger::debug('Order::getGiftCertsToDirectlySend', 'primary DB lookup', [
                'orderId' => $orderId,
                'rows'    => count($blocksData),
            ]);

            // Diagnostic: if nothing found, look for orphan certs in the last hour to verify the timing theory.
            if (DebugLogger::isDebug() && count($blocksData) === 0) {
                try {
                    $diag = $this->queryBuilderFactory->create()
                        ->select('OXID, YSGIFTCERTNR, OXORDERID, YSDATEPURCHASED')
                        ->from('ysgiftcert')
                        ->where('ysdelivered = 0')
                        ->andWhere('ysdatepurchased >= :since')
                        ->orderBy('ysdatepurchased', 'DESC')
                        ->setParameter('since', date('Y-m-d H:i:s', time() - 3600))
                        ->setMaxResults(10)
                        ->execute()
                        ->fetchAll();
                    DebugLogger::debug('Order::getGiftCertsToDirectlySend', 'recent ysgiftcert rows (diag)', [
                        'orderId_used' => $orderId,
                        'rows'         => $diag,
                    ]);
                } catch (\Throwable $e) {
                    DebugLogger::warning('Order::getGiftCertsToDirectlySend', 'diag query failed', [
                        'message' => $e->getMessage(),
                    ]);
                }
            }
            foreach ($blocksData as $entry) {
                $options = json_decode($entry["YSINDIVIDUALOPTIONS"], true);
                $image = "";

                // Only add the ones that should be sent directly
                // - Business giftcerts are never send by email
                // - All others only if delivery option is email AND preferred send date is not set or already over
                if (!isset($options["isBusinessGiftcert"]) || $options["isBusinessGiftcert"] == 0) {
                    if ($entry["YSDELIVERYOPTION"] == 0 && (!$options["recipientMail"] || !$options["preferredSendDate"] || $options["preferredSendDate"] <= date("Y-m-d"))) {
                        if (is_array($options) && isset($options['originArticleId'])) {
                            $queryBuilder = $this->queryBuilderFactory->create();
                            $queryBuilder
                                ->select('YS_GIFTCERT_IMAGE')
                                ->from("oxarticles")
                                ->where('oxid = :id')
                                ->setParameters([
                                    "id" => $options['originArticleId']
                                ]);
                            $blocksData = $queryBuilder->execute();
                            $blocksData = $blocksData->fetchAll();
                            if (is_array($blocksData) && isset($blocksData[0]['YS_GIFTCERT_IMAGE'])) {
                                $image = $blocksData[0]['YS_GIFTCERT_IMAGE'];
                            }
                        }
                        $cert = oxNew(GiftCert::class);
                        $cert->load($entry["OXID"]);
                        $this->_aGiftcertToDirectlyMailList[$entry["YSGIFTCERTNR"]] = [
                            'dValue'   => $entry["YSINITIALDISCOUNT"],
                            'sImage'   => $image,
                            // new Parameters
                            'giftcert' => $cert
                        ];
                    }
                }
            }
        }

        return $this->_aGiftcertToDirectlyMailList;
    }


}