<?php

namespace DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\DAO;

use WC_Product_Factory;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\ProductTypes\SingleProductTypesCollection;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\ConditionalLogic\ConditionalLogicCollection;
use WP_Query;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\ConditionalLogic\SingleConditionalLogic;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\ConditionalLogic\Fields\Abstraction\WPPostsField;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\ConditionalLogic\Fields\Abstraction\WPPostsMetaField;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\ConditionalLogic\Fields\Abstraction\WPTaxonomyField;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\ConditionalLogic\Conditions\Abstraction\ConditionOperator;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\ConditionalLogic\Conditions\Abstraction\ConditionWithValue;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\ConditionalLogic\Conditions\Abstraction\Condition;
use DropshippingExportFreeVendor\WPDesk\Library\DropshippingExport\ConditionalLogic\Fields\Abstraction\DataType;
use WC_Product_Variable;
class ProductDAO
{
    public const PRODUCT_IMPORT_ID_META = 'import_uid';
    public const PRODUCT_IMPORT_STARTED_AT_META = 'import_started_at';
    public const PRODUCT_IMPORT_NAME = 'import_started_at';
    public const HAS_PARENT_META_KEY = 'has_parent';
    public const HAS_PARENT_VALUE_YES = 'yes';
    public const HAS_PARENT_VALUE_NO = 'no';
    public const RESYNC_META_KEY = 'resync';
    public const RESYNC_VALUE_YES = 'yes';
    public const RESYNC_VALUE_NO = 'no';
    public const DEFAULT_PRODUCTS_LIMIT = 20;
    public const DEFAULT_PRODUCTS_OFFSET = 0;
    public const DEFAULT_PRODUCTS_NO_LIMIT = -1;
    private WC_Product_Factory $wc_factory;
    private array $query_args_cache = [];
    private array $conditional_logic_cache = [];
    public function __construct(WC_Product_Factory $wc_factory)
    {
        $this->wc_factory = $wc_factory;
    }
    public static function create_post_args_key(string $column_name, string $condition_name): string
    {
        return strtolower($column_name . '__' . $condition_name);
    }
    public function find_products_to_export(SingleProductTypesCollection $product_types, ConditionalLogicCollection $conditions, int $offset = self::DEFAULT_PRODUCTS_OFFSET, int $limit = self::DEFAULT_PRODUCTS_LIMIT): array
    {
        $product_ids = $this->get_product_ids($product_types, $conditions, $offset, $limit);
        $products = [];
        foreach ($product_ids as $product_id) {
            $product = $this->wc_factory->get_product($product_id);
            if ($product) {
                $products[] = $product;
            }
        }
        return $products;
    }
    private function get_product_ids(SingleProductTypesCollection $product_types, ConditionalLogicCollection $conditions, int $offset = self::DEFAULT_PRODUCTS_OFFSET, int $limit = self::DEFAULT_PRODUCTS_LIMIT): array
    {
        $ids = [];
        $product_query = $this->create_product_query($product_types, $conditions, self::DEFAULT_PRODUCTS_OFFSET, self::DEFAULT_PRODUCTS_NO_LIMIT);
        $ids = $product_query->posts;
        wp_reset_postdata();
        if ($product_types->has_variable_type()) {
            $parent_query = $this->create_parent_query($product_types, $conditions, self::DEFAULT_PRODUCTS_OFFSET, self::DEFAULT_PRODUCTS_NO_LIMIT);
            $parent_ids = $parent_query->posts;
            wp_reset_postdata();
            $parent_ids_for_variations = array_unique(array_merge($ids, $parent_ids), \SORT_NUMERIC);
            if (!empty($parent_ids_for_variations)) {
                $variation_query = $this->create_product_variation_query($conditions, $parent_ids_for_variations);
                $variation_ids = $variation_query->posts;
                wp_reset_postdata();
                $ids = array_unique(array_merge($ids, $variation_ids), \SORT_NUMERIC);
            }
        }
        if ($limit !== self::DEFAULT_PRODUCTS_NO_LIMIT) {
            return array_slice($ids, $offset, $limit);
        }
        return $ids;
    }
    public function count_products_to_export(SingleProductTypesCollection $product_types, ConditionalLogicCollection $conditions): int
    {
        $cache_key = 'count_' . md5(serialize($product_types) . spl_object_hash($conditions));
        if (isset($this->query_args_cache[$cache_key])) {
            return $this->query_args_cache[$cache_key];
        }
        $count = count($this->get_product_ids($product_types, $conditions, self::DEFAULT_PRODUCTS_OFFSET, self::DEFAULT_PRODUCTS_NO_LIMIT));
        $this->query_args_cache[$cache_key] = $count;
        return $count;
    }
    private function get_conditional_logic_post_args(ConditionalLogicCollection $conditions): array
    {
        $cache_key = 'post_args_' . spl_object_hash($conditions);
        if (isset($this->conditional_logic_cache[$cache_key])) {
            return $this->conditional_logic_cache[$cache_key];
        }
        $args = [];
        /**
         * @var SingleConditionalLogic $single_condition
         */
        foreach ($conditions as $single_condition) {
            $field = $single_condition->get_field();
            $condition = $single_condition->get_condition();
            if ($field instanceof WPPostsField && $condition instanceof ConditionOperator && $condition instanceof Condition) {
                $key = self::create_post_args_key($field::get_column_name(), $condition::get_name());
                if ($condition instanceof ConditionWithValue) {
                    $args[$key] = $condition->get_values();
                }
            }
        }
        $this->conditional_logic_cache[$cache_key] = $args;
        return $args;
    }
    private function get_conditional_logic_postmeta_args(ConditionalLogicCollection $conditions): array
    {
        $cache_key = 'postmeta_args_' . spl_object_hash($conditions);
        if (isset($this->conditional_logic_cache[$cache_key])) {
            return $this->conditional_logic_cache[$cache_key];
        }
        $args = [];
        $meta_query = [];
        /**
         * @var SingleConditionalLogic $single_condition
         */
        foreach ($conditions as $single_condition) {
            $field = $single_condition->get_field();
            $condition = $single_condition->get_condition();
            $tmp_array = [];
            if ($field instanceof WPPostsMetaField && $condition instanceof ConditionOperator && $condition instanceof Condition) {
                if (empty($meta_query)) {
                    $meta_query['relation'] = 'AND';
                }
                $key = $field::get_meta_key();
                $type = 'CHAR';
                if ($field instanceof DataType) {
                    $type = $field->get_data_type();
                }
                if ($condition instanceof ConditionWithValue) {
                    $values = $condition->get_values();
                    if (!empty($values)) {
                        $tmp_array['relation'] = 'OR';
                        foreach ($values as $value) {
                            $tmp_array[] = ['key' => $key, 'value' => $value, 'compare' => $condition::get_operator(), 'type' => $type];
                        }
                    }
                } else {
                    $tmp_array = ['key' => $key, 'compare' => $condition::get_operator(), 'type' => $type];
                }
                $meta_query[] = $tmp_array;
            }
        }
        if (!empty($meta_query)) {
            $args['meta_query'] = $meta_query;
            //phpcs:ignore
        }
        $this->conditional_logic_cache[$cache_key] = $args;
        return $args;
    }
    private function get_conditional_logic_taxonomy_args(ConditionalLogicCollection $conditions): array
    {
        $cache_key = 'taxonomy_args_' . spl_object_hash($conditions);
        if (isset($this->conditional_logic_cache[$cache_key])) {
            return $this->conditional_logic_cache[$cache_key];
        }
        $args = [];
        $tax_query = [];
        /**
         * @var SingleConditionalLogic $single_condition
         */
        foreach ($conditions as $single_condition) {
            $field = $single_condition->get_field();
            $condition = $single_condition->get_condition();
            $tmp_array = [];
            if ($field instanceof WPTaxonomyField && $condition instanceof ConditionOperator && $condition instanceof Condition) {
                $key = $field::get_taxonomy();
                if ($condition instanceof ConditionWithValue) {
                    $values = $condition->get_values();
                    if (!empty($values)) {
                        $tmp_array[] = ['taxonomy' => $key, 'field' => $field::get_taxonomy_field(), 'terms' => $values, 'operator' => $condition::get_operator()];
                    }
                } else {
                    $tmp_array = ['taxonomy' => $key, 'field' => $field::get_taxonomy_field(), 'operator' => 'EXISTS'];
                }
                $tax_query[] = $tmp_array;
            }
        }
        if (!empty($tax_query)) {
            $args['tax_query'] = $tax_query;
            //phpcs:ignore
        }
        $this->conditional_logic_cache[$cache_key] = $args;
        return $args;
    }
    private function get_product_types_args(array $product_types): array
    {
        return ['tax_query' => [
            //phpcs:ignore
            'relation' => 'AND',
            ['taxonomy' => 'product_type', 'field' => 'slug', 'terms' => $product_types],
        ]];
    }
    private function create_product_variation_query(ConditionalLogicCollection $conditions, array $post_ids = []): WP_Query
    {
        $cache_key = 'variation_query_' . md5(spl_object_hash($conditions) . serialize($post_ids));
        if (isset($this->query_args_cache[$cache_key])) {
            return new WP_Query($this->query_args_cache[$cache_key]);
        }
        $args = $this->get_base_query_args(self::DEFAULT_PRODUCTS_OFFSET, self::DEFAULT_PRODUCTS_NO_LIMIT, 'product_variation');
        if (!empty($post_ids)) {
            $args['post_parent__in'] = $post_ids;
        }
        $meta_args = $this->get_conditional_logic_postmeta_args($conditions);
        if (!empty($meta_args)) {
            $args = \array_merge_recursive($args, $meta_args);
        }
        $this->query_args_cache[$cache_key] = $args;
        return new WP_Query($args);
    }
    private function create_parent_query(SingleProductTypesCollection $product_types, ConditionalLogicCollection $conditions, int $offset = self::DEFAULT_PRODUCTS_OFFSET, int $limit = self::DEFAULT_PRODUCTS_NO_LIMIT): WP_Query
    {
        $cache_key = 'parent_query_' . md5(serialize($product_types->get_product_types()) . spl_object_hash($conditions) . $offset . $limit);
        if (isset($this->query_args_cache[$cache_key])) {
            return new WP_Query($this->query_args_cache[$cache_key]);
        }
        $args = $this->get_base_query_args($offset, $limit);
        $args = \array_merge_recursive($args, $this->get_product_types_args($product_types->get_product_types()), $this->get_conditional_logic_post_args($conditions), $this->get_conditional_logic_taxonomy_args($conditions));
        $this->query_args_cache[$cache_key] = $args;
        return new WP_Query($args);
    }
    private function get_base_query_args(int $offset = self::DEFAULT_PRODUCTS_OFFSET, int $limit = self::DEFAULT_PRODUCTS_NO_LIMIT, string $post_type = 'product'): array
    {
        return ['order' => 'DESC', 'orderby' => 'ID', 'post_type' => $post_type, 'posts_per_page' => $limit, 'page' => 1, 'offset' => $offset, 'paginate' => \false, 'status' => ['draft', 'pending', 'private', 'publish'], 'fields' => 'ids'];
    }
    public function clear_cache(): void
    {
        $this->query_args_cache = [];
        $this->conditional_logic_cache = [];
    }
    private function create_product_query(SingleProductTypesCollection $product_types, ConditionalLogicCollection $conditions, int $offset, int $limit): WP_Query
    {
        $cache_key = 'product_query_' . md5(serialize($product_types->get_product_types()) . spl_object_hash($conditions) . $offset . $limit);
        if (isset($this->query_args_cache[$cache_key])) {
            return new WP_Query($this->query_args_cache[$cache_key]);
        }
        $args = $this->get_base_query_args($offset, $limit);
        $args = \array_merge_recursive($args, $this->get_product_types_args($product_types->get_product_types()), $this->get_conditional_logic_post_args($conditions), $this->get_conditional_logic_postmeta_args($conditions), $this->get_conditional_logic_taxonomy_args($conditions));
        $this->query_args_cache[$cache_key] = $args;
        return new WP_Query($args);
    }
}
