<?php
declare(strict_types=1);
 
namespace Edara\Includes;

use WC_Product;

class ProductService extends AbstractService
{
 
    /**
     * Predefined discount meta keys
     *
     * @var array|string[]
     */
    private array $discountMetaKeys = [
        'schedule_sale_start_date', 'schedule_sale_end_date', 'schedule_sale_price'
    ];

    /**
     * Create new product the from Edara
     *
     * @param array $data
     * @throws \WC_Data_Exception
     */
    public function createProduct(array $data = []):void
    {
        $stockService = new StockService();
        $externalProductId = array_get($data, 'data.id');
        $stockBalance = $stockService->retrieveStockAvailableBalance($externalProductId);
        $product = new WC_Product();
        $product->set_name(array_get($data, 'data.description'));
        $product->set_catalog_visibility('visible');
        $product->set_price(array_get($data, 'data.price'));
        $product->set_regular_price(array_get($data, 'data.price'));
        $product->set_manage_stock(true);
        $product->set_stock_quantity($stockBalance);
        // Set stock status based of stock quantity
        $product->set_stock_status($stockBalance ? 'instock' : 'outofstock');
        $product->set_status('draft');
        // Assign the right category to product
        //        $categoryId = $this->getCategoryIdByClassificationId(array_get($data, 'data.classification_id'));
        //        if ($categoryId) {
        //            $product->set_category_ids($categoryId);
        //        }
        $productId = $product->save();
        add_post_meta($productId, 'external_info', $data);
        add_post_meta($productId, 'external_id', array_get($data, 'data.id'));
        add_post_meta($productId, 'external_code_id', array_get($data, 'data.code'));
        add_post_meta($productId, ProductTaxRate::EXTERNAL_PRODUCT_TAX_RATE, array_get($data, 'data.tax_rate'));
        $product->set_sku(array_get($data, 'data.part_number'));
        $product->save();

        if ($productId) {
            $this->setProductDiscount(wc_get_product($productId), array_get($data, 'data', []));
        }
    }

    /**
     * Update product
     *
     * @param array $data
     * @throws \GuzzleHttp\Exception\GuzzleException
     * @throws \WC_Data_Exception
     */
    public function updateProduct(array $data = []):void
    {
        $externalProductId = array_get($data, 'data.id');
        $product = $this->findProductByExternalId($externalProductId);

        if (!$product) {
            $this->createProduct($data);
            exit;
        }
        $stockService = new StockService();
        $stockBalance = $stockService->retrieveStockAvailableBalance($externalProductId);

        // $product->set_name(array_get($data, 'data.description'));
        $product->set_catalog_visibility('visible');
        $product->set_price(array_get($data, 'data.price'));
        $product->set_regular_price(array_get($data, 'data.price'));
        $product->set_manage_stock(true);
        $product->set_stock_quantity($stockBalance);
        // Set stock status based of stock quantity
        $product->set_stock_status($stockBalance ? 'instock' : 'outofstock');
        //        // Assign the right category to product
        //        $categoryId = $this->getCategoryIdByClassificationId(array_get($data, 'data.classification_id'));
        //        if ($categoryId) {
        //            $product->set_category_ids($categoryId);
        //        }
        $productId = $product->save();
        update_post_meta($productId, 'external_info', $data);
        update_post_meta($productId, 'external_id', array_get($data, 'data.id'));
        update_post_meta($productId, 'external_code_id', array_get($data, 'data.code'));
        update_post_meta($productId, ProductTaxRate::EXTERNAL_PRODUCT_TAX_RATE, array_get($data, 'data.tax_rate'));
        $product->set_sku(array_get($data, 'data.part_number'));
        $product->save();

        $this->setProductDiscount(wc_get_product($productId), array_get($data, 'data', []));
        // Update price of the translated product
        $this->updatePriceOfTranslatedProduct($productId, wc_get_product($productId)->get_price());
    }

    /**
     * Find product by the external Id
     *
     * @param int $externalProductId
     * @return \WC_Product_Simple
     */
    // phpcs:ignore
    public function findProductByExternalId(int $externalProductId)
    {
        return $this->findProductByMeta($externalProductId, 'external_id');
    }

