<?php

namespace Tussendoor\Reloadify\Controllers\WooCommerce;

use ReloadifyScoped\Illuminate\Database\Eloquent\ModelNotFoundException;
use Tussendoor\Reloadify\Concerns\HasActions;
use Tussendoor\Reloadify\Concerns\HasSubscriptionCheckbox;
use Tussendoor\Reloadify\Controllers\Controller;
use Tussendoor\Reloadify\Helpers\HPOSHelper;
use Tussendoor\Reloadify\Helpers\Request;
use Tussendoor\Reloadify\Models\AbandonedCart\AbandonedCart;
use Tussendoor\Reloadify\Models\SubscriptionCheckbox;
use Tussendoor\Reloadify\Plugin;
use WC_Data_Exception;

class CartController extends Controller
{
    use HasActions;
    use HasSubscriptionCheckbox;

    /**
     * The hash we retrieve from the URL to recover a abandoned cart
     *
     * @var string
     */
    protected $hash;

    /**
     * The validated product data to use to recover the abandoned cart
     *
     * @var array
     */
    protected $productData;

    /**
     * The parameter that is set in a recovery url
     *
     * @var string
     */
    protected $recoveryKey;

    /**
     * The parameter that is set in a recovery url visited by the admin
     *
     * @var string
     */
    protected $recoveryAdminKey;

    /**
     * The model associatied with the recovery hash
     *
     * @var AbandonedCart
     */
    protected $abandonedCart;

    /**
     * The error message to display
     *
     * @var string
     */
    protected $error;

    /**
     * Whether or not to add an error message
     *
     * @var boolean
     */
    protected $add_error = false;

    /**
     * Register the class
     *
     * @return void
     */
    public function register()
    {
        $this->setProperties();
        $this->addActions();
    }

    /**
     * Set class properties
     *
     * @return void
     */
    public function setProperties()
    {
        $this->recoveryKey = Plugin::config('plugin.recovery_parameter');
        $this->recoveryAdminKey = Plugin::config('plugin.recovery_admin_parameter');
    }

    /**
     * Add actions to execute the class methods
     *
     * @return void
     */
    public function addActions()
    {
        add_action('wp_loaded', [$this, 'recoveryHandler']);
        add_action('wp_enqueue_scripts', [$this, 'loadAssets']);
        add_action('woocommerce_proceed_to_checkout', [$this, 'addSubscriptionCheckbox']);
        add_action('wp_ajax_' . self::$saveCartCheckboxConsentAction, [$this, 'saveCartConsentToSession']);
        add_action('wp_ajax_nopriv_' . self::$saveCartCheckboxConsentAction, [$this, 'saveCartConsentToSession']);
    }

    /**
     * Load assets for this WooCommerce cart
     *
     * @return void
     */
    public function loadAssets()
    {
        if (! is_cart()) return;

        wp_enqueue_script(
            Plugin::config('plugin.tag').'_cart_script',
            Plugin::config('plugin.assets').'public/js/cart.js',
            ['jquery'],
            Plugin::config('plugin.version')
        );

        wp_localize_script(
            Plugin::config('plugin.tag').'_cart_script',
            'CartController',
            array(
                'ajaxurl'                               => admin_url('admin-ajax.php'),
                'recovery_param'                        => $this->recoveryKey,
                'recovery_admin_param'                  => $this->recoveryAdminKey,
                'user_wants_to_add_checkbox_in_cart'    => ($this->userWantsToAddCheckboxInCart() ? '1' : '0'),
                'checkbox_id'                           => SubscriptionCheckbox::CONSENT_CHECKBOX_NAME,
                'save_cart_consent_action'              => self::$saveCartCheckboxConsentAction,
                'save_cart_consent_nonce' => wp_create_nonce('save_cart_consent_nonce'),

            ),
        );
    }

