<?php

namespace Webkul\Rule\Helpers;

use Webkul\CartRule\Contracts\CartRule;
use Webkul\CatalogRule\Contracts\CatalogRule;
use Webkul\Checkout\Contracts\Cart as CheckoutContract;
use Webkul\Checkout\Contracts\CartItem;
use Webkul\Checkout\Facades\Cart;
use Webkul\Product\Contracts\Product;

class Validator
{
    /**
     * Validate cart rule for condition
     *
     * @param  CartRule|CatalogRule  $rule
     * @param  CheckoutContract|CartItem|Product  $entity
     * @return bool
     */
    public function validate($rule, $entity)
    {
        if (! $rule->conditions) {
            return true;
        }

        $validConditionCount = $totalConditionCount = 0;

        foreach ($rule->conditions as $condition) {
            if (
                ! $condition['attribute']
                || ! isset($condition['value'])
                || is_null($condition['value'])
            ) {
                continue;
            }

            if (
                $entity instanceof CheckoutContract
                && strpos($condition['attribute'], 'cart|') === false
            ) {
                continue;
            }

            $totalConditionCount++;

            if ($rule->condition_type == '1') {
                if (! $this->validateObject($condition, $entity)) {
                    return false;
                } else {
                    $validConditionCount++;
                }
            } elseif ($rule->condition_type == '2') {
                if ($this->validateObject($condition, $entity)) {
                    return true;
                }
            }
        }

        return $validConditionCount == $totalConditionCount;
    }

    /**
     * Return value for the attribute
     *
     * @param  array  $condition
     * @param  CartItem|Product  $entity
     * @return bool
     */
    public function getAttributeValue($condition, $entity)
    {
        $chunks = explode('|', $condition['attribute']);

        $attributeNameChunks = explode('::', $chunks[1]);

        $attributeCode = $attributeNameChunks[count($attributeNameChunks) - 1];

        switch (current($chunks)) {
            case 'cart':
                $cart = $entity instanceof CheckoutContract ? $entity : $entity->cart;

                if (in_array($attributeCode, ['postcode', 'state', 'country'])) {
                    if (! $cart->shipping_address) {
                        return;
                    }

                    return $cart->shipping_address->{$attributeCode};
                } elseif ($attributeCode == 'shipping_method') {
                    if (! $cart->shipping_method) {
                        return;
                    }

                    $shippingChunks = explode('_', $cart->shipping_method);

                    return current($shippingChunks);
                } elseif ($attributeCode == 'payment_method') {
                    if (! $cart->payment) {
                        return;
                    }

                    return $cart->payment->method;
                } else {
                    return $cart->{$attributeCode};
                }

            case 'cart_item':
                return $entity->{$attributeCode};

            case 'product':
                if ($attributeCode == 'category_ids') {
                    $value = $entity->product
                        ? $entity->product->categories()->pluck('id')->toArray()
                        : $entity->categories()->pluck('id')->toArray();

                    return $value;
                } else {
                    $value = $entity->product
                        ? $entity->product->{$attributeCode}
                        : $entity->{$attributeCode};

                    if (! in_array($condition['attribute_type'], ['multiselect', 'checkbox'])) {
                        return $value;
                    }

                    return $value ? explode(',', $value) : [];
                }
        }
    }

    /**
     * Validate object
     *
     * @param  array  $condition
     * @param  CartItem  $entity
     * @return bool
     */
    private function validateObject($condition, $entity)
    {
        $validated = false;

        foreach ($this->getAllItems($this->getAttributeScope($condition), $entity) as $item) {
            $attributeValue = $this->getAttributeValue($condition, $item);

            if ($validated = $this->validateAttribute($condition, $attributeValue)) {
                break;
            }
        }

        return $validated;
    }

    /**
     * Return all cart items
     *
     * @param  string  $attributeScope
     * @param  CheckoutContract|CartItem|Product  $item
     * @return array
     */
    private function getAllItems($attributeScope, $item)
    {
        if ($attributeScope === 'parent') {
            return [$item];
        } elseif ($attributeScope === 'children') {
            return $item->children ?: [$item];
        } else {
            $items = $item->children ?: [];

            $items[] = $item;
        }

        return $items;
    }

    /**
     * Validate object
     *
     * @param  array  $condition
     * @return string
     */
    private function getAttributeScope($condition)
    {
        $chunks = explode('|', $condition['attribute']);

        $attributeNameChunks = explode('::', $chunks[1]);

        return count($attributeNameChunks) == 2 ? $attributeNameChunks[0] : null;
    }

    /**
     * Validate attribute value for condition
     *
     * @param  array  $condition
     * @param  mixed  $attributeValue
     * @return bool
     */
    public function validateAttribute($condition, $attributeValue)
    {
        switch ($condition['operator']) {
            case '==': case '!=':
                if (is_array($condition['value'])) {
                    if (! is_array($attributeValue)) {
                        return false;
                    }

                    $result = ! empty(array_intersect($condition['value'], $attributeValue));
                } else {
                    if (is_array($attributeValue)) {
                        $result = count($attributeValue) == 1 && array_shift($attributeValue) == $condition['value'];
                    } else {
                        $result = $attributeValue == $condition['value'];
                    }
                }

                break;

            case '<=': case '>':
                if (! is_scalar($attributeValue)) {
                    return false;
                }

                $result = $attributeValue <= $condition['value'];

                break;

            case '>=': case '<':
                if (! is_scalar($attributeValue)) {
                    return false;
                }

                $result = $attributeValue >= $condition['value'];

                break;

            case '{}': case '!{}':
                if (
                    is_scalar($attributeValue)
                    && is_array($condition['value'])
                ) {
                    foreach ($condition['value'] as $item) {
                        if (stripos($attributeValue, $item) !== false) {
                            $result = true;

                            break;
                        }
                    }
                } elseif (is_array($condition['value'])) {
                    if (! is_array($attributeValue)) {
                        return false;
                    }

                    $result = ! empty(array_intersect($condition['value'], $attributeValue));
                } else {
                    if (is_array($attributeValue)) {
                        $result = self::validateArrayValues($attributeValue, $condition['value']);
                    } else {
                        $result = strpos($attributeValue, $condition['value']) !== false;
                    }
                }

                break;
        }

        if (in_array($condition['operator'], ['!=', '>', '<', '!{}'])) {
            $result = ! $result;
        }

        return $result;
    }

    /**
     * Validate the condition value against a multi dimensional array recursively
     */
    private static function validateArrayValues(array $attributeValue, string $conditionValue): bool
    {
        if (in_array($conditionValue, $attributeValue, true) === true) {
            return true;
        }

        foreach ($attributeValue as $subValue) {
            if (! is_array($subValue)) {
                continue;
            }

            if (self::validateArrayValues($subValue, $conditionValue) === true) {
                return true;
            }
        }

        return false;
    }
}
