<?php

declare(strict_types=1);

namespace MyCustomizer\WooCommerce\Connector\Controller;

use DOMDocument;
use Exception;
use MyCustomizer\WooCommerce\Connector\Factory\MczrFactory;
use MyCustomizer\WooCommerce\Connector\Libs\MczrConnect;
use MyCustomizer\WooCommerce\Connector\Libs\MczrProduct;
use MyCustomizer\WooCommerce\Connector\Libs\MczrSettings;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use WC_Order_Item_Product;
use WC_Product;
use WC_Product_Variation;

class MczrCartController
{
    private $defaultQuantities = [
        'min_value' => 1,
        'max_value' => 9999,
        'step' => 1,
    ];
    /** @var MczrFactory */
    private $factory;
    /** @var MczrConnect */
    private $mczr;
    /** @var MczrProduct */
    private $productToolkit;
    /** @var array */
    private $quantities;
    /** @var Request */
    private $request;
    /** @var Response */
    private $response;
    /** @var MczrSettings */
    private $settings;
    /** @var \Twig\Environment */
    private $twig;

    public function __construct()
    {
        $this->factory = new MczrFactory();
        $this->mczr = new MczrConnect();
        $this->productToolkit = new MczrProduct();
        $this->request = Request::createFromGlobals();
        $this->response = new Response();
        $this->settings = new MczrSettings();
        $this->twig = $this->factory->getTwig();
    }

    public function init(): void
    {
        \add_action('woocommerce_checkout_create_order_line_item', [$this, 'saveItemMetasAsOrderMetas'], 10, 3);
        \add_action('woocommerce_new_order_item', [$this, 'saveItemMetasOnNewOrderItem'], 10, 3);
        \add_action('wp_ajax_addToCart', [$this, 'addToCart'], 10, 0);
        \add_action('wp_ajax_nopriv_addToCart', [$this, 'addToCart'], 10, 0);
        \add_action('woocommerce_before_cart', [$this, 'displayCartCss'], 10, 0);
        \add_action('woocommerce_before_checkout_form', [$this, 'displayCartCss'], 10, 0);
        \add_filter('woocommerce_cart_item_name', [$this, 'displayPropertiesAfterTitleInCartPage'], 10, 3);
        \add_filter('woocommerce_cart_item_thumbnail', [$this, 'filterCartImageThumbnail'], 10, 3);
        \add_filter('woocommerce_cart_item_permalink', [$this, 'filterCartItemLink'], 10, 3);
        \add_filter('woocommerce_hidden_order_itemmeta', [$this, 'hideInternalOrderItemMeta'], 10, 1);
    }

    public function saveItemMetasAsOrderMetas(WC_Order_Item_Product $item, string $_cart_item_key, array $values): void
    {
        if (isset($values['mczrMetas'])) {
            $item->update_meta_data('mczr', $values['mczrMetas']);
        }
    }

    /**
     * @param mixed $item
     */
    public function saveItemMetasOnNewOrderItem(int $_item_id, $item, int $_order_id): void
    {
        if (!($item instanceof WC_Order_Item_Product)) {
            return;
        }

        if (!empty($item->get_meta('mczr', true))) {
            return;
        }

        $mczrMetas = \get_post_meta((int) $item->get_variation_id(), '_mczr_metas', true);
        if (!is_array($mczrMetas) || empty($mczrMetas)) {
            return;
        }

        $this->saveItemMetasAsOrderMetas($item, '', ['mczrMetas' => $mczrMetas]);
        $item->save();
    }

    public function hideInternalOrderItemMeta(array $hiddenMeta): array
    {
        if (!in_array('_bundle_cart_key', $hiddenMeta, true)) {
            $hiddenMeta[] = '_bundle_cart_key';
        }

        return $hiddenMeta;
    }

    public function inputLimitFilter(array $args, ?WC_Product $_product)
    {
        $quantities = array_merge($args, $this->quantities);
        // Reset quantities for next row
        $this->quantities = $this->defaultQuantities;

        return $quantities;
    }

    private function getQuantitiesConfiguration(array $item, WC_Product $product): array
    {
        $this->quantities = $this->defaultQuantities;
        $quantities = [];
        $corresponding = [
            'min_value' => 'mczrQuantityMin',
            'max_value' => 'mczrQuantityMax',
            'step' => 'mczrQuantityStep',
        ];

        // Has input value
        $start = \get_post_meta($product->get_id(), 'mczrQuantityStart', true);
        $input = (int) $item['quantity'];
        if (!empty($start) && $start > 1 && 1 === $item['quantity']) {
            $input = (int) \get_post_meta($product->get_id(), 'mczrQuantityStart', true);
        }

        foreach ($corresponding as $name => $metaName) {
            $metaIsEmpty = \get_post_meta($product->get_id(), $metaName, true) === '';
            $quantities[$name] = ($metaIsEmpty) ? $this->defaultQuantities[$name] : (int) \get_post_meta($product->get_id(), $metaName, true);
        }

        $quantities['input_value'] = $input;

        return $quantities;
    }

