<?php

/**
 * Cart Validation Trait
 *
 * Handles add-to-cart validation, cart quantity updates, composition rules,
 * and checkout customer restriction checks.
 *
 * @package ArraySubs\Features\SubscriptionCheckout\Services\Traits
 */

namespace ArraySubs\Features\SubscriptionCheckout\Services\Traits;

defined('ABSPATH') || exit;

/**
 * Cart validation methods
 */
trait CartValidationTrait
{
    /**
     * Validate add to cart for subscription products
     * Enforces multiple subscription settings and trial restrictions
     *
     * @param bool  $passed     Current validation status
     * @param mixed $product_id Product ID being added
     * @param mixed $quantity   Quantity being added
     * @param mixed $variation_id Variation ID (if applicable). WooCommerce may pass this as a string from the request.
     * @param mixed $variations Variation attributes
     * @return bool Whether the product can be added
     */
    public function validateAddToCart($passed, $product_id = 0, $quantity = 1, $variation_id = 0, $variations = []): bool
    {
        $passed       = (bool) $passed;
        $product_id   = absint($product_id);
        $quantity     = absint($quantity);
        $variation_id = absint($variation_id);

        if (!$passed) {
            return $passed;
        }

        // Validate product ID
        if ($product_id <= 0) {
            return $passed;
        }

        // Check if helper functions exist
        if (!function_exists('arraysubs_is_subscription_product') || !function_exists('arraysubs_get_customer_subscriptions')) {
            return $passed;
        }

        // Check if the product being added is a subscription
        $check_id = $variation_id > 0 ? $variation_id : $product_id;
        $is_subscription = arraysubs_is_subscription_product($check_id);

        // Non-subscription product: check mixed-cart restriction if cart has subscriptions
        if (!$is_subscription) {
            // One-click checkout products replace the cart, skip composition checks
            if (
                function_exists('arraysubs_is_one_click_checkout_product')
                && arraysubs_is_one_click_checkout_product($check_id)
            ) {
                return $passed;
            }

            if (function_exists('WC') && WC()->cart && !empty(WC()->cart->get_cart())) {
                $allow_mixed_default = (bool) arraysubs_get_setting('multiple_subscriptions.allow_mixed_cart', true);
                $allow_mixed         = apply_filters('arraysubs_allow_mixed_cart', $allow_mixed_default);

                if (!$allow_mixed) {
                    foreach (WC()->cart->get_cart() as $cart_item) {
                        $cid = ($cart_item['variation_id'] ?? 0) > 0 ? $cart_item['variation_id'] : $cart_item['product_id'];
                        if (arraysubs_is_subscription_product($cid)) {
                            wc_add_notice(
                                $this->getMixedCartRestrictionMessage(),
                                'error'
                            );
                            return false;
                        }
                    }
                }
            }

            return $passed;
        }

        // Cart composition checks (apply to all users including guests)
        $passed = $this->validateCartComposition($passed, $check_id);
        if (!$passed) {
            return false;
        }

        $passed = $this->validateSingleProductQuantity($passed, $check_id, $quantity);
        if (!$passed) {
            return false;
        }

        $passed = $this->validateSingleTrialQuantity($passed, $check_id, $quantity);
        if (!$passed) {
            return false;
        }

        // One subscription per customer: prevent adding a second distinct subscription to the cart.
        // This closes the gap where a new customer (no DB subscriptions) could add multiple
        // subscription products and end up with more than one subscription after checkout.
        if (
            function_exists('arraysubs_is_one_subscription_per_customer')
            && arraysubs_is_one_subscription_per_customer()
            && function_exists('WC') && WC()->cart
            && !(function_exists('arraysubs_is_one_click_checkout_product') && arraysubs_is_one_click_checkout_product($check_id))
        ) {
            foreach (WC()->cart->get_cart() as $cart_item) {
                $item_check_id = $this->getCartItemProductId($cart_item);
                if (
                    $item_check_id > 0
                    && $item_check_id !== $check_id
                    && arraysubs_is_subscription_product($item_check_id)
                ) {
                    wc_add_notice(
                        $this->getOneSubscriptionPerCustomerCartMessage(),
                        'error'
                    );
                    return false;
                }
            }
        }

        $user_id = get_current_user_id();
        $identity = $this->getCheckoutIdentity([
            'customer_id' => $user_id,
        ]);

        // Guest users - skip subscription-per-customer checks (they can't have existing subscriptions)
        if ($user_id <= 0) {
            return $passed;
        }

        // Check: One subscription per customer
        if (function_exists('arraysubs_is_one_subscription_per_customer') && arraysubs_is_one_subscription_per_customer()) {
            $existing_subscriptions = arraysubs_get_customer_subscriptions_for_identity(
                $identity['customer_id'],
                $identity['email'],
                $this->getRestrictedSubscriptionStatuses()
            );

            if (!empty($existing_subscriptions)) {
                if (function_exists('arraysubs_can_auto_migrate_subscription_on_checkout') && arraysubs_can_auto_migrate_subscription_on_checkout()) {
                    $migration_context = $this->getCheckoutMigrationContext($check_id, $quantity, $identity);

                    if ($migration_context['eligible']) {
                        return $passed;
                    }

                    wc_add_notice($migration_context['message'], 'error');
                    return false;
                }

                wc_add_notice(
                    __('One Subscription per Customer is enabled and this account already has a live subscription. Manage the existing subscription first, or choose an allowed plan switch if checkout migration is enabled.', 'arraysubs'),
                    'error'
                );
                return false;
            }
        }

        // Check: One trial per customer
        if (function_exists('arraysubs_is_one_trial_per_customer') && arraysubs_is_one_trial_per_customer()) {
            if (function_exists('arraysubs_product_has_free_trial') && arraysubs_product_has_free_trial($check_id)) {
                // Check if customer has ever had a trial subscription (including expired/cancelled)
                $past_trials = arraysubs_get_customer_subscriptions($user_id, ['arraysubs-active', 'arraysubs-on-hold', 'arraysubs-trial', 'arraysubs-cancelled', 'arraysubs-expired']);
                foreach ($past_trials as $sub_id) {
                    $had_trial = (int) get_post_meta($sub_id, '_trial_length', true) > 0;
                    if ($had_trial) {
                        wc_add_notice(
                            __('You have already used a free trial. Free trials are limited to one per customer.', 'arraysubs'),
                            'error'
                        );
                        return false;
                    }
                }
            }
        }

        return $passed;
    }