    /**
     * Find the abandoned cart and try to recover the data
     *
     * @return void
     */
    public function recoveryHandler()
    {
        if (is_admin() || WC()->is_rest_api_request()) {
            return;
        }

        $request = Request::fromGlobal();

        // Security: Ensure the recoveryKey is sanitized before use

        $this->hash = sanitize_text_field($request->getKey($this->recoveryKey));
        if(empty($this->hash)) {
            return;
        }

        if ($this->cartWasAlreadyRecoveredInThisSession()) {
            return;
        }

        try {
            $this->abandonedCart = AbandonedCart::where('hash', $this->hash)->firstOrFail();
            $this->validateProducts()->recoverProducts()->recoverCustomerSessionData()->saveRecoveredCartHashInSession();
        } catch (ModelNotFoundException $e) {
            $this->addError(__('Cart recovery failed: Cart not found.', 'tsd-reloadify'));
        } catch (\Exception $e) {
            $this->addError(__('Cart recovery failed.', 'tsd-reloadify'));
        } catch (WC_Data_Exception $e) {
            $this->addError(__('Cart recovery failed.', 'tsd-reloadify'));
        }
    }

    /**
     * Validate if the products in the abandoned cart can be recovered
     *
     * @return $this
     */
    protected function validateProducts()
    {

        $productData = json_decode($this->abandonedCart->product_data, true);

        foreach ($productData as $product_id => $data) {
            $product = wc_get_product($product_id);

            // Remove product from the product data
            if (!$product || !$product->is_in_stock()) {
                unset($productData[$product_id]);
                $this->addOutOfStockError($product->get_name());
            }

            // Lower quantity for this product to acceptable amount
            if ($product && $product->is_in_stock() && !$product->has_enough_stock($data['quantity'])) {
                $productData[$product_id]['quantity'] = $product->get_stock_quantity();
                $this->addLowStockAmountNotice($product, $data['quantity']);
            }

        }

        $this->productData = $productData;

        return $this;
    }

    /**
     * Recover the abandoned cart by re-adding the products
     *
     * @return self
     */
    protected function recoverProducts()
    {
        // Make sure cart is empty before recovering cart
        if(! WC()->cart->is_empty()) {
            WC()->cart->empty_cart();
        }

        foreach ($this->productData as $product_id => $data) {
            $cart_item_data = isset($data['cart_item_data']) ? $data['cart_item_data'] : [];

            if ($data['is_variable']) {
                WC()->cart->add_to_cart(
                    $product_id,
                    (int) $data['quantity'],
                    (int) $data['variation_id'],
                    (array) $data['variation_data'],
                    $cart_item_data  // Add the custom cart item data
                );
                continue;
            }

            WC()->cart->add_to_cart(
                $product_id,
                (int) $data['quantity'],
                0,  // No variation ID for simple products
                [],  // No variation data for simple products
                $cart_item_data  // Add the custom cart item data
            );
        }

        return $this;
    }

    /**
     * Add abandoned cart meta as customer data
     * Can throw WC_Data_Exception on fail
     *
     * @since 1.4.0 Return early if abandoned cart is empty
     * @since 1.5.4 Recover billing and shipping data for cart recovery
     *
     * @return self
     */
    protected function recoverCustomerSessionData()
    {
        if (empty($this->abandonedCart)) {
            return $this;
        }

        // Basic customer info
        WC()->customer->set_billing_first_name(esc_html($this->abandonedCart->get_meta('first_name')));
        WC()->customer->set_billing_last_name(esc_html($this->abandonedCart->get_meta('last_name')));
        WC()->customer->set_billing_email(esc_html($this->abandonedCart->get_meta('email')));

        // Billing address
        WC()->customer->set_billing_address_1(esc_html($this->abandonedCart->get_meta('billing_street')));
        WC()->customer->set_billing_city(esc_html($this->abandonedCart->get_meta('billing_city')));
        WC()->customer->set_billing_state(esc_html($this->abandonedCart->get_meta('billing_state')));
        WC()->customer->set_billing_postcode(esc_html($this->abandonedCart->get_meta('billing_postcode')));
        WC()->customer->set_billing_country(esc_html($this->abandonedCart->get_meta('billing_country')));

        // Shipping address
        WC()->customer->set_shipping_address_1(esc_html($this->abandonedCart->get_meta('shipping_street')));
        WC()->customer->set_shipping_city(esc_html($this->abandonedCart->get_meta('shipping_city')));
        WC()->customer->set_shipping_state(esc_html($this->abandonedCart->get_meta('shipping_state')));
        WC()->customer->set_shipping_postcode(esc_html($this->abandonedCart->get_meta('shipping_postcode')));
        WC()->customer->set_shipping_country(esc_html($this->abandonedCart->get_meta('shipping_country')));

        return $this;
    }

