<?php
/**
 * Cart tracking for Maple Carts.
 *
 * @package MapleCarts
 */

defined( 'ABSPATH' ) || exit;

/**
 * Tracking class.
 */
class Maple_Carts_Tracking {

    /**
     * Single instance.
     *
     * @var Maple_Carts_Tracking
     */
    private static $instance = null;

    /**
     * Get instance.
     *
     * @return Maple_Carts_Tracking
     */
    public static function instance() {
        if ( is_null( self::$instance ) ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor.
     */
    private function __construct() {
        if ( 'yes' !== Maple_Carts::get_option( 'enabled', 'yes' ) ) {
            return;
        }

        // Frontend tracking.
        add_action( 'woocommerce_after_checkout_form', [ $this, 'tracking_script' ] );
        add_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_after', [ $this, 'tracking_script' ] );

        // AJAX handlers.
        add_action( 'wp_ajax_maple_carts_save', [ $this, 'ajax_save_cart' ] );
        add_action( 'wp_ajax_nopriv_maple_carts_save', [ $this, 'ajax_save_cart' ] );

        // Cart recovery.
        add_action( 'wp', [ $this, 'handle_recovery' ] );
        add_action( 'wp', [ $this, 'handle_unsubscribe' ] );

        // WooCommerce hooks.
        add_action( 'woocommerce_new_order', [ $this, 'handle_new_order' ] );
        add_action( 'woocommerce_thankyou', [ $this, 'handle_order_complete' ] );
        add_action( 'woocommerce_order_status_changed', [ $this, 'handle_order_status_change' ], 10, 3 );

        // Mark carts as abandoned via cron.
        add_action( 'maple_carts_send_emails', [ $this, 'mark_abandoned_carts' ], 5 );
        
        // Cleanup cron.
        add_action( 'maple_carts_cleanup', [ 'Maple_Carts_DB', 'cleanup_old_data' ] );
    }

    /**
     * Enqueue tracking script on checkout.
     */
    public function tracking_script() {
        if ( $this->should_skip_tracking() ) {
            return;
        }

        wp_enqueue_script(
            'maple-carts-tracking',
            MAPLE_CARTS_URL . 'assets/js/tracking.js',
            [ 'jquery' ],
            MAPLE_CARTS_VERSION,
            true
        );

        $session_id = $this->get_or_create_session_id();

        wp_localize_script(
            'maple-carts-tracking',
            'mapleCartsData',
            [
                'ajaxUrl'    => admin_url( 'admin-ajax.php' ),
                'nonce'      => wp_create_nonce( 'maple_carts_nonce' ),
                'sessionId'  => $session_id,
                'gdprNotice' => Maple_Carts::get_option( 'gdpr_notice', '' ),
            ]
        );
    }

    /**
     * Check if tracking should be skipped.
     *
     * @return bool
     */
    private function should_skip_tracking() {
        // Skip if disabled via cookie.
        if ( isset( $_COOKIE['maple_carts_skip'] ) ) {
            return true;
        }

        // Skip for excluded roles.
        if ( is_user_logged_in() ) {
            $user          = wp_get_current_user();
            $exclude_roles = Maple_Carts::get_option( 'exclude_roles', [] );
            
            if ( ! empty( $exclude_roles ) && array_intersect( $user->roles, $exclude_roles ) ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get or create session ID.
     *
     * @return string
     */
    private function get_or_create_session_id() {
        if ( WC()->session ) {
            $session_id = WC()->session->get( 'maple_carts_session_id' );
            
            if ( ! $session_id ) {
                $session_id = wp_generate_uuid4();
                WC()->session->set( 'maple_carts_session_id', $session_id );
            }
            
            return $session_id;
        }

        // Fallback to cookie when WC session is unavailable.
        if ( isset( $_COOKIE['maple_carts_session'] ) ) {
            return sanitize_text_field( wp_unslash( $_COOKIE['maple_carts_session'] ) );
        }

        // Generate new session ID and persist to cookie.
        $session_id = wp_generate_uuid4();
        $this->set_session_cookie( $session_id );
        
        return $session_id;
    }

    /**
     * Set session cookie for fallback persistence.
     *
     * @param string $session_id Session ID.
     */
    private function set_session_cookie( $session_id ) {
        if ( headers_sent() ) {
            return;
        }

        $secure   = is_ssl();
        $httponly = true;
        $samesite = 'Lax';
        $expire   = time() + YEAR_IN_SECONDS;

        if ( PHP_VERSION_ID >= 70300 ) {
            setcookie( 'maple_carts_session', $session_id, [
                'expires'  => $expire,
                'path'     => '/',
                'domain'   => '',
                'secure'   => $secure,
                'httponly' => $httponly,
                'samesite' => $samesite,
            ] );
        } else {
            setcookie( 'maple_carts_session', $session_id, $expire, '/; SameSite=' . $samesite, '', $secure, $httponly );
        }

        // Also set in $_COOKIE for immediate availability.
        $_COOKIE['maple_carts_session'] = $session_id;
    }

    /**
     * AJAX handler for saving cart data.
     * Includes rate limiting for security.
     */
    public function ajax_save_cart() {
        check_ajax_referer( 'maple_carts_nonce', 'nonce' );

        // Rate limiting: max 10 requests per minute per IP.
        $ip = $this->get_client_ip();
        $rate_key = 'maple_carts_rate_' . md5( $ip );
        $requests = (int) get_transient( $rate_key );
        
        if ( $requests >= 10 ) {
            wp_send_json_error( 'Rate limit exceeded. Please try again later.' );
        }
        
        set_transient( $rate_key, $requests + 1, MINUTE_IN_SECONDS );

        // Check for consent if required.
        if ( ! Maple_Carts_Privacy::has_consent() ) {
            wp_send_json_error( 'Consent not given' );
        }

        // Record consent.
        Maple_Carts_Privacy::record_consent();

        $session_id = isset( $_POST['session_id'] ) ? sanitize_text_field( wp_unslash( $_POST['session_id'] ) ) : '';
        $email      = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : '';

        if ( empty( $session_id ) ) {
            wp_send_json_error( 'Missing session ID' );
        }

        // Get cart data from WooCommerce.
        $cart = WC()->cart;
        if ( ! $cart || $cart->is_empty() ) {
            wp_send_json_error( 'Cart is empty' );
        }

        $cart_contents = [];
        foreach ( $cart->get_cart() as $key => $item ) {
            $product = $item['data'];
            $cart_contents[] = [
                'product_id'   => $item['product_id'],
                'variation_id' => $item['variation_id'] ?? 0,
                'quantity'     => $item['quantity'],
                'name'         => $product->get_name(),
                'price'        => $product->get_price(),
                'image'        => wp_get_attachment_image_url( $product->get_image_id(), 'thumbnail' ),
                'permalink'    => $product->get_permalink(),
            ];
        }

        // Collect billing data.
        $billing_data = [];
        $billing_fields = [
            'first_name', 'last_name', 'phone', 'address_1', 'address_2',
            'city', 'state', 'postcode', 'country'
        ];
        
        foreach ( $billing_fields as $field ) {
            $key = 'billing_' . $field;
            if ( isset( $_POST[ $key ] ) ) {
                $billing_data[ $field ] = sanitize_text_field( wp_unslash( $_POST[ $key ] ) );
            }
        }

        $first_name    = $billing_data['first_name'] ?? '';
        $last_name     = $billing_data['last_name'] ?? '';
        $customer_name = trim( "$first_name $last_name" );

        // Calculate items count.
        $items_count = 0;
        foreach ( $cart_contents as $item ) {
            $items_count += $item['quantity'];
        }

        // Prepare cart data.
        $data = [
            'session_id'     => $session_id,
            'email'          => $email,
            'cart_contents'  => maybe_serialize( $cart_contents ),
            'cart_total'     => $cart->get_cart_contents_total(),
            'items_count'    => $items_count,
            'currency'       => get_woocommerce_currency(),
            'customer_name'  => $customer_name ?: null,
            'customer_phone' => $billing_data['phone'] ?? null,
            'billing_data'   => maybe_serialize( $billing_data ),
            'ip_address'     => $this->get_client_ip(),
            'user_agent'     => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : null,
        ];

        // Generate recovery URL.
        $data['recovery_url'] = $this->generate_recovery_url( $session_id );

        $cart_id = Maple_Carts_DB::save_cart( $data );

        if ( $cart_id ) {
            wp_send_json_success( [ 'cart_id' => $cart_id ] );
        } else {
            wp_send_json_error( 'Failed to save cart' );
        }
    }

    /**
     * Get client IP address.
     *
     * @return string
     */
    private function get_client_ip() {
        $ip_keys = [ 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR' ];
        
        foreach ( $ip_keys as $key ) {
            if ( ! empty( $_SERVER[ $key ] ) ) {
                $ip = sanitize_text_field( wp_unslash( $_SERVER[ $key ] ) );
                // Handle comma-separated IPs.
                if ( strpos( $ip, ',' ) !== false ) {
                    $ip = trim( explode( ',', $ip )[0] );
                }
                if ( filter_var( $ip, FILTER_VALIDATE_IP ) ) {
                    return $ip;
                }
            }
        }
        
        return '0.0.0.0';
    }

    /**
     * Generate recovery URL with HMAC signature.
     *
     * @param string $session_id Session ID.
     * @param string $coupon     Optional coupon code.
     * @return string
     */
    public function generate_recovery_url( $session_id, $coupon = '' ) {
        $data = [
            'sid' => $session_id,
            'c'   => $coupon,
            't'   => time(),
        ];
        
        // Sign the token with HMAC.
        $payload   = wp_json_encode( $data );
        $signature = hash_hmac( 'sha256', $payload, $this->get_token_secret() );
        $token     = base64_encode( $payload . '|' . $signature );

        return add_query_arg( 'maple_recover', $token, wc_get_checkout_url() );
    }

    /**
     * Generate unsubscribe URL with HMAC signature.
     *
     * @param string $session_id Session ID.
     * @return string
     */
    public function generate_unsubscribe_url( $session_id ) {
        $data = [
            'sid' => $session_id,
            't'   => time(),
        ];
        
        $payload   = wp_json_encode( $data );
        $signature = hash_hmac( 'sha256', $payload, $this->get_token_secret() );
        $token     = base64_encode( $payload . '|' . $signature );

        return add_query_arg( 'maple_unsubscribe', $token, home_url() );
    }

    /**
     * Verify and decode a signed token.
     *
     * @param string $token      The token to verify.
     * @param int    $max_age    Maximum age in seconds (0 = no expiry).
     * @return array|false       Decoded data or false if invalid.
     */
    private function verify_token( $token, $max_age = 0 ) {
        $decoded = base64_decode( $token, true );
        if ( ! $decoded || strpos( $decoded, '|' ) === false ) {
            return false;
        }

        $parts = explode( '|', $decoded, 2 );
        if ( count( $parts ) !== 2 ) {
            return false;
        }

        list( $payload, $signature ) = $parts;
        
        // Verify HMAC signature (timing-safe comparison).
        $expected_signature = hash_hmac( 'sha256', $payload, $this->get_token_secret() );
        if ( ! hash_equals( $expected_signature, $signature ) ) {
            return false;
        }

        $data = json_decode( $payload, true );
        if ( ! $data ) {
            return false;
        }

        // Check token age if max_age is set.
        if ( $max_age > 0 && isset( $data['t'] ) ) {
            if ( ( time() - $data['t'] ) > $max_age ) {
                return false;
            }
        }

        return $data;
    }

    /**
     * Get secret key for token signing.
     *
     * @return string
     */
    private function get_token_secret() {
        // Use WordPress auth key + salt for signing.
        return wp_salt( 'auth' ) . 'maple_carts';
    }

    /**
     * Handle cart recovery.
     */
    public function handle_recovery() {
        if ( ! isset( $_GET['maple_recover'] ) ) {
            return;
        }

        $token = sanitize_text_field( wp_unslash( $_GET['maple_recover'] ) );
        
        // Verify signed token (valid for 30 days).
        $data = $this->verify_token( $token, 30 * DAY_IN_SECONDS );
        
        if ( ! $data || empty( $data['sid'] ) ) {
            wc_add_notice( __( 'This recovery link has expired or is invalid.', 'maple-carts' ), 'error' );
            wp_safe_redirect( wc_get_page_permalink( 'shop' ) );
            exit;
        }

        $session_id = sanitize_text_field( $data['sid'] );
        $cart       = Maple_Carts_DB::get_cart_by_session( $session_id );

        if ( ! $cart || ! in_array( $cart->status, [ 'abandoned', 'active' ], true ) ) {
            return;
        }

        // Restore cart contents.
        $cart_contents = maybe_unserialize( $cart->cart_contents );
        
        if ( ! empty( $cart_contents ) && is_array( $cart_contents ) ) {
            WC()->cart->empty_cart();

            foreach ( $cart_contents as $item ) {
                $product_id   = $item['product_id'];
                $quantity     = $item['quantity'];
                $variation_id = $item['variation_id'] ?? 0;

                // Check if product exists and is in stock.
                $product = wc_get_product( $variation_id ? $variation_id : $product_id );
                if ( $product && $product->is_in_stock() ) {
                    WC()->cart->add_to_cart( $product_id, $quantity, $variation_id );
                }
            }
        }

        // Pre-fill billing data.
        $billing_data = maybe_unserialize( $cart->billing_data );
        if ( ! empty( $billing_data ) ) {
            WC()->session->set( 'maple_carts_billing', $billing_data );
            add_filter( 'woocommerce_checkout_get_value', [ $this, 'prefill_checkout_fields' ], 10, 2 );
        }

        // Apply coupon if provided.
        $coupon = ! empty( $data['c'] ) ? sanitize_text_field( $data['c'] ) : '';
        if ( $coupon && ! WC()->cart->has_discount( $coupon ) ) {
            WC()->cart->apply_coupon( $coupon );
        }

        // Store session ID for tracking.
        WC()->session->set( 'maple_carts_session_id', $session_id );
        WC()->session->set( 'maple_carts_recovering', true );

        // Redirect to clean URL.
        wp_safe_redirect( wc_get_checkout_url() );
        exit;
    }

    /**
     * Pre-fill checkout fields.
     *
     * @param mixed  $value Default value.
     * @param string $input Field name.
     * @return mixed
     */
    public function prefill_checkout_fields( $value, $input ) {
        $billing_data = WC()->session->get( 'maple_carts_billing' );
        
        if ( ! $billing_data ) {
            return $value;
        }

        $field = str_replace( 'billing_', '', $input );
        
        if ( isset( $billing_data[ $field ] ) ) {
            return $billing_data[ $field ];
        }

        return $value;
    }

    /**
     * Handle unsubscribe.
     */
    public function handle_unsubscribe() {
        if ( ! isset( $_GET['maple_unsubscribe'] ) ) {
            return;
        }

        $token = sanitize_text_field( wp_unslash( $_GET['maple_unsubscribe'] ) );
        
        // Verify signed token (valid for 90 days).
        $data = $this->verify_token( $token, 90 * DAY_IN_SECONDS );

        if ( ! $data || empty( $data['sid'] ) ) {
            wc_add_notice( __( 'This unsubscribe link has expired or is invalid.', 'maple-carts' ), 'error' );
            wp_safe_redirect( wc_get_page_permalink( 'shop' ) );
            exit;
        }

        $session_id = sanitize_text_field( $data['sid'] );
        $cart       = Maple_Carts_DB::get_cart_by_session( $session_id );

        if ( $cart ) {
            global $wpdb;
            $wpdb->update(
                $wpdb->prefix . MAPLE_CARTS_TABLE,
                [ 'unsubscribed' => 1 ],
                [ 'id' => $cart->id ]
            );

            // Cancel pending emails.
            Maple_Carts_DB::cancel_pending_emails( $cart->id );

            // Set cookie to skip future tracking (with security flags).
            $secure   = is_ssl();
            $httponly = true;
            $samesite = 'Lax';
            
            if ( PHP_VERSION_ID >= 70300 ) {
                setcookie( 'maple_carts_skip', '1', [
                    'expires'  => time() + YEAR_IN_SECONDS,
                    'path'     => '/',
                    'secure'   => $secure,
                    'httponly' => $httponly,
                    'samesite' => $samesite,
                ] );
            } else {
                setcookie( 'maple_carts_skip', '1', time() + YEAR_IN_SECONDS, '/; SameSite=' . $samesite, '', $secure, $httponly );
            }

            wc_add_notice( __( 'You have been unsubscribed from cart reminder emails.', 'maple-carts' ), 'success' );
        }

        wp_safe_redirect( wc_get_page_permalink( 'shop' ) );
        exit;
    }

    /**
     * Handle new order creation.
     *
     * @param int $order_id Order ID.
     */
    public function handle_new_order( $order_id ) {
        $this->process_order_completion( $order_id );
    }

    /**
     * Handle order complete (thank you page).
     *
     * @param int $order_id Order ID.
     */
    public function handle_order_complete( $order_id ) {
        $this->process_order_completion( $order_id );
    }

    /**
     * Process order completion.
     *
     * @param int $order_id Order ID.
     */
    private function process_order_completion( $order_id ) {
        $order = wc_get_order( $order_id );
        if ( ! $order ) {
            return;
        }

        $email = $order->get_billing_email();
        if ( ! $email ) {
            return;
        }

        // Check session first.
        $session_id  = null;
        $is_recovery = false;
        if ( WC()->session ) {
            $session_id  = WC()->session->get( 'maple_carts_session_id' );
            $is_recovery = WC()->session->get( 'maple_carts_recovering' );
        }

        $cart = null;
        if ( $session_id ) {
            $cart = Maple_Carts_DB::get_cart_by_session( $session_id );
        }
        
        if ( ! $cart ) {
            $cart = Maple_Carts_DB::get_cart_by_email( $email );
        }

        if ( ! $cart ) {
            return;
        }

        // Determine new status and recovery source.
        $new_status      = 'converted';
        $recovery_source = null;

        if ( in_array( $cart->status, [ 'abandoned' ], true ) ) {
            $new_status = 'recovered';
            
            // Determine recovery source from UTM parameters.
            if ( $is_recovery ) {
                // Came via recovery link - check UTM source for attribution.
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended
                $utm_source = isset( $_GET['utm_source'] ) ? sanitize_text_field( wp_unslash( $_GET['utm_source'] ) ) : '';
                
                switch ( $utm_source ) {
                    case 'mailjet':
                        $recovery_source = 'mailjet';
                        break;
                    case 'maple_carts':
                        $recovery_source = 'builtin_email';
                        break;
                    default:
                        // Generic recovery link (e.g., shared, bookmarked).
                        $recovery_source = 'email_link';
                        break;
                }
            } else {
                // Returned directly without clicking recovery link.
                $recovery_source = 'direct';
            }

            // Notify admin of recovery.
            if ( 'yes' === Maple_Carts::get_option( 'notify_admin', 'yes' ) ) {
                $this->send_admin_recovery_notification( $order, $cart );
            }
        }

        Maple_Carts_DB::update_cart_status( $cart->id, $new_status, [ 'recovery_source' => $recovery_source ] );
        Maple_Carts_DB::cancel_pending_emails( $cart->id );

        // Clear session data.
        if ( WC()->session ) {
            WC()->session->__unset( 'maple_carts_session_id' );
            WC()->session->__unset( 'maple_carts_recovering' );
            WC()->session->__unset( 'maple_carts_billing' );
        }
    }

    /**
     * Handle order status change.
     *
     * @param int    $order_id   Order ID.
     * @param string $old_status Old status.
     * @param string $new_status New status.
     */
    public function handle_order_status_change( $order_id, $old_status, $new_status ) {
        // Handle completed/processing orders.
        $completed_statuses = [ 'completed', 'processing' ];
        if ( in_array( $new_status, $completed_statuses, true ) ) {
            $this->process_order_completion( $order_id );
        }
    }

    /**
     * Mark carts as abandoned.
     * Processes in batches to prevent memory issues.
     */
    public function mark_abandoned_carts() {
        global $wpdb;

        // Prevent overlapping cron runs.
        if ( get_transient( 'maple_carts_marking_abandoned' ) ) {
            return;
        }
        set_transient( 'maple_carts_marking_abandoned', 1, 300 ); // 5 minute lock.

        $table       = $wpdb->prefix . MAPLE_CARTS_TABLE;
        $cutoff_time = (int) Maple_Carts::get_option( 'cart_cutoff_time', 15 );
        $cutoff      = gmdate( 'Y-m-d H:i:s', strtotime( "-{$cutoff_time} minutes" ) );
        $batch_size  = 100; // Process 100 at a time.

        // Get active carts that should be marked as abandoned (limited batch).
        // Use COALESCE to handle existing carts that don't have last_tracked_at set yet.
        $carts = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT id, email, billing_data FROM {$table} 
                 WHERE status = 'active' 
                   AND email IS NOT NULL 
                   AND email != ''
                   AND COALESCE(last_tracked_at, updated_at) < %s
                 LIMIT %d",
                $cutoff,
                $batch_size
            )
        );

        foreach ( $carts as $cart ) {
            // Determine abandonment type.
            $abandonment_type = $this->detect_abandonment_type( $cart );
            
            Maple_Carts_DB::update_cart_status( $cart->id, 'abandoned', [ 
                'abandonment_type' => $abandonment_type,
            ] );
            Maple_Carts_DB::schedule_emails( $cart->id );
        }

        delete_transient( 'maple_carts_marking_abandoned' );
    }

    /**
     * Detect abandonment type based on cart data.
     *
     * @param object $cart Cart object (minimal data from query).
     * @return string Abandonment type: browsing, checkout, or payment.
     */
    private function detect_abandonment_type( $cart ) {
        // No email = browsing abandonment (but we don't track these, so this shouldn't happen).
        if ( empty( $cart->email ) ) {
            return 'browsing';
        }

        // Check billing data for payment indicators.
        $billing_data = maybe_unserialize( $cart->billing_data );
        
        // If we have address data, they got further into checkout.
        $has_address = ! empty( $billing_data['address_1'] ) || 
                       ! empty( $billing_data['city'] ) || 
                       ! empty( $billing_data['postcode'] );

        if ( $has_address ) {
            // They filled out billing = likely payment abandonment.
            return 'payment';
        }

        // Just email = checkout abandonment.
        return 'checkout';
    }

    /**
     * Send admin recovery notification.
     *
     * @param WC_Order $order Order object.
     * @param object   $cart  Cart object.
     */
    private function send_admin_recovery_notification( $order, $cart ) {
        $admin_email = Maple_Carts::get_option( 'admin_email', get_option( 'admin_email' ) );
        $site_name   = get_bloginfo( 'name' );

        $subject = sprintf(
            /* translators: %1$s: Site name, %2$d: Order ID */
            __( '[%1$s] Recovered Order #%2$d', 'maple-carts' ),
            $site_name,
            $order->get_id()
        );

        $message = sprintf(
            /* translators: %1$d: Order ID, %2$s: Cart total, %3$s: Order link */
            __( "Great news! An abandoned cart has been recovered.\n\nOrder #%1\$d\nRecovered Value: %2\$s\n\nView order: %3\$s", 'maple-carts' ),
            $order->get_id(),
            wc_price( $order->get_total() ),
            admin_url( 'post.php?post=' . $order->get_id() . '&action=edit' )
        );

        wp_mail( $admin_email, $subject, $message );
    }
}