    /**
     * Validate cart quantity updates for subscription products.
     *
     * @param bool   $passed        Current validation status.
     * @param string $cart_item_key Cart item key being updated.
     * @param array  $values        Cart item values.
     * @param int    $quantity      Requested quantity.
     * @return bool
     */
    public function validateCartQuantityUpdate(bool $passed, string $cart_item_key, array $values, $quantity): bool
    {
        if (!$passed) {
            return $passed;
        }

        $quantity = absint($quantity);

        if ($quantity <= 0) {
            return $passed;
        }

        $check_id = $this->getCartItemProductId($values);

        if ($check_id <= 0 || !arraysubs_is_subscription_product($check_id)) {
            return $passed;
        }

        // One per product: block quantity increase above 1
        if (
            $quantity > 1
            && function_exists('arraysubs_is_one_subscription_per_product')
            && arraysubs_is_one_subscription_per_product()
        ) {
            wc_add_notice($this->getSingleProductQuantityMessage(), 'error');
            return false;
        }

        if (!$this->exceedsAllowedTrialCartQuantity($check_id, $quantity, $cart_item_key)) {
            return $passed;
        }

        wc_add_notice($this->getTrialCartValidationMessage(), 'error');

        return false;
    }

    /**
     * Validate checkout restrictions for guests and email-matched returning customers.
     *
     * @param array     $data   Posted checkout data.
     * @param \WP_Error $errors Checkout validation errors.
     * @return void
     */
    public function validateCheckoutCustomerRestrictions(array $data, \WP_Error $errors): void
    {
        foreach ($this->getTrialQuantityRestrictionErrors() as $code => $message) {
            $errors->add($code, $message);
        }

        $identity = $this->getCheckoutIdentity([
            'customer_id' => get_current_user_id(),
            'email'       => sanitize_email($data['billing_email'] ?? ''),
        ]);

        foreach ($this->getCustomerRestrictionErrors($identity) as $code => $message) {
            $errors->add($code, $message);
        }
    }