    /**
     * Add notice for products with lower available quantity then is asked for
     * When a product is saved with an amount that is not available anymore we add this notice
     *
     * @see validateProducts()
     *
     * @param  \WC_Product $product
     * @param  int $quantity
     * @return void
     */
    protected function addLowStockAmountNotice(\WC_Product $product, int $quantity)
    {
        $message = sprintf(
            /* Translators: %d orginal amount in cart, %s product name, %d amount in stock, %s plural or singular word for "being", Dutch: "zijn" or "is". %d amount added */
            esc_html__('Helaas kunnen we niet meer de %d "%s" producten toevoegen omdat er nog maar %d op voorraad %s. %d toegevoegd.', 'tsd-reloadify'),
            $quantity,
            '<strong><a href="' . $product->get_permalink() . '">' . esc_html($product->get_name()) . '</a></strong>',
            wc_format_stock_quantity_for_display($product->get_stock_quantity(), $product),
            $product->get_stock_quantity() === 1 ? esc_html__('is', 'tsd-reloadify') : esc_html__('zijn', 'tsd-reloadify'),
            wc_format_stock_quantity_for_display($product->get_stock_quantity(), $product),
        );

        wc_add_notice($message, 'notice');
    }

    /**
     * Add error when a product needs to be recovered that is not in stock anymore
     *
     * @see validateProducts()
     *
     * @param  string $name
     * @return void
     */
    protected function addOutOfStockError(string $name)
    {
        $message = sprintf(
            esc_html__('Helaas kunnen we niet meer de "%s" toevoegen omdat deze niet meer op voorraad is.', 'tsd-reloadify'),
            /* Translators: %s product name */
            '<strong>' . esc_html($name) . '</strong>',
        );

        wc_add_notice($message, 'error');
    }

    /**
     * Save the recovered cart hash in the session
     *
     * @return void
     */
    private function saveRecoveredCartHashInSession()
    {

        if (!function_exists('WC') || empty(WC()->session) || !WC()->session->has_session()) {
            return false;
        }

        WC()->session->set('reloadify_cart_recovered', $this->hash);
    }

    /**
     * Check if the cart was already recovered in this session based on the hash property.
     *
     * @return bool True if the cart was already recovered, false otherwise.
     */
    private function cartWasAlreadyRecoveredInThisSession()
    {
        // Ensure WC session is available and has started.
        if (!function_exists('WC') || empty(WC()->session) || !WC()->session->has_session()) {
            return false;
        }


        // Retrieve the recovered cart hash from the session, if available.
        $recoveredHash = WC()->session->get('reloadify_cart_recovered', '');


        // Compare the current hash with the one stored in the session.
        return $this->hash === $recoveredHash;
    }


    /**
     * Save the cart consent checkbox value to the session
     *
     * @return void
     */
    public function saveCartConsentToSession()
    {
        check_ajax_referer('save_cart_consent_nonce', '_ajax_nonce', true);

        if (isset($_POST['consent'])) {
            $consent = sanitize_text_field($_POST['consent']) === 'true' ? '1' : '0';
            WC()->session->set(SubscriptionCheckbox::CONSENT_CHECKBOX_SESSION_KEY, $consent);
            wp_send_json_success(WC()->session->get(SubscriptionCheckbox::CONSENT_CHECKBOX_SESSION_KEY));
        } else {
            wp_send_json_error('Consent data missing.');
        }
    }


    /**
     * Adds an error message and sets add_error flag
     *
     * @param string $message Error message to display
     */
    private function addError($message)
    {
        $this->add_error = true;
        $this->error = $message;
        wc_add_notice($message, 'error');
    }

}