    public function displayPropertiesAfterTitleInCartPage(string $product_name, array $cart_item, string $_cart_item_key): string
    {
        // Reset quantities
        $product = \wc_get_product($cart_item['product_id']);
        if (!$product->is_type('mczr')) {
            return $product_name;
        }
        if (!(isset($cart_item['mczrMetas']['summary_v2']) && count($cart_item['mczrMetas']['summary_v2']) >= 1)) {
            return $product_name;
        }
        // Set quantities
        $this->quantities = $this->getQuantitiesConfiguration($cart_item, $product);
        \add_filter('woocommerce_quantity_input_args', [$this, 'inputLimitFilter'], 99, 2);

        $vars = [
            'properties' => $cart_item['mczrMetas']['summary_v2']
        ];
        $product_name .= $this->twig->render('Cart/properties.html.twig', $vars);

        return $product_name;
    }

    public function displayCartCss(): void
    {
        echo '<style type="text/css">
		li.mczrPropsListItem {
		    overflow-wrap: break-word;
		    word-wrap: break-word;
		    -ms-word-break: break-all;
		    word-break: break-all;
		    word-break: break-word;
		    -ms-hyphens: none;
		    -moz-hyphens: none;
		    -webkit-hyphens: none;
		    hyphens: none;
		}
		</style>';
    }

    /**
     * @throws Exception
     */
    public function addToCart(): void
    {
        $this->response->headers->set('Content-Type', 'application/json');
        try {
            $productId = (int) $this->request->get('productId');
            $start = \get_post_meta($productId, 'mczrQuantityStart', true);
            $quantity = 1;
            if (!empty($start) && $start > 1 && is_numeric($start)) {
                $quantity = $start;
            }

            $props = $this->request->get('props');
            $pathDesign = "/brands/{$this->settings->get('brand')}/designs/{$props['designId']}";

            // Connect to mczr api && get the real price : avoid cheating
            $productMzcrApi = $this->mczr->get($pathDesign, true);
            $props['price'] = $productMzcrApi->price;
            $props['originalStartingPoint'] = \get_post_meta($productId, 'mczrStartingPoint', true);
            $props['startingPoint'] = $productMzcrApi->_id;

            // Generate Product Protection Token
            $pathProtectionToken = "/brands/{$this->settings->get('brand')}/designs/{$props['customizationNumber']}/protection-token";
            $designProtectionToken = $this->mczr->post($pathProtectionToken);

            $props['designProtectionToken'] = json_decode(wp_remote_retrieve_body($designProtectionToken))->token;

            // Add the variation to the cart
            $variationDatas = [
                'attributes' => [],
                'sku' => 'mczr_' . uniqid(),
                'regular_price' => $productMzcrApi->price,
                'sale_price' => $productMzcrApi->price,
                'stock_qty' => 99999,
            ];
            $variationId = $this->createVariation($productId, $variationDatas);
            \update_post_meta($variationId, '_mczr_metas', $props);
            $addedToCart = \WC()->cart->add_to_cart($productId, $quantity, $variationId, [], ['mczrMetas' => $props]);

            $cart_content = \WC()->cart->cart_contents;
            $cart_content_changed = false;
            foreach ($cart_content as $cart_item_key => $cart_item) {
                if (
                    $cart_item_key === $addedToCart
                    && !isset($cart_item['mczrMetas'])
                ) {
                    $cart_item['mczrMetas'] = $props;
                    $cart_content[$cart_item_key] = $cart_item;
                    $cart_content_changed = true;
                }
            }
            if ($cart_content_changed) {
                \WC()->cart->set_cart_contents($cart_content);
            }

            // Oups
            if (!$addedToCart) {
                throw new Exception('Unable to add to cart');
            }

            // All ok
            $vars = [
                'message' => 'Product added to cart.',
                'success' => true,
                'data' => [
                    'props' => $props,
                    'redirect' => \get_permalink(wc_get_page_id('cart')),
                    'fragments' => \apply_filters('woocommerce_add_to_cart_fragments', []),
                    'cart_hash' => \WC()->cart->get_cart_hash(),
                ],
            ];
            \do_action('woocommerce_ajax_added_to_cart', $productId);

            $this->response
                ->setContent(json_encode($vars))
                ->setStatusCode(Response::HTTP_OK)
                ->send();
        } catch (Exception $ex) {
            // All ok
            $vars = [
                'message' => 'ERROR : ' . $ex->getMessage(),
                'success' => false,
            ];
            $this->response
                ->setContent(json_encode($vars))
                ->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR)
                ->send();
        }