    /**
     * Validate Store API checkout restrictions for guests and email-matched returning customers.
     *
     * @param \WC_Order $order  Order object.
     * @param \WP_Error $errors Validation errors.
     * @return void
     */
    public function validateCheckoutCustomerRestrictionsStoreApi($order, \WP_Error $errors): void
    {
        foreach ($this->getTrialQuantityRestrictionErrors() as $code => $message) {
            $errors->add($code, $message);
        }

        $identity = $this->getCheckoutIdentity([
            'customer_id' => (int) $order->get_customer_id(),
            'email'       => sanitize_email($order->get_billing_email()),
        ]);

        foreach ($this->getCustomerRestrictionErrors($identity) as $code => $message) {
            $errors->add($code, $message);
        }
    }

    /**
     * Validate cart composition rules when adding a subscription product.
     *
     * Checks mixed-cart, multiple-subscriptions, and different-billing-cycle restrictions.
     *
     * @param bool $passed   Current validation status.
     * @param int  $check_id Product or variation ID being added.
     * @return bool
     */
    private function validateCartComposition(bool $passed, int $check_id): bool
    {
        // One-click checkout products replace the cart, skip composition checks
        if (
            function_exists('arraysubs_is_one_click_checkout_product')
            && arraysubs_is_one_click_checkout_product($check_id)
        ) {
            return $passed;
        }

        if (!function_exists('WC') || !WC()->cart) {
            return $passed;
        }

        $cart_contents = WC()->cart->get_cart();

        if (empty($cart_contents)) {
            return $passed;
        }

        $has_subscription_in_cart = false;
        $has_regular_in_cart      = false;
        $cart_cycles              = [];
        $cart_subscription_ids    = [];

        foreach ($cart_contents as $cart_item) {
            $item_product_id = $cart_item['product_id'];
            $item_variation  = $cart_item['variation_id'] ?? 0;
            $item_check_id   = $item_variation > 0 ? $item_variation : $item_product_id;

            if (arraysubs_is_subscription_product($item_check_id)) {
                $has_subscription_in_cart = true;
                $cart_subscription_ids[$item_check_id] = true;
                if (function_exists('arraysubs_get_product_subscription_data')) {
                    $sub_data = arraysubs_get_product_subscription_data($item_check_id);
                    if ($sub_data) {
                        $cart_cycles[$sub_data['period'] . ':' . $sub_data['interval']] = true;
                    }
                }
            } else {
                $has_regular_in_cart = true;
            }
        }

        // Mixed cart: subscription being added while cart has regular items
        if ($has_regular_in_cart) {
            $allow_mixed_default = (bool) arraysubs_get_setting('multiple_subscriptions.allow_mixed_cart', true);
            $allow_mixed         = apply_filters('arraysubs_allow_mixed_cart', $allow_mixed_default);

            if (!$allow_mixed) {
                wc_add_notice(
                    $this->getMixedCartRestrictionMessage(),
                    'error'
                );
                return false;
            }
        }

        // Multiple subscriptions: another subscription being added while cart already has one
        if ($has_subscription_in_cart) {
            $allow_multiple_default = (bool) arraysubs_get_setting('multiple_subscriptions.allow_multiple_in_cart', true);
            $allow_multiple         = apply_filters('arraysubs_allow_multiple_subscriptions', $allow_multiple_default);

            $cart_would_have_distinct_subscriptions = $cart_subscription_ids;
            $cart_would_have_distinct_subscriptions[$check_id] = true;

            if (!$allow_multiple && count($cart_would_have_distinct_subscriptions) > 1) {
                wc_add_notice(
                    $this->getMultipleSubscriptionsCartRestrictionMessage(),
                    'error'
                );
                return false;
            }

            // Different billing cycles
            if (!empty($cart_cycles) && function_exists('arraysubs_get_product_subscription_data')) {
                $new_sub_data = arraysubs_get_product_subscription_data($check_id);
                if ($new_sub_data) {
                    $new_cycle = $new_sub_data['period'] . ':' . $new_sub_data['interval'];
                    $cart_cycles[$new_cycle] = true;

                    if (count($cart_cycles) > 1) {
                        $allow_diff_default = (bool) arraysubs_get_setting('multiple_subscriptions.allow_different_cycles', true);
                        $allow_diff         = apply_filters('arraysubs_allow_different_billing_cycles', $allow_diff_default);

                        if (!$allow_diff) {
                            wc_add_notice($this->getDifferentBillingCyclesRestrictionMessage(), 'error');
                            return false;
                        }
                    }
                }
            }
        }

        return $passed;
    }

