<?php

namespace YSquare\GiftCert\Model;

use OxidEsales\Eshop\Application\Model\User;
use OxidEsales\Eshop\Core\Field;
use OxidEsales\Eshop\Core\Price;
use OxidEsales\Eshop\Core\Registry;
use YSquare\GiftCert\Core\GiftCertHandler;

class GiftCert extends \OxidEsales\Eshop\Core\Model\BaseModel
{

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

    /**
     * @var string Name of current class
     */
    protected $_sClassName = 'ysgiftcert';

    /**
     * Class constructor, initiates parent constructor (parent::oxBase()).
     */
    public function __construct()
    {
        parent::__construct();
        $this->init('ysgiftcert');
    }


    /**
     * Gets voucher from db by given number.
     *
     * @param string $sGiftcertNr Gift certificate number
     * @param bool $blCheckavalability check if voucher is still reserved or not
     *
     * @return mixed
     * @throws oxVoucherException exception
     *
     */
    public function getGiftcertByNr($sGiftcertNr, $blCheckavalability = false)
    {
        $oRet = null;
        if (!is_null($sGiftcertNr)) {

            $sViewName = $this->getViewName();
            $oDb = \OxidEsales\Eshop\Core\DatabaseProvider::getDb(\OxidEsales\Eshop\Core\DatabaseProvider::FETCH_MODE_ASSOC);

            $sQ = "select {$sViewName}.* from {$sViewName} where 
                    {$sViewName}.ysgiftcertnr = " . $oDb->quote($sGiftcertNr) . " and {$sViewName}.ysrestdiscount > 0";

            //voucher timeout for 3 hours
            if ($blCheckavalability) {
                $iTime = time() - $this->_getGiftcertTimeout();
                $sQ .= " and {$sViewName}.ysreserved < '{$iTime}' ";
            }

            $sQ .= " limit 1";

            if (!($oRet = $this->assignRecord($sQ))) {
                $oEx = oxNew(\OxidEsales\Eshop\Core\Exception\VoucherException::class,
                    'ERROR_MESSAGE_VOUCHER_ISRESERVED');
                $oEx->setVoucherNr($sGiftcertNr);
                throw $oEx;
            }


            if ($blCheckavalability) {
                $expiration_threshold = $this->_getGiftcertExpirationThreshold();
                if ($expiration_threshold > 0) {
                    $expiration_date = date('Y-m-d', strtotime('+' . $expiration_threshold . ' days',
                        strtotime($this->ysgiftcert__ysdatepurchased->value)));
                    $now = date('Y-m-d');
                    if ($now > $expiration_date) {
                        $oEx = oxNew(\OxidEsales\Eshop\Core\Exception\VoucherException::class,
                            'ERROR_MESSAGE_VOUCHER_NOVOUCHER');
                        $oEx->setVoucherNr($sGiftcertNr);
                        throw $oEx;
                    }
                }
            }
        }

        return $oRet;
    }

    /**
     * Returns a configured value for voucher timeouts or a default
     * of 3 hours if not configured
     *
     * @return integer Seconds a voucher can stay in status reserved
     */
    protected function _getGiftcertTimeout()
    {
        $oConfig = Registry::getConfig();
        $iGiftcertTimeout = intval($oConfig->getConfigParam('iYsGiftcertTimeout')) ?
            intval($oConfig->getConfigParam('iYsGiftcertTimeout')) :
            3 * 3600;

        return $iGiftcertTimeout;
    }

    /**
     * Loads the time after which the giftcert expires (configured in backend)
     * @return int
     */
    protected function _getGiftcertExpirationThreshold()
    {
        $oConfig = Registry::getConfig();
        $iGiftcertTimeout = intval($oConfig->getConfigParam('iYsGiftcertInvalidateThreshold'));
        return $iGiftcertTimeout;
    }

    /**
     * Returns the date of expiration of this giftcert if expiration threshold is set in backend
     *
     * @return false|string
     */
    public function getGiftcertExpirationDate()
    {
        $expiration_threshold = $this->_getGiftcertExpirationThreshold();
        if ($expiration_threshold > 0) {
            $expiration_date = date('d.m.Y',
                strtotime('+' . $expiration_threshold . ' days', strtotime($this->ysgiftcert__ysdatepurchased->value)));
            return $expiration_date;
        }
        return false;
    }

    /**
     * Checks if voucher is not yet reserved before.
     *
     * @return bool
     * @throws oxVoucherException exception
     *
     */
    public function isNotReserved()
    {

        if ($this->ysgiftcert__ysreserved->value < time() - $this->_getGiftcertTimeout()) {
            return true;
        }

        $oEx = oxNew(\OxidEsales\Eshop\Core\Exception\VoucherException::class, 'EXCEPTION_VOUCHER_ISRESERVED');
        $oEx->setVoucherNr($this->ysgiftcert__ysgiftcertnr->value);
        throw $oEx;
    }

