<?php

namespace CouponsPlus\App\Coupon;

use WC_Cart;
use Throwable;
use CouponsPlus\App\Conditions\ItemsSet;
use CouponsPlus\App\Coupon\Abilities\CouponModel;
use CouponsPlus\App\Coupon\Rows;
use CouponsPlus\App\Offers\Abilities\Offers;
use CouponsPlus\App\Offers\OffersScheduler;
use CouponsPlus\App\Offers\OffersSet;
use CouponsPlus\Original\Cache\MemoryCache;
use CouponsPlus\Original\Characters\StringManager;
use CouponsPlus\Original\Collections\Collection;
use CouponsPlus\Original\Environment\Env;
use WC_Coupon;
use CouponsPlus\App\Coupon\Tree\Cart\CartModifiedException;

#[\AllowDynamicProperties]
Class Coupon
{
    protected WC_Coupon $classicCoupon;
    protected CouponModel $couponModel;
    public ?WC_Cart $cart;

    /**
     * Since instances of this class are instantiated in different parts of the code
     * *DO NOT* add events (like action hooks) in the constructor.
     */
    public function __construct(WC_Coupon $WCCoupon, CouponOffersTracker $couponOffersTracker)
    {
        $this->classicCoupon = $WCCoupon; 
        $this->couponOffersTracker = $couponOffersTracker;
        $this->cache = new MemoryCache; 
        $this->cart = \WC()->cart;
    }

    public function setModel(CouponModel $couponModel)
    {
        $this->couponModel = $couponModel;
    }

    public function getModel(): CouponModel
    {
        return $this->couponModel;
    }

    public function getClassic() : WC_Coupon
    {
        return $this->classicCoupon;   
    }
    
    public function isCouponsPlusCoupon() : bool
    {
        /*// change this to chcek if it has configuration content
        if (!$this->hasRows() || empty($this->JSONOptions)) {
            return false;
        }

        return $this->JSONOptions !== '{}';   */
        return $this->couponModel->hasConfigurationContent();
    }

    public function applyOffers(OffersScheduler $offersScheduler)
    {
        if ($this->isInCart() && !$this->couponOffersTracker->haveBeenApplied($this)) {
            try {
                (object) $offers = $this->getOffers();

                $offers->apply($offersScheduler);

                $this->couponOffersTracker->track($this, $offers);
            } catch (CartModifiedException $e) {
                /**
                 * Known scenarios:
                 * -CartModifiedException: when a product is added to the cart that causes a loop. The coupon has run again and know
                 *                         after running we are here, so just silently stop here because this inst needed anymore,
                 *                         and the remaining offers are going actually older and outdated than the coupons that have
                 *                         been executed since.
                 *                         Basically, execution at this point is not needed anymore since coupons have re-run already.
                 *                         the remaining offers are just redundant at this point and may potentially cause bugs.
                 */


                // just skip this
            }
        }
    }

    /**
     * Apply offers in admin order editing context.
     * This bypasses cart-based validation since we're working with an order.
     */
    public function applyOffersForOrder(\WC_Order $order, \WC_Discounts $wcDiscounts, OffersScheduler $offersScheduler)
    {
        try {
            (object) $offers = $this->getOffersForOrder($order);

            if ($offers->canBeApplied()) {
                $offers->apply($offersScheduler);
            }
        } catch (\Throwable $e) {
            // Silently fail in admin context to avoid breaking the order editor
        }
    }

    /**
     * Get offers evaluated against order items instead of cart items.
     */
    protected function getOffersForOrder(\WC_Order $order): Offers
    {
        return $this->couponModel->getOffersForOrder($order);
    }

    public function isInvalid(Offers $offers = null) : bool
    {
        if ($this->cart->is_empty()) {
            // silently remove it, otherwise WooCommerce would generate
            // an obnoxious error on the cart page.
            $this->cart->remove_coupon($this->classicCoupon->get_code($context = 'edit'));
            return false;
        }

        return !$this->couponModel->isValid();
    }

    public function hasClassicDicounts() : bool
    {
        (object) $classicDiscountTypes = new Collection(['percent', 'fixed_product', 'fixed_cart']);
        (boolean) $aClassicDscountTypeIsSlected = $classicDiscountTypes->contain($this->classicCoupon->get_discount_type($context = 'edit'));
        (boolean) $hasAClassicDiscountAmount = $this->classicCoupon->get_amount($context  = 'edit');

        return $aClassicDscountTypeIsSlected && $hasAClassicDiscountAmount;
    }
    
    public function canBeAutoApplied()
    {
        return $this->autoApplyingIsEnabled() && (boolean) $this->code()->isNotEmpty();
    }

    public function code() : StringManager
    {
        return new StringManager($this->classicCoupon->get_code($context = 'edit'));
    }

    public function autoApplyingIsEnabled()
    {
        return $this->classicCoupon->get_meta(Env::getWithPrefix('coupon_auto_apply_is_enabled')) === 'yes';
    }

    // aka has rows or a grove
    public function hasConfigurationContent() : bool
    {
        //if back wards compatible then check for rows
        //otherwise check for one or more trees
        return $this->couponModel->hasConfigurationContent();
    }

    public function removeCoupon()
    {
        $this->cart->remove_coupon($this->classicCoupon->get_code($context = 'edit'));   
        $this->classicCoupon->add_coupon_message(WC_Coupon::E_WC_COUPON_INVALID_REMOVED);
    }
    
    protected function getOffers() : Offers
    {
        return $this->couponModel->getOffers();
    }

    public function isInCart() : bool
    {
        return in_array(
            $this->classicCoupon->get_code($context = 'edit'), 
            $this->cart->get_applied_coupons()
        );
    }

    public function isTree() : bool
    {
        return $this->getModel()->getModelType() === TreeCouponModel::TYPE;
    }
}