    /**
     * Find grouped product by the external bundle Id
     *
     * @param int $externalProductId
     * @return \WC_Product_Simple
     */
    // phpcs:ignore
    public function findGroupedProductByExternalBundleId(int $externalBundleId) {
        return $this->findProductByMeta($externalBundleId, 'external_bundle_id');
    }

    /**
     * Find product by the external Id
     *
     * @param int $externalProductId
     * @return \WC_Product_Simple
     */
    // phpcs:ignore
    private function findProductByMeta(int $externalId, string $metaKey = 'external_id') {
        $args = [
            'post_type' => 'product',
            'meta_key' => $metaKey,
            'meta_value' => $externalId,
            'meta_compare' => '=',
            'numberposts' => '1',
            'status' => ['draft', 'pending', 'private', 'publish']
        ];

        $products = wc_get_products($args);
        if ($products) {
            return $products[0];
        }

        return false;
    }

    /**
     * Get the category from classification
     *
     * @param array $data
     * @return int
     */
    private function getCategoryIdByClassificationId(int $classificationId):int
    {
        $classifications = $this->prepareClassifications(false);

        if (!$classifications || ! is_array($classifications)) {
            return 0;
        }

        return array_get($classifications, "$classificationId.classification_id", 0);
    }

    /**
     * Daily balance checker
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function dailyBalanceChecker():void
    {
        $limit = 30;
        $args = [
            'post_type' => 'product',
            'meta_query' => [['key' => 'external_id']],
            'offset' => $_POST['offset'] * $limit,
            'numberposts' => $limit,
        ];

        $data = [];
        foreach (get_posts($args) as $post) {
            $externalId = (int) get_post_meta($post->ID, 'external_id', true);
            /** @var \WC_Product_Simple $product */
            $product = $this->findProductByExternalId($externalId);
            // Product not exist
            if (!$product) {
                continue;
            }

            $stockBalance = (new StockService())->retrieveStockAvailableBalance($externalId);