    /**
     * mark voucher as reserved
     */
    public function markAsReserved()
    {
        //saving ysreserved field
        $sVoucherID = $this->ysgiftcert__oxid->value;

        if ($sVoucherID) {
            $oDb = \OxidEsales\Eshop\Core\DatabaseProvider::getDb();
            $sQ = "update ysgiftcert set ysreserved = " . time() . " where oxid = " . $oDb->quote($sVoucherID);
            $oDb->Execute($sQ);
        }
    }

    /**
     * un mark as reserved
     */
    public function unMarkAsReserved()
    {
        //saving oxreserved field
        $sVoucherID = $this->ysgiftcert__oxid->value;

        if ($sVoucherID) {
            $oDb = \OxidEsales\Eshop\Core\DatabaseProvider::getDb();
            $sQ = "update ysgiftcert set ysreserved = 0 where oxid = " . $oDb->quote($sVoucherID);
            $oDb->Execute($sQ);
        }
    }

    /**
     * @param $order
     * @param $user
     * @param $valueRedeemed
     */
    public function markAsUsed($orderid, $userid, $valueRedeemed)
    {
        // Check if already used in this order. If so, ignore it and log an error
        // If order ID equals "-" this is a manual redemtion so just let it pass
        $giftcertHandler = oxNew(GiftCertHandler::class);
        if ($orderid !== "-" && $giftcertHandler->isGiftCertRedeemedInOrder($this->ysgiftcert__oxid->value, $orderid)) {
            $logger = Registry::getLogger();
            $logger->error('This giftcert is already used: ' . $this->ysgiftcert__ysgiftcertnr->value, [__CLASS__, __FUNCTION__]);
            return false;
        }

        // unmark reserved
        $this->unMarkAsReserved();

        // reduce available value
        if (!Registry::getConfig()->getConfigParam('blYsGiftcertRedeemPartly')) {
            $restvalue = 0;
        } else {
            $restvalue = $this->ysgiftcert__ysrestdiscount->value - $valueRedeemed;
        }
        if ($restvalue < 0) {
            $restvalue = 0;
        }

        // If the rest value should not be left on the card
        // but go to the personal pool we store it there and not on the card
        // BUT ONLY if the user is existent and not a guest
        if (Registry::getConfig()->getConfigParam('blYsGiftcertRedeemToPool')) {
            $oUser = oxNew(User::class);
            if ($oUser->load($userid)) {
                // check the guest status
                if (!empty($user->oxuser__oxpassword->value)) {
                    $oUser->addYSGiftcertPoolAmount($restvalue);
                    $restvalue = 0;
                }
            }
        }

        $this->ysgiftcert__ysrestdiscount->setValue($restvalue);
        $this->save();

        // add log entry for purchase
        $giftcertLog = oxNew(\YSquare\GiftCert\Model\GiftCertLog::class);
        $giftcertLog->ysgiftcertlog__ysgiftcertid = new Field($this->ysgiftcert__oxid->value);
        $giftcertLog->ysgiftcertlog__ysamountused = new Field($valueRedeemed);
        $giftcertLog->ysgiftcertlog__ysorderid = new Field($orderid);
        $giftcertLog->ysgiftcertlog__ysuserid = new Field($userid);
        $giftcertLog->ysgiftcertlog__ysdateused = new Field(date("Y-m-d H:i:s", Registry::getUtilsDate()->getTime()));
        $giftcertLog->save();
    }

    public function getGiftcertLogList()
    {
        if (null === $this->_aGiftcertLogList) {
            $this->_aGiftcertLogList = [];
            $sQ = "SELECT oxid from ysgiftcertlog where ysgiftcertid=?";

            $oDb = \OxidEsales\Eshop\Core\DatabaseProvider::getDb(\OxidEsales\Eshop\Core\DatabaseProvider::FETCH_MODE_ASSOC);
            $rs = $oDb->select($sQ, [$this->ysgiftcert__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;
    }

    /**
     * Mark the Giftcert as delivered - mainly used to determine delivery on special day
     */
    public function markAsDelivered()
    {
        $this->ysgiftcert__ysdelivered = new Field(1);
        $this->save();
    }

    /**
     * @return mixed|Price
     */
    public function getPrice()
    {
        $price = oxNew(Price::class);
        $price->setPrice($this->ysgiftcert__ysinitialdiscount->value);
        return $price;
    }

    public function paymentStatus()
    {
        // 1 = no payment needed
        // 2 = payment ok
        // 3 = payment not ok
        // 4 = order no longer exists

        // If no order ID set, the cert was not ordered but created manually
        if (trim($this->ysgiftcert__oxorderid->value) == "") return 1;

        // check if order still available
        $order = oxNew(\OxidEsales\Eshop\Application\Model\Order::class);
        $orderAvailable = $order->load($this->ysgiftcert__oxorderid->value);
        if (!$orderAvailable) return 4;

        // if order is available check if paid or not
        $configStates = Registry::getConfig()->getConfigParam('aYsGiftcertPaymentSuccessfulStates');

        $acceptedOrderStates = array_map('strtolower',
            is_array($configStates) ? $configStates : []
        );
        $paid = in_array(strtolower($order->oxorder__oxtransstatus->value), $acceptedOrderStates);

        if ($paid) {
            return 2;
        } else {
            return 3;
        }
    }
}