    /**
     * Enforce one subscription per product as a cart quantity rule.
     *
     * @param bool $passed   Current validation status.
     * @param int  $check_id Product or variation ID being added.
     * @param int  $quantity Quantity being added.
     * @return bool
     */
    private function validateSingleProductQuantity(bool $passed, int $check_id, int $quantity): bool
    {
        if (!function_exists('arraysubs_is_one_subscription_per_product') || !arraysubs_is_one_subscription_per_product()) {
            return $passed;
        }

        if (!function_exists('WC') || !WC()->cart) {
            return $passed;
        }

        $existing_quantity = 0;

        foreach (WC()->cart->get_cart() as $cart_item) {
            $item_check_id = $this->getCartItemProductId($cart_item);

            if ($item_check_id !== $check_id) {
                continue;
            }

            $existing_quantity += max(1, absint($cart_item['quantity'] ?? 1));
        }

        $quantity = max(1, $quantity);

        // First add of a product passes even with quantity > 1: the request
        // is clamped to 1 instead of rejected — classic checkout via the
        // woocommerce_add_to_cart_quantity filter, Store API via
        // enforceSingleProductQuantityOnAdd(). Rejecting here would kill the
        // add before WC_Cart::add_to_cart runs the clamp filter and leave a
        // fresh guest with an empty cart and no visible notice.
        if ($existing_quantity === 0 && $quantity > 1) {
            return $passed;
        }

        if (($existing_quantity + $quantity) <= 1) {
            return $passed;
        }

        wc_add_notice($this->getSingleProductQuantityMessage(), 'error');

        return false;
    }

    /**
     * Clamp direct add-to-cart requests to a quantity of one when the per-product rule is enabled.
     *
     * @param mixed $quantity   Requested quantity.
     * @param int   $product_id Product ID.
     * @return int
     */
    public function limitAddToCartSubscriptionQuantity($quantity, int $product_id): int
    {
        $quantity = absint($quantity);

        if (
            $quantity <= 1
            || !function_exists('arraysubs_is_one_subscription_per_product')
            || !arraysubs_is_one_subscription_per_product()
            || !function_exists('arraysubs_is_subscription_product')
            || !arraysubs_is_subscription_product($product_id)
        ) {
            return max(1, $quantity);
        }

        wc_add_notice($this->getSingleProductQuantityMessage(), 'error');

        return 1;
    }