            // Update the product quantity
            $product->set_stock_quantity($stockBalance);
            $product->save();
            $data[] = [
                'post_id' => $post->ID,
                'external_id' => $externalId,
                'stock_balance' => $stockBalance,
            ];
        }

        $myFileLink2 = fopen((ABSPATH . 'response'), 'w+');
        fwrite($myFileLink2, json_encode($data));
        fclose($myFileLink2);
    }

    /**
     * Add product seeder method
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function seedProducts():void
    {
        $classifications = $this->prepareClassifications();
        $products = wc_get_products(['limit' => 1000]);
        /** @var \WC_Product_Simple $product */
        foreach ($products as $product) {
            // Escape if the product already have external code id which is mean [it's seeded before]
            if (get_post_meta($product->get_id(), 'external_code_id', true)) {
                continue;
            }

            $data = [
                'description' => $product->get_name(),
                'price' => $product->get_price(),
                'purchase_price' => $product->get_price(),
            ];

            $id = $this->findTheRemoteClassificationId($classifications, $product->get_category_ids());
            if ($id) {
                $data['classification_id'] = $id;
            }

            $response = $this->api()->createStockItem($data);
            if (200 === (int) array_get($response, 'status_code')) {
                $externalId = (int) array_get($response, 'result');
                // Update post meta with new code and product id
                update_post_meta($product->get_id(), 'external_id', $externalId);
                update_post_meta($product->get_id(), 'external_code_id', $externalId);
            }
        }
    }

    /**
     * Prepare classification list
     *
     * @return array
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    private function prepareClassifications($groupedByLocal = true): array
    {
        $classifications = array_get($this->api()
            ->retrieveClassifications(), 'result', []);

        $filter = fn($item) => EDARA_PARENT_CLASSIFICATION_ID === (int) array_get($item, 'parent_id');
        $classifications = array_filter($classifications, $filter);

        $output = [];
        foreach ($classifications as $key => $item) {
            $localCategoryId = substr($item['classification_code'], 3, 10);
            $output[$groupedByLocal ? $localCategoryId : $item['classification_id']] = $item;
            $output[$groupedByLocal ? $localCategoryId : $item['classification_id']]['local_category_id'] = $localCategoryId;
        }

        return $output;
    }

    /**
     * Find remote classification by Id
     *
     * @param array $classifications
     * @param array $ids
     * @return int
     */
    private function findTheRemoteClassificationId(array $classifications, array $ids): int
    {
        $id = 0;
        if (!$ids) {
            return $id;
        }
        $id = reset($ids);

        return (int) array_get($classifications, "{$id}.classification_id", 0);
    }

    /**
     * Delete Products
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function resetProducts():void
    {
        $response = $this->api()->listStockItems(5, 0);
        $totalCount = array_get($response, 'total_count');

        for ($i = 10; $i <= $totalCount; $i = $i + 10) {
            $response = $this->api()->listStockItems($i, 0);
            $products = array_get($response, 'result');
            foreach ($products as $product) {
                $output = $this->api()->deleteStockItemById(array_get($product, 'id'));
            }
        }
    }

    /**
     * Remove all draft products and Edara key in DB
     */
    public function resetLocalProducts(): void
    {
        $args = ['post_status' => 'draft', 'limit' => 10000];
        foreach (wc_get_products($args) as $product) {
            wp_delete_post($product->get_id());
        }

        $args = ['post_status' => 'trash', 'limit' => 10000];
        foreach (wc_get_products($args) as $product) {
            wp_delete_post($product->get_id());
        }

        global $wpdb;
        $wpdb->get_results("delete FROM wp_postmeta WHERE meta_key like '%external%'");
    }

    public function bundleSeeder(): void
    {
        $offset = 0;
        $limit = 50;
        while (true) {
            $response = $this->api()->listBundles($limit, $offset * $limit);
            $bundles = array_get($response, 'result');
            # If no bundles go out
            if (!$bundles) {
                break;
            }

            foreach ($bundles as $bundle) {
                #1 Prepare children products ids
                $childrenProductIdsWithIds = $this->getChildrenProductIdsWithQntFromBundle(array_get($bundle, 'bundle_details') ?: []);
                $externalBundleId = array_get($bundle, 'id');

                #2 Find grouped products by bundle id
                $groupedProduct = $this->findGroupedProductByExternalBundleId($externalBundleId);

                $title = array_get($bundle, 'description');
                #3 Update the grouped product if exist
                if ($groupedProduct) {
                    $this->updateGroupedProducts($groupedProduct, $title, $bundle, $externalBundleId, $childrenProductIdsWithIds);
                    continue;
                }
                #3 Create a new grouped product
                $this->createGroupedProducts($title, $bundle, $externalBundleId, $childrenProductIdsWithIds);
            }

            $offset++;
        }
    }

    /**
     * Create grouped products
     *
     * @param string $title
     * @param int $externalBundleId
     * @param array $groupedProductIds
     */
    public function createGroupedProducts(string $title, array $bundle, int $externalBundleId, array $groupedProductWithQntIds = []): void
    {
        $childrenProductIds = "";
        foreach ($groupedProductWithQntIds as $productId => $qnt) {
            $childrenProductIds .= "{$productId}/$qnt,";
        }
        $childrenProductIds = trim($childrenProductIds, ',');

        $price = 0;
        if (array_get($bundle, 'bundle_details')) {
            foreach (array_get($bundle, 'bundle_details') as $item) {
                $price += ((float) $item['quantity']) * (float) $item['price'];
            }
        }

        $smartBundle = new \WC_Product_Woosb();
        $smartBundle->set_name($title);
        $smartBundle->set_status('draft');
        $postId = $smartBundle->save();
        update_post_meta($postId, 'woosb_ids', \WPCleverWoosb_Helper::woosb_clean_ids($childrenProductIds));
        $productIds = array_keys($groupedProductWithQntIds);

        $originalPrice = 0;
        foreach ($productIds as $productId) {
            $product = wc_get_product($productId);
            $originalPrice += (float) ($product->get_price() * $groupedProductWithQntIds[$productId]);
        }

        // Display discount
        $smartBundle->set_regular_price((float) $originalPrice);
        $smartBundle->set_sale_price((float) ($price));
        $smartBundle->save();
        update_post_meta($postId, 'woosb_discount_amount', (float) ($originalPrice - $price));
        update_post_meta($postId, 'external_bundle_id', $externalBundleId);
        update_post_meta($postId, 'woosb_disable_auto_price', 'on');
    }

    /**
     * Update grouped products
     *
     * @param string $title
     * @param int $externalBundleId
     * @param array $groupedProductIds
     */
    public function updateGroupedProducts(WC_Product $smartBundle, string $title, array $bundle, int $externalBundleId, array $groupedProductWithQntIds = []):void
    {
        $childrenProductIds = "";
        foreach ($groupedProductWithQntIds as $productId => $qnt) {
            $childrenProductIds .= "{$productId}/$qnt,";
        }
        $childrenProductIds = trim($childrenProductIds, ',');
        $price = 0;
        if (array_get($bundle, 'bundle_details')) {
            foreach (array_get($bundle, 'bundle_details') as $item) {
                $price += ((float) $item['quantity']) * (float) $item['price'];
            }
        }

        //        $smartBundle->set_name($title);
        // If not products set as draft
        if (!$groupedProductWithQntIds) {
            $smartBundle->set_status('draft');
        }

        $postId = $smartBundle->save();

        update_post_meta($postId, 'woosb_ids', \WPCleverWoosb_Helper::woosb_clean_ids($childrenProductIds));
        $productIds = array_keys($groupedProductWithQntIds);

        $originalPrice = 0;
        foreach ($productIds as $productId) {
            $product = wc_get_product($productId);
            $originalPrice += (float) ($product->get_price() * $groupedProductWithQntIds[$productId]);
        }
        $smartBundle->set_regular_price((float) $originalPrice);
        $smartBundle->set_sale_price((float) ($price));
        $smartBundle->save();
        // Display discount
        update_post_meta($postId, 'woosb_discount_amount', (float) ($originalPrice - $price));
        update_post_meta($postId, 'external_bundle_id', $externalBundleId);
        update_post_meta($postId, 'woosb_disable_auto_price', 'on');
    }

    /**
     * Get children products ids
     *
     * @param array $bundle
     * @return array
     * @throws \Exception
     */
    private function getChildrenProductIdsWithQntFromBundle(array $bundle): array
    {
        $childrenProductIdsWithQnt = [];
        foreach ($bundle as $stockItem) {
            $externalProductId = intval(array_get($stockItem, 'stock_item_id'));
            $childrenProduct = $this->findProductByExternalId($externalProductId);

            // Check product is exist
            if (!$childrenProduct) {
                throw new \Exception('Product is not exist in database.');
            }
            $childrenProductIdsWithQnt[$childrenProduct->get_id()] = array_get($stockItem, 'quantity');
        }

        return $childrenProductIdsWithQnt;
    }

    /**
     * Seed the SKU from edara to wordpress
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     * @throws \WC_Data_Exception
     */
    public function productSKUSeeder():void {

        $offset = 0;
        $limit = 100;
        while (true) {
            $response = $this->api()->listStockItems($limit, $offset * $limit);
            $stockItems = array_get($response, 'result');
            # If no bundles go out
            if (!$stockItems) {
                break;
            }

            foreach ($stockItems as $stockItem) {
                $partNumber = array_get($stockItem, 'part_number');
                if (!$partNumber) {
                    continue;
                }

                $externalProductId = array_get($stockItem, 'id');
                /** @var \WC_Product_Simple $product */
                $product = $this->findProductByExternalId($externalProductId);

                if (!$product || !$partNumber) {
                    continue;
                }
                // Set SKU
                $product->set_sku($partNumber);
                $product->save();
            }

            $offset++;
        }
    }

    /**
     * Seed discounts from Edara to WP
     */
    public function seedDiscounts():void
    {
        $offset = 0;
        $limit = 100;
        while (true) {
            $response = $this->api()->listStockItems($limit, $offset * $limit);
            $stockItems = array_get($response, 'result');

            foreach ($stockItems as $stockItem) {
                $discount = (float)array_get($stockItem, 'sales_price_discount', 0);
                $isPercentage = (bool)array_get($stockItem, 'sales_price_discount_type');
                $hasDiscountPeriod = (bool)array_get($stockItem, 'sales_price_discount_is_limited');
                $discountDateFrom = array_get($stockItem, 'sales_price_discount_date_from');
                $discountDateTo = array_get($stockItem, 'sales_price_discount_date_to');
                $regularPrice = (float)array_get($stockItem ,'price');
                $externalProductId = array_get($stockItem, 'id');

                /** @var \WC_Product_Simple $product */
                $product = $this->findProductByExternalId($externalProductId);

                if (!$product) {
                    continue;
                }

                // Set product discount Fixed price or percentage
                $newSalesPrice = ($regularPrice - $discount);
                if ($isPercentage) {
                    $newSalesPrice = $regularPrice * ((100 - $discount) / 100);
                }

                // Set sale price immediately as there is no due date is defined
                if (!$hasDiscountPeriod) {
                    $product->set_price($regularPrice);
                    $product->set_sale_price($newSalesPrice);
                    $product->save();
                    continue;
                }

                // Update product meta to be checked by cron to stop start or end the
                update_post_meta($product->get_id() ,'schedule_sale_start_date', strtotime($discountDateFrom));
                update_post_meta($product->get_id() ,'schedule_sale_end_date', strtotime($discountDateTo));
                update_post_meta($product->get_id() ,'schedule_sale_price', (float) $newSalesPrice);
            }

            $offset++;
        }
    }

    /**
     * Set product discount
     *
     * @param  \WC_Product_Simple  $product
     * @param  array  $data
     */
    private function setProductDiscount(\WC_Product_Simple $product, array $data)
    {
        $discountDateFrom = $discountDateTo = 0;
        $discount = (float)array_get($data, 'sales_price_discount', 0);
        $isPercentage = (bool)array_get($data, 'sales_price_discount_type');
        $hasDiscountPeriod = (bool)array_get($data, 'sales_price_discount_is_limited');
        $regularPrice = (float)array_get($data ,'price');
        // Prepare discount date from
        if (array_get($data, 'sales_price_discount_date_from')) {
            $discountDateFrom = strtotime(date('Y-m-d', strtotime(array_get($data, 'sales_price_discount_date_from'))));
        }
        // Prepare discount date to
        if (array_get($data, 'sales_price_discount_date_to')) {
            $discountDateTo = strtotime(date('Y-m-d', strtotime(array_get($data, 'sales_price_discount_date_to'))));
        }

        // Reset price/sale price of product
        $product->set_price($regularPrice);
        $product->set_sale_price('');
        $product->save();
        $this->updatePriceOfTranslatedProduct($product->get_id(), $regularPrice);

        // Set product discount Fixed price or percentage
        $newSalesPrice = ($regularPrice - $discount);
        if ($isPercentage) {
            $newSalesPrice = $regularPrice * ((100 - $discount) / 100);
        }

        $this->deleteDiscountMetaKeys($product->get_id());

        // Set sale price immediately as there is no due date is defined
        if (!$hasDiscountPeriod) {
            $product->set_price($regularPrice);
            $product->set_sale_price($newSalesPrice);
            $product->save();
            $this->updatePriceOfTranslatedProduct($product->get_id(), $newSalesPrice);
            return;
        }

        // Check time now is after the starting discount date then I have to start it immediately
        if (time() > $discountDateFrom && time() < $discountDateTo) {
            $product->set_price($regularPrice);
            $product->set_sale_price($newSalesPrice);
            $product->save();
            $this->updatePriceOfTranslatedProduct($product->get_id(), $newSalesPrice);
        }

        // Update product meta to be checked by cron to stop start or end the
        update_post_meta($product->get_id() ,'schedule_sale_start_date', $discountDateFrom);
        update_post_meta($product->get_id() ,'schedule_sale_end_date', $discountDateTo);
        update_post_meta($product->get_id() ,'schedule_sale_price', (float) $newSalesPrice);
    }

    /**
     * Check product discounts
     */
    public function checkProductDiscountDueDates()
    {
        global $wpdb;
        $today = strtotime('today');
        // Query the end date is before or equal today
        $rows = $wpdb->get_results("select * from {$wpdb->postmeta} where meta_key = 'schedule_sale_end_date' and meta_value <= $today");
        #1 Remove the discount from products if the discount is expired
        foreach ($rows as $row) {
            $endDate = $row->meta_value;
            // Should remove the discount from product
            if ($today >= $endDate) {
                $product = wc_get_product($row->post_id);
                $product->set_sale_price('');
                $product->save();
                $this->updatePriceOfTranslatedProduct($product->get_id(), $product->get_price());
                $this->deleteDiscountMetaKeys($product->get_id());
            }
        }
        // Query before or equal today
        $rows = $wpdb->get_results("select * from {$wpdb->postmeta} where meta_key = 'schedule_sale_start_date' and meta_value <= $today");
        #2 Set the discount for products if we are in start date
        foreach ($rows as $row) {
            $startDate = $row->meta_value;
            if ($today >= $startDate) {
                $product = wc_get_product($row->post_id);
                $salePrice = get_post_meta($product->get_id(), 'schedule_sale_price', true);
                $product->set_sale_price($salePrice);
                $product->save();
                $this->updatePriceOfTranslatedProduct($product->get_id(), $salePrice);
            }
        }
    }

    /**
     * Update price of translated product
     *
     * @param $productId
     * @param $price
     */
    private function updatePriceOfTranslatedProduct($productId, $price): void
    {
        // Update price of the translated product
        $translatedProductId = apply_filters('wpml_object_id', $productId, 'product', false, 'ar');
        if ($translatedProduct = wc_get_product($translatedProductId)) {
            $translatedProduct->set_price($price);
            $translatedProduct->save();
            update_post_meta($translatedProductId, '_price', $price);
        }
    }

    /**
     * Delete the discount meta keys from product
     *
     * @param $productId
     */
    private function deleteDiscountMetaKeys($productId): void
    {
        ## Remove post metas
        foreach ($this->discountMetaKeys as $key) {
            delete_post_meta($productId, $key);
        }
    }

    /**
     * Sync product names from WP to Edara
     */
    public function syncProductsNamesFromWPToEdara(): void
    {
        $offset = $_POST['offset'];
        $limit = 30;
        $mappedClassifications = $this->prepareClassifications();
        $products = wc_get_products(['limit' => $limit, 'offset' => $offset * $limit]);

        /** @var $product \WC_Product */
        foreach ($products as $product) {
            // Simple products only
            if ('simple' !== $product->get_type()) {
                continue;
            }

            $externalId = get_post_meta($product->get_id(), 'external_id', true);
            $enTitle = $arTitle = $product->get_title();
            // $translatedProductId = wpml_object_id_filter($product->get_id(), 'post', FALSE, 'ar');
            $translatedProductId = apply_filters( 'wpml_object_id', $product->get_id(), 'post', FALSE, 'ar'  );
            if ($translatedProductId) {
                $arTitle = wc_get_product($translatedProductId)->get_title();
            }

            $externalProduct = $this->api()->findStockItem($externalId);
            $data = array_get($externalProduct ,'result');
            if (!$data) {
                continue;
            }

            $id = $this->findTheRemoteClassificationId($mappedClassifications, $product->get_category_ids());
            $data['classification_id'] = $id ?: EDARA_DEFAULT_CLASSIFICATION;
            $data['description'] = $enTitle;
            $data['other_lang_description'] = $arTitle;
            $this->api()->updateStockItem($data);
        }
    }

    /**
     * Sync categories
     *
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function syncCategories():void
    {
        $localCategories = get_terms('product_cat');
        /** @var \WP_Term $localCategory */
        foreach ($localCategories as $localCategory) {
            $payload = [
                'classification_code' => "zu_$localCategory->term_id",
                'description' => $localCategory->name,
                'parent_id' => EDARA_PARENT_CLASSIFICATION_ID,
                'tree_level' => 2
            ];
            $this->api()->createClassification($payload);
        }
    }
}
