<?php

namespace CouponsPlus\App\Coupon\Tree\Cart;

use stdClass;
use Throwable;
use JsonException;
use CouponsPlus\App\Conditions\CartItem;
use CouponsPlus\Original\Environment\Env;
use CouponsPlus\Original\Collections\Collection;

class AutoAddedItems
{
    protected Collection $itemsAddedByUs;
    protected static ?self $instance;

    public static function storageKey(): string
    {
        return Env::getWithPrefix('auto_added_items');
    }

    public function __construct()
    {
        $defaultItems = '[]';

        $session = WC()->session;

        if ($session) {
            $defaultItems = $session->get(self::storageKey(), $defaultItems);
        }

        $this->itemsAddedByUs = new Collection(static::importData(is_array($defaultItems) ? json_encode($defaultItems) : $defaultItems));
    }

    public static function instance(): self
    {
        if (!isset(self::$instance)) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    public static function reset(): void
    {
        static::$instance = null;
    }

    /**
     */
    public static function allByComponent(string $id) : Collection
    {
        $self = static::instance();

        return $self->itemsAddedByUs->filter(function (AutoAddedItem $autoAddedItem) use ($id): bool {
            return $autoAddedItem->componentId === $id;
        });
    }

    public static function registerItems(
        CartItem $itemToAdd,
        int $quantity,
        string $componentId,
        /**
         * Will increase the quantity by default. Set to true to set the quantity instead.
         */
        bool $setQuantity = false
    ): void {
        $autoAddedItem = new AutoAddedItem($itemToAdd, $quantity, $componentId);
        $self = self::instance();

        if ($setQuantity && $self->hasAddedItem($autoAddedItem)) {
            $self->setItemQuantity(
                // important because these are two different instances
                $autoAddedItem,
                $quantity
            );
            $self->save();
        } else {
            $self->addItem($autoAddedItem);
        }
    }

    public static function all(): Collection
    {
        return static::instance()->itemsAddedByUs;
    }

    private function addItem(AutoAddedItem $autoAddedItem): void
    {
        if ($this->hasAddedItem($autoAddedItem)) {
            $this->increaseItemQuantity($autoAddedItem);
        } else {
            $this->itemsAddedByUs->push( $autoAddedItem);
        }

        $this->save();
    }

    private function hasAddedItem(AutoAddedItem $autoAddedItemToAdd) : bool
    {
        return (bool) $this->findAutoAddedItem($autoAddedItemToAdd);
    }

    private function increaseItemQuantity(AutoAddedItem $autoAddedItemToModify): void
    {
        $autoAddedItem = $this->findAutoAddedItem($autoAddedItemToModify);

        if ($autoAddedItem) {
            $autoAddedItem->quantity += $autoAddedItemToModify->quantity;
        }
    }

    private function setItemQuantity(AutoAddedItem $autoAddedItemToModify, int $quantity): void
    {
        $autoAddedItem = $this->findAutoAddedItem($autoAddedItemToModify);

        if ($autoAddedItem) {
            $autoAddedItem->quantity = $quantity;
        }
    }

    private function findAutoAddedItem(AutoAddedItem $autoAddedItemToAdd): ?AutoAddedItem
    {
        return $this->itemsAddedByUs->find(function (AutoAddedItem $autoAddedItem) use ($autoAddedItemToAdd) {
            return $autoAddedItem->isTheSameAs($autoAddedItemToAdd);
        });
    }

    public function importData(string $data) : array
    {
        try {
            $arrayOfData = json_decode($data, false, 512, JSON_THROW_ON_ERROR);
        } catch (Throwable $e) {
            $arrayOfData = [];
        }

        // sometimes data is an stdClass instead of an array because for some reason woocomerce seems to mess with the data format and so we end up with an object list instead of an array
        if ($arrayOfData instanceof stdClass) {
            $arrayOfData = (array) $arrayOfData;
        }

        return Collection::create($arrayOfData)->map(function (stdClass $autoAddedItem): AutoAddedItem {
            return new AutoAddedItem(
                $autoAddedItem->cartItemKey,
                $autoAddedItem->quantity,
                $autoAddedItem->componentId
            );
        })->asArray();
    }

    /**
     * @throws JsonException
     */
    public function exportData() : string
    {
        return json_encode($this->itemsAddedByUs->asArray(), JSON_THROW_ON_ERROR);
    }

    public function save(): void
    {
        $session = WC()->session;

        if (!$session) {
            return;
        }

        $session->set(static::storageKey(), $this->exportData());
    }

    public static function deregisterItems(CartItem $itemToRemove, int $quantity, string $componentId): void
    {
        $autoAddedItem = new AutoAddedItem($itemToRemove, $quantity, $componentId);
        $self = self::instance();

        $self->removeItem($autoAddedItem);
    }

    private function removeItem(AutoAddedItem $autoAddedItem): void
    {
        if ($this->hasAddedItem($autoAddedItem)) {
            $this->decreaseItemQuantity($autoAddedItem);
        }

        $this->save();
    }

    private function decreaseItemQuantity(AutoAddedItem $autoAddedItemToModify): void
    {
        $autoAddedItem = $this->findAutoAddedItem ($autoAddedItemToModify);

        if ($autoAddedItem) {
            $autoAddedItem->quantity -= $autoAddedItemToModify->quantity;

            if ($autoAddedItem->quantity <= 0) {
                $keyToRemove = $this->itemsAddedByUs->findKey(function (AutoAddedItem $item) use ($autoAddedItem) {
                    return $item->isTheSameAs($autoAddedItem);
                });

                if ($keyToRemove !== null) {
                    $this->itemsAddedByUs->remove($keyToRemove);
                }
            }
        }
    }

    /**
     * Item has been completely removed from the cart, deregister it entirely from all items
     */
    public static function deregisterRemovedItem(string $cart_item_key)
    {
        $self = self::instance();

        $self->itemsAddedByUs = $self->itemsAddedByUs->filter(function (AutoAddedItem $autoAddedItem) use ($cart_item_key) {
            return $autoAddedItem->cartItemKey !== $cart_item_key;
        });

        $self->save();
    }
}