    /**
     * Clamp a just-added subscription cart line to quantity 1 when the
     * one-per-product rule is enabled.
     *
     * Classic checkout clamps via the woocommerce_add_to_cart_quantity
     * filter, but WooCommerce's Store API add-item route never applies that
     * filter, so a single request with quantity > 1 would land as-is. This
     * runs on woocommerce_add_to_cart (both paths) as a backstop.
     *
     * @param string $cart_item_key  Cart item key.
     * @param int    $product_id     Product ID.
     * @param int    $quantity       Quantity added.
     * @param int    $variation_id   Variation ID.
     * @param mixed  $variation      Variation data.
     * @param mixed  $cart_item_data Cart item data.
     * @return void
     */
    public function enforceSingleProductQuantityOnAdd(string $cart_item_key, $product_id, $quantity, $variation_id = 0, $variation = [], $cart_item_data = []): void
    {
        $quantity = absint($quantity);

        if (
            $quantity <= 1
            || !function_exists('arraysubs_is_one_subscription_per_product')
            || !arraysubs_is_one_subscription_per_product()
            || !function_exists('WC') || !WC()->cart
        ) {
            return;
        }

        $check_id = absint($variation_id) > 0 ? absint($variation_id) : absint($product_id);

        if ($check_id <= 0 || !arraysubs_is_subscription_product($check_id)) {
            return;
        }

        WC()->cart->set_quantity($cart_item_key, 1, true);

        if (WC()->session) {
            WC()->session->set('arraysubs_single_product_quantity_error', true);
        }

        wc_add_notice($this->getSingleProductQuantityMessage(), 'error');
    }

    /**
     * Reset Store API quantity updates that exceed the one-per-product rule.
     *
     * @param string   $cart_item_key Cart item key.
     * @param int      $quantity      Requested quantity.
     * @param int      $old_quantity  Old quantity.
     * @param \WC_Cart $cart          Cart object.
     * @return void
     */
    public function enforceStoreApiSingleProductQuantity(string $cart_item_key, int $quantity, int $old_quantity, $cart): void
    {
        if (
            $quantity <= 1
            || !function_exists('arraysubs_is_one_subscription_per_product')
            || !arraysubs_is_one_subscription_per_product()
            || !$cart
            || !method_exists($cart, 'get_cart_item')
        ) {
            return;
        }

        $cart_item = $cart->get_cart_item($cart_item_key);
        if (empty($cart_item)) {
            return;
        }

        $check_id = $this->getCartItemProductId($cart_item);
        if ($check_id <= 0 || !arraysubs_is_subscription_product($check_id)) {
            return;
        }

        $cart->set_quantity($cart_item_key, 1, true);

        if (function_exists('WC') && WC()->session) {
            WC()->session->set('arraysubs_single_product_quantity_error', true);
        }
    }

    /**
     * Get the one-per-product cart validation message.
     *
     * @return string
     */
    private function getSingleProductQuantityMessage(): string
    {
        return __('One Subscription per Product is enabled. Each subscription product can appear only once per order, so reduce the quantity to 1 before continuing.', 'arraysubs');
    }

    /**
     * Get the cart validation message for mixed-cart enforcement.
     *
     * @return string
     */
    private function getMixedCartRestrictionMessage(): string
    {
        return __('Mixed carts are disabled for subscriptions. Remove either the subscription product or the regular product before continuing.', 'arraysubs');
    }

    /**
     * Get the cart validation message for multiple subscription plan enforcement.
     *
     * @return string
     */
    private function getMultipleSubscriptionsCartRestrictionMessage(): string
    {
        return __('Multiple subscription plans are disabled for one checkout. Keep only one subscription plan in the cart, then place a separate order for any other plan.', 'arraysubs');
    }

    /**
     * Get the cart validation message for one-subscription-per-customer enforcement.
     *
     * @return string
     */
    private function getOneSubscriptionPerCustomerCartMessage(): string
    {
        return __('One Subscription per Customer is enabled. Keep only one subscription plan in the cart; if the customer already has a live subscription, manage that subscription or switch to an allowed target plan.', 'arraysubs');
    }