        exit;
    }

    public function filterCartItemLink(string $permalink, array $cart_item, string $_cart_item_key): ?string
    {
        $product = \wc_get_product($cart_item['product_id']);
        if (!($product->is_type('mczr') && isset($cart_item['mczrMetas']['customizationNumber']))) {
            return $permalink;
        }
        $productUrl = $this->productToolkit->buildProductUrl($product->get_id(), false);
        $url = str_replace('<designId>', $cart_item['mczrMetas']['customizationNumber'], $productUrl);
        if (isset($cart_item['mczrMetas']['designProtectionToken'])) {
            $url = str_replace('<token>', $cart_item['mczrMetas']['designProtectionToken'], $url);
        }

        return $url;
    }

    public function filterCartImageThumbnail(string $imgTag, array $cart_item, string $_cart_item_key): string
    {
        $product = \wc_get_product($cart_item['product_id']);
        if (!($product->is_type('mczr') && isset($cart_item['mczrMetas']['image']))) {
            return $imgTag;
        }
        // Edit image the good way
        $doc = new DOMDocument();
        libxml_use_internal_errors(true);
        $doc->loadHTML($imgTag, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        $img = $doc->getElementsByTagName('img')->item(0);
        if (!isset($img)) {
            // Edit image the wrong way I guess
            return "<img src='" . $cart_item['mczrMetas']['image'] . "' srcset='" . $cart_item['mczrMetas']['image'] . "' class='mczrDesignImg'/>";
        }
        $img->setAttribute('src', $cart_item['mczrMetas']['image']);
        $img->setAttribute('srcset', $cart_item['mczrMetas']['image']);
        // Add a specific class on mczr img for tests
        $img->setAttribute('class', $img->getAttribute('class') . ' mczrDesignImg');

        return $doc->saveHTML();
    }

    public function createVariation(int $product_id, array $variation_data): int
    {
        // Get the Variable product object (parent)
        $product = \wc_get_product($product_id);

        $variation_post = [
            'post_title' => $product->get_title(),
            'post_name' => 'mczr-product-' . $product_id . '-variation',
            'post_status' => 'publish',
            'post_parent' => $product_id,
            'post_author' => (int) \get_post_field('post_author', $product_id),
            'post_type' => 'product_variation',
            'guid' => $product->get_permalink(),
        ];

        // Creating the product variation
        $variationPostId = \wp_insert_post($variation_post);
        $variation = new WC_Product_Variation($variationPostId);

        // Process attributes
        foreach ($variation_data['attributes'] as $key => $term_name) {
            $taxonomy = 'pa_' . $key;
            // Term does not exists
            if (!term_exists($term_name, $taxonomy)) {
                \wp_insert_term($term_name, $taxonomy);
            }

            $term_slug = \get_term_by('name', $term_name, $taxonomy)
                ->slug;
            // Get the post Terms names from the parent variable product.
            $post_term_names = \wp_get_post_terms($product_id, $taxonomy, ['fields' => 'names']);

            // Check if the post term exist && if not we set it in the parent variable product.
            if (!in_array($term_name, $post_term_names)) {
                \wp_set_post_terms($product_id, $term_name, $taxonomy, true);
            }

            // Set/save the attribute data in the product variation
            \update_post_meta($variationPostId, 'attribute_' . $taxonomy, $term_slug);
        }
        // Set/save all other data
        if (!empty($variation_data['sku'])) {
            $variation->set_sku($variation_data['sku']);
        }

        // Prices
        if (empty($variation_data['sale_price'])) {
            $variation->set_price($variation_data['regular_price']);
        } else {
            $variation->set_price($variation_data['sale_price']);
            $variation->set_sale_price($variation_data['sale_price']);
        }
        $variation->set_regular_price($variation_data['regular_price']);

        // Stock
        $variation->set_manage_stock(false);
        if (!empty($variation_data['stock_qty'])) {
            $variation->set_stock_quantity($variation_data['stock_qty']);
            $variation->set_manage_stock(true);
            $variation->set_stock_status('');
        }

        // Reset weight
        $variation->set_weight('');

        return $variation->save();
    }
}