    /**
     * Get the cart validation message for different billing cycle enforcement.
     *
     * @return string
     */
    private function getDifferentBillingCyclesRestrictionMessage(): string
    {
        return __('Different billing cycles are disabled for one checkout. Keep subscription plans with the same billing cycle in the cart, or place separate orders.', 'arraysubs');
    }

    /**
     * Enforce a single free-trial item in the cart when one-trial-per-customer is enabled.
     *
     * @param bool $passed   Current validation status.
     * @param int  $check_id Product or variation ID being added.
     * @param int  $quantity Quantity being added.
     * @return bool
     */
    private function validateSingleTrialQuantity(bool $passed, int $check_id, int $quantity): bool
    {
        if (!$this->exceedsAllowedTrialCartQuantity($check_id, $quantity)) {
            return $passed;
        }

        wc_add_notice($this->getTrialCartValidationMessage(), 'error');

        return false;
    }

    /**
     * Collect customer-specific restriction errors for the current cart.
     *
     * @param array $identity Resolved checkout identity.
     * @return array<string,string>
     */
    private function getCustomerRestrictionErrors(array $identity): array
    {
        if (!function_exists('WC') || !WC()->cart) {
            return [];
        }

        $summary = $this->getSubscriptionCartSummary(WC()->cart);
        if (($summary['subscription_item_count'] ?? 0) === 0) {
            return [];
        }

        if (!function_exists('arraysubs_is_one_subscription_per_customer') || !arraysubs_is_one_subscription_per_customer()) {
            return [];
        }

        $existing_subscriptions = arraysubs_get_customer_subscriptions_for_identity(
            $identity['customer_id'] ?? 0,
            $identity['email'] ?? '',
            $this->getRestrictedSubscriptionStatuses()
        );

        if (empty($existing_subscriptions)) {
            return [];
        }

        if (!function_exists('arraysubs_can_auto_migrate_subscription_on_checkout') || !arraysubs_can_auto_migrate_subscription_on_checkout()) {
            return [
                'arraysubs_one_per_customer' => __('One Subscription per Customer is enabled and this checkout email already has a live subscription. Manage the existing subscription first, or enable checkout migration and choose an allowed switch target.', 'arraysubs'),
            ];
        }

        if (($summary['subscription_item_count'] ?? 0) !== 1 || ($summary['distinct_subscription_count'] ?? 0) !== 1) {
            return [
                'arraysubs_checkout_migration_cart' => __('Checkout migration is enabled, but it can replace only one existing subscription with one target subscription product at a time. Keep a single subscription plan in the cart, then try again.', 'arraysubs'),
            ];
        }

        $target_product_id = (int) ($summary['first_subscription_product_id'] ?? 0);
        $quantity = (int) ($summary['quantities'][$target_product_id] ?? 1);
        $migration_context = $this->getCheckoutMigrationContext($target_product_id, $quantity, $identity);

        return $migration_context['eligible']
            ? []
            : ['arraysubs_checkout_migration' => $migration_context['message']];
    }

    /**
     * Collect checkout restriction errors for trial-quantity abuse.
     *
     * @return array<string,string>
     */
    private function getTrialQuantityRestrictionErrors(): array
    {
        if (!function_exists('WC') || !WC()->cart) {
            return [];
        }

        if (!function_exists('arraysubs_is_one_trial_per_customer') || !arraysubs_is_one_trial_per_customer()) {
            return [];
        }

        $summary = $this->getSubscriptionCartSummary(WC()->cart);
        $trial_quantity = array_sum($summary['trial_quantities'] ?? []);

        if ($trial_quantity <= 1) {
            return [];
        }

        return [
            'arraysubs_trial_quantity' => $this->getTrialCartValidationMessage(),
        ];
    }
}
