<?php
/**
 * Aysa Link Purchase Manager
 * 
 * Handles link purchase requests and automation
 */

if (!defined('ABSPATH')) {
    exit;
}

class Aysa_Link_Purchase_Manager {
    
    private $table_name;
    private $api_base_url;
    private $current_request_project_id;

    private function resolve_bound_project_id($requested_project_id = 0) {
        $bound_project_id = intval(get_option('aysa_project_id', 0));
        $requested_project_id = intval($requested_project_id);
        if ($bound_project_id <= 0 || ($requested_project_id > 0 && $requested_project_id !== $bound_project_id)) {
            return 0;
        }
        return $bound_project_id;
    }

    private function resolve_api_base_url() {
        if (class_exists('Aysa_API_Config') && method_exists('Aysa_API_Config', 'get_api_base_url')) {
            return rtrim((string) Aysa_API_Config::get_api_base_url(), '/') . '/';
        }

        if (defined('AYSA_API_BASE_URL') && trim((string) AYSA_API_BASE_URL) !== '') {
            return rtrim((string) AYSA_API_BASE_URL, '/') . '/';
        }

        return 'https://api.aysa.ai/';
    }
    
    /**
     * Constructor
     */
    public function __construct() {
        global $wpdb;
        $this->table_name = esc_sql($wpdb->prefix . 'aysa_link_purchases');
        $this->api_base_url = $this->resolve_api_base_url();
        
        add_action('init', array($this, 'schedule_cron_jobs'));
        add_action('aysa_process_link_purchases', array($this, 'process_link_purchases'));
        add_action('wp_ajax_aysa_save_link_purchase_request', array($this, 'handle_save_link_purchase_request'));
        add_action('wp_ajax_aysa_get_link_purchase_status', array($this, 'handle_get_link_purchase_status'));
        add_action('wp_ajax_aysa_reset_link_purchase_errors', array($this, 'handle_reset_errors'));
        add_action('wp_ajax_aysa_fix_task_status', array($this, 'handle_fix_task_status'));
        add_action('wp_ajax_aysa_test_cron_payload', array($this, 'handle_test_cron_payload'));

        
        register_activation_hook(AYSA_PLUGIN_FILE, array($this, 'on_plugin_activation'));
        register_deactivation_hook(AYSA_PLUGIN_FILE, array($this, 'clear_scheduled_hooks'));
    }
    
    /**
     * Get default excluded URL patterns for AYSA global exclusions
     */
    private function get_default_excluded_url_patterns() {
        return array(
            // WooCommerce system pages
            '/cart/',
            '/checkout/',
            '/my-account/',
            '/wishlist/',
            // WordPress internal archives
            '/tag/',
            '/author/',
            '/date/',
            '/\d{4}/\d{2}/', // date archives like /2025/04/
            // Search parameters and internal queries
            '/?s=',
            '/search/',
            '/?add-to-cart=',
            '/?wc-ajax=',
            '/?orderby=',
            '/?filter_',
            // Feeds and technical pages
            '/feed/',
            '/comments/feed/',
            '/rss/',
            '/atom/',
            // WordPress admin and system
            '/wp-admin/',
            '/wp-content/',
            '/wp-includes/',
            '/wp-json/',
            // Common technical patterns
            '.xml',
            '.json',
            '.css',
            '.js',
            '.ico'
        );
    }
    
    /**
     * Check if URL matches any excluded patterns
     */
    private function is_url_excluded($url) {
        // Use centralized global exclusions
        if (class_exists('Aysa_Global_Exclusions')) {
            return Aysa_Global_Exclusions::is_url_excluded($url);
        }
        
        // Fallback to local patterns if global class not available
        $excluded_patterns = $this->get_default_excluded_url_patterns();
        
        if (empty($excluded_patterns)) {
            return false;
        }
        
        // Parse URL to get path and query
        $parsed_url = wp_parse_url($url);
        $path = $parsed_url['path'] ?? '';
        $query = $parsed_url['query'] ?? '';
        $full_url = $path . ($query ? '?' . $query : '');
        
        foreach ($excluded_patterns as $pattern) {
            $pattern = trim($pattern);
            if (empty($pattern)) continue;
            
            // Handle simple string patterns
            if (strpos($full_url, $pattern) !== false) {
                aysa_debug_log("[AYSA LINK PURCHASE] URL '$url' excluded by pattern: $pattern");
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Create the link purchases table
     */
    public function create_table() {
        global $wpdb;
        
        $charset_collate = $this->get_safe_charset_collate();
        
        $sql = "CREATE TABLE {$this->table_name} (
            id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
            project_id bigint(20) unsigned NOT NULL,
            task_id bigint(20) unsigned NOT NULL,
            task_title varchar(255) NOT NULL,
            links_requested int(11) NOT NULL DEFAULT 0,
            links_purchased int(11) NOT NULL DEFAULT 0,
            target_keyword varchar(255) DEFAULT 'Domain Authority',
            status varchar(20) DEFAULT 'pending',
            order_data longtext NULL,
            last_purchase_attempt datetime NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY project_id (project_id),
            KEY task_id (task_id),
            KEY status (status),
            KEY created_at (created_at)
        ) $charset_collate;";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql);
        
        aysa_debug_log('[AYSA LINK PURCHASE] Table created successfully');
    }
    
    /**
     * Ensure table has correct schema
     */
    private function ensure_table_schema() {
        global $wpdb;
        
        // Check if table exists
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$this->table_name}'");
        
        if (!$table_exists) {
            aysa_debug_log('[AYSA LINK PURCHASE] Table does not exist, creating...');
            $this->create_table();
            return;
        }
        
        // Check if required columns exist
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $columns = $wpdb->get_col("DESCRIBE {$this->table_name}");
        $required_columns = ['task_title', 'links_purchased', 'links_requested'];
        
        $missing_columns = array_diff($required_columns, $columns);
        
        if (!empty($missing_columns)) {
            aysa_debug_log('[AYSA LINK PURCHASE] Missing columns: ' . implode(', ', $missing_columns) . '. Recreating table...');
            $this->create_table();
        } else {
            aysa_debug_log('[AYSA LINK PURCHASE] Table schema is correct');
        }
    }
    
    /**
     * Get safe charset collate
     */
    private function get_safe_charset_collate() {
        global $wpdb;
        $charset_collate = $wpdb->get_charset_collate();
        
        if (strpos($charset_collate, 'UTF-8') !== false) {
            $charset_collate = str_replace('UTF-8', 'utf8mb4', $charset_collate);
        }
        
        if (empty($charset_collate) || strpos($charset_collate, 'UTF-8') !== false) {
            $charset_collate = 'DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci';
        }
        
        return $charset_collate;
    }
    
    /**
     * Schedule cron jobs
     */
    public function schedule_cron_jobs() {
        if (!wp_next_scheduled('aysa_process_link_purchases')) {
            // Schedule to run every 5 minutes for more frequent processing
            wp_schedule_event(time(), 'aysa_five_minutes', 'aysa_process_link_purchases');
        }
        
        // Add custom interval if not exists
        add_filter('cron_schedules', array($this, 'add_custom_cron_intervals'));
    }
    
    /**
     * Add custom cron intervals
     */
    public function add_custom_cron_intervals($schedules) {
        $schedules['aysa_five_minutes'] = array(
            'interval' => 300, // 5 minutes
            'display' => __('Every 5 Minutes', 'aysaaiseo')
        );
        return $schedules;
    }
    
    /**
     * Clear scheduled hooks
     */
    public function clear_scheduled_hooks() {
        wp_clear_scheduled_hook('aysa_process_link_purchases');
    }
    
    /**
     * Plugin activation handler
     */
    public function on_plugin_activation() {
        // Create table
        $this->create_table();
        
        // Ensure cron is scheduled
        $this->ensure_cron_scheduled();
        
        aysa_debug_log('[AYSA LINK PURCHASE] Plugin activated, table created and cron scheduled');
    }
    
    /**
     * Ensure cron job is scheduled (called on activation and when needed)
     */
    public function ensure_cron_scheduled() {
        if (!wp_next_scheduled('aysa_process_link_purchases')) {
            aysa_debug_log('[AYSA LINK PURCHASE] Scheduling cron job');
            wp_schedule_event(time(), 'aysa_five_minutes', 'aysa_process_link_purchases');
        }
        
        // Ensure custom interval is registered
        add_filter('cron_schedules', array($this, 'add_custom_cron_intervals'));
    }
    
    /**
     * Save link purchase request
     */
    public function handle_save_link_purchase_request() {
        aysa_debug_log('[AYSA LINK PURCHASE] AJAX request started');
        
        // Check nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if ($nonce === '' || !wp_verify_nonce($nonce, 'aysa_link_purchase_nonce')) {
            aysa_debug_log('[AYSA LINK PURCHASE] Nonce verification failed');
            wp_send_json_error('Security check failed');
        }
        
        if (!current_user_can('manage_options')) {
            aysa_debug_log('[AYSA LINK PURCHASE] Insufficient permissions');
            wp_send_json_error('Insufficient permissions');
        }
        
        // Ensure cron job is scheduled when saving request
        $this->ensure_cron_scheduled();
        
        // Ensure table exists with correct schema
        $this->ensure_table_schema();
        
        $task_id = isset($_POST['task_id']) ? intval((string) wp_unslash($_POST['task_id'])) : 0;
        $project_id = $this->resolve_bound_project_id(isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0);
        $links_requested = isset($_POST['links_requested']) ? intval((string) wp_unslash($_POST['links_requested'])) : 0;
        $task_title = isset($_POST['task_title']) ? sanitize_text_field((string) wp_unslash($_POST['task_title'])) : '';
        
        aysa_debug_log('[AYSA LINK PURCHASE] Parameters: task_id=' . $task_id . ', project_id=' . $project_id . ', links_requested=' . $links_requested . ', task_title=' . $task_title);
        
        if (!$task_id || !$project_id || $links_requested <= 0) {
            aysa_debug_log('[AYSA LINK PURCHASE] Invalid parameters');
            wp_send_json_error('Invalid request parameters');
        }
        
        global $wpdb;
        
        // Check if request already exists
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing = $wpdb->get_row($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT * FROM {$this->table_name} WHERE task_id = %d AND project_id = %d AND status != 'completed'",
            $task_id, $project_id
        ));
        
        if ($existing) {
            aysa_debug_log('[AYSA LINK PURCHASE] Updating existing request');
            // Update existing request
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $result = $wpdb->update(
                $this->table_name,
                array(
                    'links_requested' => $links_requested,
                    'status' => 'pending',
                    'updated_at' => current_time('mysql')
                ),
                array('id' => $existing->id),
                array('%d', '%s', '%s'),
                array('%d')
            );
        } else {
            aysa_debug_log('[AYSA LINK PURCHASE] Creating new request');
            // Create new request
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $result = $wpdb->insert(
                $this->table_name,
                array(
                    'project_id' => $project_id,
                    'task_id' => $task_id,
                    'task_title' => $task_title,
                    'links_requested' => $links_requested,
                    'links_purchased' => 0, // Explicitly set to 0
                    'status' => 'pending'
                ),
                array('%d', '%d', '%s', '%d', '%d', '%s')
            );
        }
        
        if ($result !== false) {
            aysa_debug_log('[AYSA LINK PURCHASE] Request saved successfully');
        } else {
            aysa_debug_log('[AYSA LINK PURCHASE] Database error: ' . $wpdb->last_error);
        }
        
        if ($result !== false) {
            wp_send_json_success(array(
                'message' => 'Link purchase request saved successfully',
                'links_requested' => $links_requested
            ));
        } else {
            wp_send_json_error('Failed to save link purchase request');
        }
    }
    
    /**
     * Get link purchase status for a task
     */
    public function handle_get_link_purchase_status() {
        check_ajax_referer('aysa_link_purchase_nonce', 'nonce');
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        
        // Ensure cron job is scheduled when checking status
        $this->ensure_cron_scheduled();
        
        $task_id = isset($_GET['task_id']) ? intval(wp_unslash($_GET['task_id'])) : 0;
        $project_id = $this->resolve_bound_project_id(isset($_GET['project_id']) ? intval((string) wp_unslash($_GET['project_id'])) : 0);
        
        if (!$task_id || !$project_id) {
            wp_send_json_error('Invalid parameters');
        }
        
        global $wpdb;
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $purchase_request = $wpdb->get_row($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT * FROM {$this->table_name} WHERE task_id = %d AND project_id = %d ORDER BY created_at DESC LIMIT 1",
            $task_id, $project_id
        ));
        
        if ($purchase_request) {
            $response_data = array(
                'links_requested' => $purchase_request->links_requested,
                'links_purchased' => $purchase_request->links_purchased,
                'status' => $purchase_request->status,
                'last_purchase_attempt' => $purchase_request->last_purchase_attempt
            );
            
            // Include detailed error information if status is error
            if ($purchase_request->status === 'error' && !empty($purchase_request->order_data)) {
                $order_data = json_decode($purchase_request->order_data, true);
                $response_data['error_details'] = $order_data;
                
                // Also include the original request data for debugging
                $request_data = $this->prepare_order_data($purchase_request, 1);
                $response_data['request_details'] = $request_data;
            }
            
            wp_send_json_success($response_data);
        } else {
            wp_send_json_success(array(
                'links_requested' => 0,
                'links_purchased' => 0,
                'status' => 'none'
            ));
        }
    }
    
    /**
     * Process link purchases via cron
     */
    public function process_link_purchases() {
        aysa_debug_log('[AYSA LINK PURCHASE] Starting link purchase processing');

        $project_id = $this->resolve_bound_project_id(0);
        if ($project_id <= 0) {
            return;
        }
        
        // Ensure table schema is correct
        $this->ensure_table_schema();
        
        global $wpdb;
        
        // Get pending purchase requests (exclude error status to prevent infinite retry of failed requests)
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
        $sql = $wpdb->prepare("SELECT * FROM {$this->table_name}
             WHERE status IN ('pending', 'processing') 
             AND links_purchased < links_requested
             AND project_id = %d
             ORDER BY created_at ASC 
             LIMIT 5", $project_id);
        
        aysa_debug_log("[AYSA LINK PURCHASE] Query: {$sql}");
        
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query: table identifiers and structure are internal, and request-derived values are prepared before execution. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $pending_requests = $wpdb->get_results($sql);
        
        aysa_debug_log("[AYSA LINK PURCHASE] Found " . count($pending_requests) . " pending requests");
        
        if (empty($pending_requests)) {
            aysa_debug_log('[AYSA LINK PURCHASE] No pending requests found');
            return;
        }
        
        foreach ($pending_requests as $request) {
            $this->process_single_purchase($request);
        }
    }
    
    /**
     * Process a single link purchase
     */
    private function process_single_purchase($request) {
        aysa_debug_log("[AYSA LINK PURCHASE] Processing request ID: {$request->id}");
        
        try {
            // Calculate how many links we still need to purchase
            $remaining_links = $request->links_requested - $request->links_purchased;
            aysa_debug_log("[AYSA LINK PURCHASE] Remaining links to purchase: {$remaining_links}");
            
            if ($remaining_links <= 0) {
                aysa_debug_log("[AYSA LINK PURCHASE] No more links needed, marking as completed");
                $this->update_request_status($request->id, 'completed');
                
                // Update ResearchTask to 100% when completed
                $this->update_research_task_percent($request, $request->links_requested);
                return;
            }
            
            // Purchase one link at a time
            $links_to_buy = 1;
            aysa_debug_log("[AYSA LINK PURCHASE] Preparing order data for {$links_to_buy} link(s)");
            
            $order_data = $this->prepare_order_data($request, $links_to_buy);
            aysa_debug_log("[AYSA LINK PURCHASE] Order data prepared: " . json_encode($order_data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
            
            aysa_debug_log("[AYSA LINK PURCHASE] Sending order to API...");
            $response = $this->send_order_to_api($order_data);
            aysa_debug_log("[AYSA LINK PURCHASE] API call completed, processing response...");
            
            // Check for various success indicators in API response
            $is_success = false;
            $success_message = '';
            
            if ($response) {
                aysa_debug_log("[AYSA LINK PURCHASE] Analyzing API response: " . json_encode($response));
                
                // Check multiple success indicators
                if (isset($response['success']) && $response['success']) {
                    $is_success = true;
                    $success_message = 'API returned success=true';
                } elseif (isset($response['status']) && in_array(strtolower($response['status']), ['success', 'completed', 'processed', 'ok'])) {
                    $is_success = true;
                    $success_message = 'API returned success status: ' . $response['status'];
                } elseif (isset($response['orderId']) || isset($response['order_id']) || isset($response['id'])) {
                    $is_success = true;
                    $success_message = 'API returned order ID, assuming success';
                } elseif (is_array($response) && !isset($response['error']) && !isset($response['failed']) && !empty($response)) {
                    // If no error indicators and we got a structured response, assume success
                    $is_success = true;
                    $success_message = 'API returned structured response without error indicators';
                }
                
                aysa_debug_log("[AYSA LINK PURCHASE] Success check result: " . ($is_success ? 'SUCCESS' : 'FAILED') . " - {$success_message}");
            } else {
                aysa_debug_log("[AYSA LINK PURCHASE] No response from API or empty response");
            }
            
            if ($is_success) {
                // Update the purchased count
                global $wpdb;
                $new_purchased_count = $request->links_purchased + $links_to_buy;
                
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $result = $wpdb->update(
                    $this->table_name,
                    array(
                        'links_purchased' => $new_purchased_count,
                        'last_purchase_attempt' => current_time('mysql'),
                        'status' => ($new_purchased_count >= $request->links_requested) ? 'completed' : 'processing',
                        'order_data' => json_encode($response)
                    ),
                    array('id' => $request->id),
                    array('%d', '%s', '%s', '%s'),
                    array('%d')
                );
                
                if ($result === false) {
                    aysa_debug_log("[AYSA LINK PURCHASE] Database update failed: " . $wpdb->last_error);
                } else {
                aysa_debug_log("[AYSA LINK PURCHASE] Successfully purchased link. Progress: {$new_purchased_count}/{$request->links_requested}");
                    
                    // Update the percent field in ResearchTasks table
                    $this->update_research_task_percent($request, $new_purchased_count);
                }
            } else {
                // Log detailed error information
                aysa_debug_log("[AYSA LINK PURCHASE] Purchase failed for request ID: {$request->id}");
                
                if (is_array($response)) {
                    $error_msg = isset($response['error']) ? $response['error'] : 'API response did not indicate success';
                    aysa_debug_log("[AYSA LINK PURCHASE] API response analysis - Error: {$error_msg}");
                    
                    // For timeout errors, set status to 'pending' to retry later
                    if (isset($response['error']) && $response['error'] === 'timeout') {
                        aysa_debug_log("[AYSA LINK PURCHASE] API timeout - keeping request as pending for retry");
                        $this->update_request_status($request->id, 'pending', $response);
                    } elseif (isset($response['error']) && strpos(strtolower($response['error']), 'timeout') !== false) {
                        aysa_debug_log("[AYSA LINK PURCHASE] Timeout detected in error message - keeping as pending");
                        $this->update_request_status($request->id, 'pending', $response);
                    } else {
                        aysa_debug_log("[AYSA LINK PURCHASE] Setting status to error due to: {$error_msg}");
                        $this->update_request_status($request->id, 'error', $response);
                        
                        // Even if this link failed, update progress based on what was purchased so far
                        // This shows accurate progress: e.g., 1/2 purchased, 1/2 failed
                        $this->update_research_task_percent($request, $request->links_purchased);
                    }
                } elseif ($response === null || $response === false) {
                    aysa_debug_log("[AYSA LINK PURCHASE] Null/false response - likely API connectivity issue - keeping as pending");
                    $error_data = array('error' => 'No response from API', 'details' => 'API call returned null or false');
                    $this->update_request_status($request->id, 'pending', $error_data);
                } else {
                    aysa_debug_log("[AYSA LINK PURCHASE] Invalid response format - Response type: " . gettype($response) . " - Content: " . aysa_debug_export($response, true));
                    $error_data = array('error' => 'Invalid response format', 'response_type' => gettype($response), 'content' => $response);
                    $this->update_request_status($request->id, 'error', $error_data);
                    
                    // Update progress based on what was purchased so far
                    $this->update_research_task_percent($request, $request->links_purchased);
                }
            }
            
        } catch (Exception $e) {
            aysa_debug_log("[AYSA LINK PURCHASE] Error processing request ID {$request->id}: " . $e->getMessage());
            $error_data = array('error' => 'Exception occurred', 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString());
            $this->update_request_status($request->id, 'error', $error_data);
            
            // Update progress based on what was purchased so far
            $this->update_research_task_percent($request, $request->links_purchased);
        }
    }
    
    /**
     * Prepare order data for API
     */
    private function prepare_order_data($request, $links_to_buy) {
        // Get project settings
        $project_settings = $this->get_project_seo_settings($request->project_id);
        
        // Store project_id in context for get_wordpress_urls to use
        $this->current_request_project_id = $request->project_id;
        
        // Get WordPress URLs based on associations for this project
        $wordpress_urls = $this->get_wordpress_urls();
        
        return array(
            'projectId' => (int)$request->project_id,
            'country' => $project_settings['country'] ?? 'Romania',
            'language' => $project_settings['language'] ?? 'ro',
            'minDa' => 20, // Default values
            'maxDa' => 80,
            'numberOfLinksToBuy' => $links_to_buy,
            'wordToLinks' => $wordpress_urls
        );
    }
    
    /**
     * Get WordPress URLs - returns ALL WordPress content URLs
     * Prioritizes comprehensiveness over association filtering 
     */
    private function get_wordpress_urls() {
        aysa_debug_log('[AYSA LINK PURCHASE] Getting ALL WordPress URLs (comprehensive approach)');
        
        try {
            // Always return ALL WordPress URLs regardless of associations
            // This ensures maximum coverage for link building opportunities
            return $this->get_all_wordpress_urls();
            
        } catch (Exception $e) {
            aysa_debug_log('[AYSA LINK PURCHASE] Error getting WordPress URLs: ' . $e->getMessage());
            // Return at least the homepage if everything fails
            return array(array('url' => home_url('/')));
        }
    }
    
    /**
     * Add category URLs that have associations
     */
    private function add_category_urls_with_associations(&$urls, $associations) {
        $categories = get_categories(array('hide_empty' => false));
        
        foreach ($categories as $category) {
            // Check if this category has an association
            $has_association = false;
            foreach ($associations as $association) {
                if (strtolower($association['AssociatedOn']) === 'category' && 
                    strtolower($association['Value']) === strtolower($category->name)) {
                    $has_association = true;
                    break;
                }
            }
            
            if ($has_association) {
                $category_url = get_category_link($category->term_id);
                if ($category_url && !$this->url_exists_in_array($urls, $category_url)) {
                    $urls[] = array('url' => $category_url);
                }
            }
        }
    }
    
    /**
     * Add page URLs that have associations
     */
    private function add_page_urls_with_associations(&$urls, $associations) {
        $pages = get_pages(array('post_status' => 'publish'));
        
        foreach ($pages as $page) {
            // Check if this page has an association
            $has_association = false;
            foreach ($associations as $association) {
                if ((strtolower($association['AssociatedOn']) === 'page' || strtolower($association['AssociatedOn']) === 'product') && 
                    (strtolower($association['Value']) === strtolower($page->post_title) || 
                     strtolower($association['Value']) === strtolower($page->post_name))) {
                    $has_association = true;
                    break;
                }
            }
            
            if ($has_association) {
                $page_url = get_permalink($page->ID);
                if ($page_url && !$this->url_exists_in_array($urls, $page_url)) {
                    $urls[] = array('url' => $page_url);
                }
            }
        }
    }
    
    /**
     * Add post URLs that have associations
     */
    private function add_post_urls_with_associations(&$urls, $associations) {
        $posts = get_posts(array(
            'post_status' => 'publish',
            'numberposts' => -1, // Get ALL posts, not limited
            'post_type' => 'post'
        ));
        
        foreach ($posts as $post) {
            // Check if this post has an association
            $has_association = false;
            foreach ($associations as $association) {
                if ((strtolower($association['AssociatedOn']) === 'post' || strtolower($association['AssociatedOn']) === 'product') && 
                    (strtolower($association['Value']) === strtolower($post->post_title) || 
                     strtolower($association['Value']) === strtolower($post->post_name))) {
                    $has_association = true;
                    break;
                }
            }
            
            if ($has_association) {
                $post_url = get_permalink($post->ID);
                if ($post_url && !$this->url_exists_in_array($urls, $post_url)) {
                    $urls[] = array('url' => $post_url);
                }
            }
        }
    }
    
    /**
     * Add product URLs that have associations (WooCommerce)
     */
    private function add_product_urls_with_associations(&$urls, $associations) {
        // Check if WooCommerce is active
        if (!function_exists('wc_get_products')) {
            return;
        }
        
        $products = wc_get_products(array(
            'status' => 'publish',
            'limit' => -1 // Get ALL products, not limited
        ));
        
        foreach ($products as $product) {
            // Check if this product has an association
            $has_association = false;
            foreach ($associations as $association) {
                if (strtolower($association['AssociatedOn']) === 'product' && 
                    (strtolower($association['Value']) === strtolower($product->get_name()) || 
                     strtolower($association['Value']) === strtolower($product->get_slug()))) {
                    $has_association = true;
                    break;
                }
            }
            
            if ($has_association) {
                $product_url = get_permalink($product->get_id());
                if ($product_url && !$this->url_exists_in_array($urls, $product_url)) {
                    $urls[] = array('url' => $product_url);
                }
            }
        }
    }
    
    /**
     * Enhanced: Get all WordPress URLs with associated keywords
     */
    private function get_all_wordpress_urls() {
        $urls = array();
        
        // Get all associations for this project to match with URLs
        $associations = $this->get_project_associations($this->current_request_project_id);
        
        aysa_debug_log('[AYSA LINK PURCHASE] Found ' . count($associations) . ' associations for keyword matching');
        
        // Homepage - only include if it has keywords
        $homepage_url = home_url('/');
        $homepage_keywords = $this->get_keywords_for_url($homepage_url, $associations, 'homepage', 0);
        if (!empty($homepage_keywords)) {
            $homepage_data = array('url' => $homepage_url);
            $homepage_data = array_merge($homepage_data, $homepage_keywords);
            $urls[] = $homepage_data;
            aysa_debug_log("[AYSA LINK PURCHASE] ✅ INCLUDED Homepage with keywords: " . json_encode($homepage_keywords));
        } else {
            aysa_debug_log("[AYSA LINK PURCHASE] ❌ EXCLUDED Homepage - no keywords found");
        }
        
        // Categories - only include categories that have keywords
        $categories = get_categories(array('hide_empty' => false));
        foreach ($categories as $category) {
            $category_url = get_category_link($category->term_id);
            if ($category_url) {
                $category_keywords = $this->get_keywords_for_url($category_url, $associations, 'Category', $category->term_id);
                if (!empty($category_keywords)) {
                    $category_data = array('url' => $category_url);
                    $category_data = array_merge($category_data, $category_keywords);
                    $urls[] = $category_data;
                    aysa_debug_log("[AYSA LINK PURCHASE] ✅ INCLUDED Category '{$category->name}' with keywords");
                } else {
                    aysa_debug_log("[AYSA LINK PURCHASE] ❌ EXCLUDED Category '{$category->name}' - no keywords found");
                }
            }
        }
        
        // Tags - only include tags that have keywords
        $tags = get_tags(array('hide_empty' => false));
        foreach ($tags as $tag) {
            $tag_url = get_tag_link($tag->term_id);
            if ($tag_url) {
                $tag_keywords = $this->get_keywords_for_url($tag_url, $associations, 'Tag', $tag->term_id);
                if (!empty($tag_keywords)) {
                    $tag_data = array('url' => $tag_url);
                    $tag_data = array_merge($tag_data, $tag_keywords);
                    $urls[] = $tag_data;
                    aysa_debug_log("[AYSA LINK PURCHASE] ✅ INCLUDED Tag '{$tag->name}' with keywords");
                } else {
                    aysa_debug_log("[AYSA LINK PURCHASE] ❌ EXCLUDED Tag '{$tag->name}' - no keywords found");
                }
            }
        }
        
        // Pages - only include pages that have keywords and are not excluded
        $pages = get_pages(array('post_status' => 'publish'));
        foreach ($pages as $page) {
            $page_url = get_permalink($page->ID);
            if ($page_url) {
                // Check if URL is excluded
                if ($this->is_url_excluded($page_url)) {
                    aysa_debug_log("[AYSA LINK PURCHASE] ❌ EXCLUDED Page '{$page->post_title}' - URL matches exclusion pattern");
                    continue;
                }
                
                $page_keywords = $this->get_keywords_for_url($page_url, $associations, 'Page', $page->ID);
                if (!empty($page_keywords)) {
                    $page_data = array('url' => $page_url);
                    $page_data = array_merge($page_data, $page_keywords);
                    $urls[] = $page_data;
                    aysa_debug_log("[AYSA LINK PURCHASE] ✅ INCLUDED Page '{$page->post_title}' with keywords");
                } else {
                    aysa_debug_log("[AYSA LINK PURCHASE] ❌ EXCLUDED Page '{$page->post_title}' - no keywords found");
                }
            }
        }
        
        // Posts - only include posts that have keywords and are not excluded
        $posts = get_posts(array('post_status' => 'publish', 'numberposts' => -1));
        foreach ($posts as $post) {
            $post_url = get_permalink($post->ID);
            if ($post_url) {
                // Check if URL is excluded
                if ($this->is_url_excluded($post_url)) {
                    aysa_debug_log("[AYSA LINK PURCHASE] ❌ EXCLUDED Post '{$post->post_title}' - URL matches exclusion pattern");
                    continue;
                }
                
                $post_keywords = $this->get_keywords_for_url($post_url, $associations, 'Post', $post->ID);
                if (!empty($post_keywords)) {
                    $post_data = array('url' => $post_url);
                    $post_data = array_merge($post_data, $post_keywords);
                    $urls[] = $post_data;
                    aysa_debug_log("[AYSA LINK PURCHASE] ✅ INCLUDED Post '{$post->post_title}' with keywords");
                } else {
                    aysa_debug_log("[AYSA LINK PURCHASE] ❌ EXCLUDED Post '{$post->post_title}' - no keywords found");
                }
            }
        }
        
        // Products (if WooCommerce) - only include products that have keywords and are not excluded
        if (function_exists('wc_get_products')) {
            $products = wc_get_products(array('status' => 'publish', 'limit' => -1));
            foreach ($products as $product) {
                $product_url = get_permalink($product->get_id());
                if ($product_url) {
                    // Check if URL is excluded
                    if ($this->is_url_excluded($product_url)) {
                        aysa_debug_log("[AYSA LINK PURCHASE] ❌ EXCLUDED Product '{$product->get_name()}' - URL matches exclusion pattern");
                        continue;
                    }
                    
                    $product_keywords = $this->get_keywords_for_url($product_url, $associations, 'Product', $product->get_id());
                    if (!empty($product_keywords)) {
                        $product_data = array('url' => $product_url);
                        $product_data = array_merge($product_data, $product_keywords);
                        $urls[] = $product_data;
                        aysa_debug_log("[AYSA LINK PURCHASE] ✅ INCLUDED Product '{$product->get_name()}' with keywords");
                    } else {
                        aysa_debug_log("[AYSA LINK PURCHASE] ❌ EXCLUDED Product '{$product->get_name()}' - no keywords found");
                    }
                }
            }
            
            // Product categories - only include product categories that have keywords
            $product_categories = get_terms(array(
                'taxonomy' => 'product_cat',
                'hide_empty' => false,
            ));
            foreach ($product_categories as $category) {
                $category_url = get_term_link($category);
                if ($category_url && !is_wp_error($category_url)) {
                    $category_keywords = $this->get_keywords_for_url($category_url, $associations, 'Category', $category->term_id);
                    if (!empty($category_keywords)) {
                        $category_data = array('url' => $category_url);
                        $category_data = array_merge($category_data, $category_keywords);
                        $urls[] = $category_data;
                        aysa_debug_log("[AYSA LINK PURCHASE] ✅ INCLUDED Product Category '{$category->name}' with keywords");
                    } else {
                        aysa_debug_log("[AYSA LINK PURCHASE] ❌ EXCLUDED Product Category '{$category->name}' - no keywords found");
                    }
                }
            }
            
            // Product tags - only include product tags that have keywords
            $product_tags = get_terms(array(
                'taxonomy' => 'product_tag',
                'hide_empty' => false,
            ));
            foreach ($product_tags as $tag) {
                $tag_url = get_term_link($tag);
                if ($tag_url && !is_wp_error($tag_url)) {
                    $tag_keywords = $this->get_keywords_for_url($tag_url, $associations, 'Tag', $tag->term_id);
                    if (!empty($tag_keywords)) {
                        $tag_data = array('url' => $tag_url);
                        $tag_data = array_merge($tag_data, $tag_keywords);
                        $urls[] = $tag_data;
                        aysa_debug_log("[AYSA LINK PURCHASE] ✅ INCLUDED Product Tag '{$tag->name}' with keywords");
                    } else {
                        aysa_debug_log("[AYSA LINK PURCHASE] ❌ EXCLUDED Product Tag '{$tag->name}' - no keywords found");
                    }
                }
            }
        }
        
        // Custom post types - only include custom posts that have keywords
        $custom_post_types = get_post_types(array('public' => true, '_builtin' => false), 'names');
        foreach ($custom_post_types as $post_type) {
            // Skip product post type as we already handled it above
            if ($post_type === 'product') {
                continue;
            }
            
            $custom_posts = get_posts(array(
                'post_type' => $post_type,
                'post_status' => 'publish',
                'numberposts' => -1
            ));
            
            foreach ($custom_posts as $custom_post) {
                $custom_post_url = get_permalink($custom_post->ID);
                if ($custom_post_url) {
                    $custom_post_keywords = $this->get_keywords_for_url($custom_post_url, $associations, ucfirst($post_type), $custom_post->ID);
                    if (!empty($custom_post_keywords)) {
                        $custom_post_data = array('url' => $custom_post_url);
                        $custom_post_data = array_merge($custom_post_data, $custom_post_keywords);
                        $urls[] = $custom_post_data;
                        aysa_debug_log("[AYSA LINK PURCHASE] ✅ INCLUDED {$post_type} '{$custom_post->post_title}' with keywords");
                    } else {
                        aysa_debug_log("[AYSA LINK PURCHASE] ❌ EXCLUDED {$post_type} '{$custom_post->post_title}' - no keywords found");
                    }
                }
            }
        }
        
        // Summary logging
        $urls_with_main = 0;
        $urls_with_secondary = 0;
        $urls_with_both = 0;
        
        foreach ($urls as $url_data) {
            $has_main = !empty($url_data['mainKeywords']);
            $has_secondary = !empty($url_data['secondaryKeywords']);
            
            if ($has_main) $urls_with_main++;
            if ($has_secondary) $urls_with_secondary++;
            if ($has_main && $has_secondary) $urls_with_both++;
        }
        
        aysa_debug_log('[AYSA LINK PURCHASE] 📊 FILTERING SUMMARY:');
        aysa_debug_log('[AYSA LINK PURCHASE] ✅ Total URLs with keywords: ' . count($urls));
        aysa_debug_log('[AYSA LINK PURCHASE] 🎯 URLs with main keywords: ' . $urls_with_main);
        aysa_debug_log('[AYSA LINK PURCHASE] 🔄 URLs with secondary keywords: ' . $urls_with_secondary);
        aysa_debug_log('[AYSA LINK PURCHASE] 💎 URLs with both main & secondary: ' . $urls_with_both);
        aysa_debug_log('[AYSA LINK PURCHASE] 🚀 Ready for link building with targeted keywords!');
        
        return $urls;
    }
    
    
    /**
     * Get keywords associated with a specific URL
     */
    private function get_keywords_for_url($url, $associations, $entity_type, $entity_id) {
        $main_keywords = array();
        $secondary_keywords = array();
        
        // Clean URL for comparison
        $url_clean = rtrim($url, '/');
        
        foreach ($associations as $association) {
            // Check if this association matches our entity
            $matches = false;
            
            // Method 1: Match by entity type and ID
            if (!empty($association['idIntern']) && !empty($association['associatedOn'])) {
                $assoc_type = strtolower($association['associatedOn']);
                $assoc_id = $association['idIntern'];
                
                // Handle different entity type variations
                if (($entity_type === 'Post' && $assoc_type === 'post') ||
                    ($entity_type === 'Page' && $assoc_type === 'page') ||
                    ($entity_type === 'Product' && $assoc_type === 'product') ||
                    ($entity_type === 'Category' && ($assoc_type === 'category' || $assoc_type === 'product_cat')) ||
                    ($entity_type === 'Tag' && ($assoc_type === 'tag' || $assoc_type === 'product_tag'))) {
                    
                    if ($assoc_id == $entity_id) {
                        $matches = true;
                    }
                }
            }
            
            // Method 2: Match by URL if we have a link in the association
            if (!$matches && !empty($association['link'])) {
                $assoc_url_clean = rtrim($association['link'], '/');
                if ($url_clean === $assoc_url_clean) {
                    $matches = true;
                }
            }
            
            if ($matches && !empty($association['keyword'])) {
                // Determine if this is a main keyword
                $is_main = isset($association['isMain']) && $association['isMain'] == 1;
                
                if ($is_main) {
                    $main_keywords[] = $association['keyword'];
                } else {
                    $secondary_keywords[] = $association['keyword'];
                }
                
                // Also check for secondary keyword field
                if (!empty($association['secondKeyword'])) {
                    $secondary_keywords[] = $association['secondKeyword'];
                }
            }
        }
        
        // Build the result array
        $result = array();
        
        if (!empty($main_keywords)) {
            // Remove duplicates and clean up
            $main_keywords = array_unique(array_filter($main_keywords));
            $result['mainKeywords'] = array_values($main_keywords);
        }
        
        if (!empty($secondary_keywords)) {
            // Remove duplicates and clean up
            $secondary_keywords = array_unique(array_filter($secondary_keywords));
            $result['secondaryKeywords'] = array_values($secondary_keywords);
        }
        
        if (!empty($result)) {
            aysa_debug_log("[AYSA LINK PURCHASE] Found keywords for URL {$url}: " . json_encode($result));
        }
        
        return $result;
    }
    
    /**
     * Check if URL already exists in array
     */
    private function url_exists_in_array($urls, $url) {
        foreach ($urls as $url_data) {
            if ($url_data['url'] === $url) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Get current project ID from various sources
     */
    private function get_current_project_id() {
        // First check if we have it from current request context
        if ($this->current_request_project_id) {
            return intval($this->current_request_project_id);
        }
        
        // Try to get from cookies
        if (isset($_COOKIE['project_id'])) {
            return intval($_COOKIE['project_id']);
        }
        
        // Try to get from WordPress options
        $project_id = get_option('aysa_current_project_id', 0);
        if ($project_id) {
            return intval($project_id);
        }
        
        // Try to get from latest request in database
        global $wpdb;
        $table_name = esc_sql($wpdb->prefix . 'aysa_link_purchases');
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $latest_request = $wpdb->get_row("SELECT project_id FROM {$table_name} ORDER BY created_at DESC LIMIT 1");
        
        if ($latest_request && $latest_request->project_id) {
            return intval($latest_request->project_id);
        }
        
        return 0;
    }
    
    /**
     * Get associations for project from API (Enhanced for keywords)
     */
    private function get_project_associations($project_id) {
        try {
            // Try AYSA Auth Manager first (more reliable and comprehensive)
            if (class_exists('Aysa_Auth_Manager')) {
                $auth_manager = Aysa_Auth_Manager::get_instance();
                $auth_status = $auth_manager->is_authenticated();
                
                if ($auth_status['authenticated']) {
                    $api_url = 'api/Association?pageSize=1000&pageNumber=1&sortOrder=asc&projectId=' . $project_id;
                    $associations = $auth_manager->make_authenticated_request($api_url);
                    
                    if ($associations && is_array($associations)) {
                        aysa_debug_log('[AYSA LINK PURCHASE] Retrieved ' . count($associations) . ' associations via Auth Manager for project ' . $project_id);
                        return $associations;
                    }
                }
            }
            
            // Fallback to direct API call if Auth Manager fails
            $api_url = $this->api_base_url . "api/associations/project/{$project_id}";
            
            $response = wp_remote_get($api_url, array(
                'timeout' => 10,
                'headers' => array(
                    'Content-Type' => 'application/json',
                )
            ));
            
            if (is_wp_error($response)) {
                aysa_debug_log('[AYSA LINK PURCHASE] API error getting associations: ' . $response->get_error_message());
                return array();
            }
            
            $body = wp_remote_retrieve_body($response);
            $data = json_decode($body, true);
            
            if (!$data || !is_array($data)) {
                aysa_debug_log('[AYSA LINK PURCHASE] Invalid associations response');
                return array();
            }
            
            aysa_debug_log('[AYSA LINK PURCHASE] Retrieved ' . count($data) . ' associations via direct API for project ' . $project_id);
            return $data;
            
        } catch (Exception $e) {
            aysa_debug_log('[AYSA LINK PURCHASE] Error fetching associations: ' . $e->getMessage());
            return array();
        }
    }
    
    /**
     * Send order to API
     */
    private function send_order_to_api($order_data) {
        $url = $this->api_base_url . 'api/order/process-order';
        aysa_debug_log("[AYSA LINK PURCHASE] Making API request to: {$url}");
        aysa_debug_log("[AYSA LINK PURCHASE] Request payload: " . json_encode($order_data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
        
        // Test API connectivity first with a quick HEAD request
        $test_response = wp_remote_head($url, array('timeout' => 5));
        if (is_wp_error($test_response)) {
            aysa_debug_log("[AYSA LINK PURCHASE] API connectivity test failed: " . $test_response->get_error_message());
            return array('success' => false, 'error' => 'API not reachable: ' . $test_response->get_error_message());
        }
        
        $response = wp_remote_post($url, array(
            'headers' => array(
                'Content-Type' => 'application/json',
            ),
            'body' => json_encode($order_data, JSON_UNESCAPED_SLASHES),
            'timeout' => 30
        ));
        
        aysa_debug_log("[AYSA LINK PURCHASE] wp_remote_post completed");
        
        if (is_wp_error($response)) {
            $error_msg = $response->get_error_message();
            aysa_debug_log('[AYSA LINK PURCHASE] API Error: ' . $error_msg);
            
            // For timeout errors, return a specific error structure
            if (strpos($error_msg, 'timeout') !== false || strpos($error_msg, 'timed out') !== false) {
                return array('success' => false, 'error' => 'timeout', 'message' => $error_msg);
            }
            
            return array('success' => false, 'error' => $error_msg);
        }
        
        $response_code = wp_remote_retrieve_response_code($response);
        aysa_debug_log("[AYSA LINK PURCHASE] Response code: {$response_code}");
        
        $body = wp_remote_retrieve_body($response);
        aysa_debug_log('[AYSA LINK PURCHASE] Raw API Response: ' . $body);
        
        // Handle non-200 response codes
        if ($response_code < 200 || $response_code >= 300) {
            aysa_debug_log("[AYSA LINK PURCHASE] API returned non-success status: {$response_code}");
            return array('success' => false, 'error' => "API returned status {$response_code}", 'response' => $body);
        }
        
        $data = json_decode($body, true);
        
        if (json_last_error() !== JSON_ERROR_NONE) {
            aysa_debug_log('[AYSA LINK PURCHASE] JSON decode error: ' . json_last_error_msg());
            return array('success' => false, 'error' => 'Invalid JSON response', 'raw_response' => $body);
        }
        
        aysa_debug_log('[AYSA LINK PURCHASE] Parsed API Response: ' . json_encode($data));
        
        return $data;
    }
    
    /**
     * Get project SEO settings
     */
    private function get_project_seo_settings($project_id) {
        // Try to get from WordPress options first
        $settings = get_option('aysa_seo_settings', array());
        
        if (!empty($settings)) {
            return $settings;
        }
        
        // Default fallback
        return array(
            'country' => 'Romania',
            'language' => 'ro'
        );
    }
    
    /**
     * Update request status
     */
    private function update_request_status($request_id, $status, $order_data = null) {
        global $wpdb;
        
        $update_data = array(
            'status' => $status,
            'last_purchase_attempt' => current_time('mysql')
        );
        
        $format = array('%s', '%s');
        
        // Include order_data if provided (for error details)
        if ($order_data !== null) {
            $update_data['order_data'] = json_encode($order_data);
            $format[] = '%s';
        }
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $wpdb->update(
            $this->table_name,
            $update_data,
            array('id' => $request_id),
            $format,
            array('%d')
        );
    }
    
    /**
     * Update the percent field in ResearchTasks table via API
     */
    private function update_research_task_percent($request, $purchased_count) {
        try {
            $percent = round(($purchased_count / $request->links_requested) * 100);
            
            aysa_debug_log("[AYSA LINK PURCHASE] ====== PROGRESS UPDATE START ======");
            aysa_debug_log("[AYSA LINK PURCHASE] Task ID: {$request->task_id}");
            aysa_debug_log("[AYSA LINK PURCHASE] Purchased: {$purchased_count}");
            aysa_debug_log("[AYSA LINK PURCHASE] Requested: {$request->links_requested}");
            aysa_debug_log("[AYSA LINK PURCHASE] Formula: ({$purchased_count} / {$request->links_requested}) * 100 = {$percent}%");
            aysa_debug_log("[AYSA LINK PURCHASE] Calculated percent: {$percent}%");
            
            $api_url = rtrim($this->api_base_url, '/') . '/api/ResearchTasks/' . $request->task_id . '/percent';
            aysa_debug_log("[AYSA LINK PURCHASE] API URL: {$api_url}");
            
            $update_data = array(
                'Percent' => $percent
            );
            aysa_debug_log("[AYSA LINK PURCHASE] Update data: " . json_encode($update_data));
            
            $response = wp_remote_request($api_url, array(
                'method' => 'PATCH',
                'headers' => array(
                    'Content-Type' => 'application/json',
                ),
                'body' => json_encode($update_data),
                'timeout' => 30
            ));
            
            if (is_wp_error($response)) {
                aysa_debug_log("[AYSA LINK PURCHASE] API request failed: " . $response->get_error_message());
                aysa_debug_log("[AYSA LINK PURCHASE] ====== PROGRESS UPDATE FAILED ======");
                return false;
            }
            
            $response_code = wp_remote_retrieve_response_code($response);
            $response_body = wp_remote_retrieve_body($response);
            
            aysa_debug_log("[AYSA LINK PURCHASE] Response code: {$response_code}");
            aysa_debug_log("[AYSA LINK PURCHASE] Response body: {$response_body}");
            
            if ($response_code >= 200 && $response_code < 300) {
                aysa_debug_log("[AYSA LINK PURCHASE] ✅ Successfully updated ResearchTask percent to {$percent}%");
                
                // If task is at 100% (completed), also update status to 'done'
                if ($percent >= 100) {
                    aysa_debug_log("[AYSA LINK PURCHASE] 🎯 TASK REACHED 100% - CALLING STATUS UPDATE TO 'done'");
                    $status_update_result = $this->update_research_task_status(
                        $request->task_id, 
                        'done', 
                        $request->task_title,
                        'Analyze domain authority averages for competitors'
                    );
                    if ($status_update_result) {
                        aysa_debug_log("[AYSA LINK PURCHASE] ✅ STATUS UPDATE TO 'done' SUCCESSFUL");
                    } else {
                        aysa_debug_log("[AYSA LINK PURCHASE] ❌ STATUS UPDATE TO 'done' FAILED");
                    }
                }
                
                aysa_debug_log("[AYSA LINK PURCHASE] ====== PROGRESS UPDATE SUCCESS ======");
                return true;
            } else {
                aysa_debug_log("[AYSA LINK PURCHASE] ❌ API update failed with status {$response_code}");
                aysa_debug_log("[AYSA LINK PURCHASE] ====== PROGRESS UPDATE FAILED ======");
                return false;
            }
            
        } catch (Exception $e) {
            aysa_debug_log("[AYSA LINK PURCHASE] Exception in progress update: " . $e->getMessage());
            aysa_debug_log("[AYSA LINK PURCHASE] ====== PROGRESS UPDATE EXCEPTION ======");
            return false;
        }
    }
    
    /**
     * Update the status field in ResearchTasks table via API
     */
    private function update_research_task_status($task_id, $status, $task_title = null, $task_description = null) {
        try {
            aysa_debug_log("[AYSA LINK PURCHASE] ====== STATUS UPDATE START ======");
            aysa_debug_log("[AYSA LINK PURCHASE] Updating ResearchTask {$task_id} status to '{$status}'");
            
            $api_url = rtrim($this->api_base_url, '/') . '/api/ResearchTasks/' . $task_id;
            
            // Simple approach like percent update - send only what we need (capital letters for DTO)
            $update_data = array(
                'Status' => $status
            );
            
            // Add title and description if provided
            if ($task_title) {
                $update_data['Title'] = $task_title;
            }
            if ($task_description) {
                $update_data['Description'] = $task_description;
            }
            
            aysa_debug_log("[AYSA LINK PURCHASE] Status update data: " . json_encode($update_data));
            
            $response = wp_remote_request($api_url, array(
                'method' => 'PUT',
                'headers' => array(
                    'Content-Type' => 'application/json',
                ),
                'body' => json_encode($update_data),
                'timeout' => 30
            ));
            
            if (is_wp_error($response)) {
                aysa_debug_log("[AYSA LINK PURCHASE] Status API request failed: " . $response->get_error_message());
                aysa_debug_log("[AYSA LINK PURCHASE] ====== STATUS UPDATE FAILED ======");
                return false;
            }
            
            $response_code = wp_remote_retrieve_response_code($response);
            $response_body = wp_remote_retrieve_body($response);
            
            aysa_debug_log("[AYSA LINK PURCHASE] Status response code: {$response_code}");
            aysa_debug_log("[AYSA LINK PURCHASE] Status response body: {$response_body}");
            
            if ($response_code >= 200 && $response_code < 300) {
                aysa_debug_log("[AYSA LINK PURCHASE] ✅ Successfully updated ResearchTask status to '{$status}'");
                aysa_debug_log("[AYSA LINK PURCHASE] ====== STATUS UPDATE SUCCESS ======");
                return true;
            } else {
                aysa_debug_log("[AYSA LINK PURCHASE] ❌ Status API update failed with status {$response_code}");
                aysa_debug_log("[AYSA LINK PURCHASE] ====== STATUS UPDATE FAILED ======");
                return false;
            }
            
        } catch (Exception $e) {
            aysa_debug_log("[AYSA LINK PURCHASE] Exception in status update: " . $e->getMessage());
            aysa_debug_log("[AYSA LINK PURCHASE] ====== STATUS UPDATE EXCEPTION ======");
            return false;
        }
    }
    
    /**
     * Reset all error requests to pending status
     */
    public function handle_reset_errors() {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if ($nonce === '' || !wp_verify_nonce($nonce, 'aysa_link_purchase_nonce')) {
            wp_send_json_error('Security check failed');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->resolve_bound_project_id(0);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        
        global $wpdb;
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $updated = $wpdb->update(
            $this->table_name,
            array(
                'status' => 'pending',
                'last_purchase_attempt' => current_time('mysql')
            ),
            array('status' => 'error', 'project_id' => $project_id),
            array('%s', '%s'),
            array('%s', '%d')
        );
        
        aysa_debug_log("[AYSA LINK PURCHASE] Reset $updated error requests to pending status");
        
        wp_send_json_success(array(
            'message' => "Reset $updated requests from error to pending status",
            'updated_count' => $updated
        ));
    }
    
    /**
     * Fix task status for completed tasks (100% progress but still in_progress)
     */
    public function handle_fix_task_status() {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if ($nonce === '' || !wp_verify_nonce($nonce, 'aysa_link_purchase_nonce')) {
            wp_send_json_error('Security check failed');
            return;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $project_id = $this->resolve_bound_project_id(isset($_POST['project_id']) ? intval((string) wp_unslash($_POST['project_id'])) : 0);
        if ($project_id <= 0) {
            wp_send_json_error('Invalid project id');
            return;
        }
        
        $task_id = isset($_POST['task_id']) ? intval(wp_unslash($_POST['task_id'])) : 0;
        $task_title = sanitize_text_field(wp_unslash($_POST['task_title'] ?? 'Get 3 links with at least 60-70 authority'));
        $task_description = sanitize_text_field(wp_unslash($_POST['task_description'] ?? 'Analyze domain authority averages for competitors'));
        
        if (!$task_id) {
            wp_send_json_error('Invalid task ID');
        }
        
        aysa_debug_log("[AYSA LINK PURCHASE] ====== FIX TASK STATUS REQUESTED ======");
        aysa_debug_log("[AYSA LINK PURCHASE] Task ID: {$task_id}");
        aysa_debug_log("[AYSA LINK PURCHASE] Task Title: {$task_title}");
        aysa_debug_log("[AYSA LINK PURCHASE] Task Description: {$task_description}");
        
        // Direct status update without GET - just like we do for percent updates
        aysa_debug_log("[AYSA LINK PURCHASE] Making direct status update to 'done'");
        
        $result = $this->update_research_task_status($task_id, 'done', $task_title, $task_description);
        
        if ($result) {
            wp_send_json_success(array(
                'message' => 'Task status updated successfully',
                'task_id' => $task_id,
                'old_status' => 'unknown', // We don't know the old status, but that's ok
                'new_status' => 'done'
            ));
        } else {
            wp_send_json_error('Failed to update task status - check debug.log for details');
        }
    }
    
    /**
     * Handle AJAX test for cron payload
     */
    public function handle_test_cron_payload() {
        // Check nonce
        $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        if ($nonce === '' || !wp_verify_nonce($nonce, 'aysa_link_purchase_nonce')) {
            wp_send_json_error('Security check failed');
        }
        
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        if (!defined('AYSA_ENABLE_LEGACY_LINK_PURCHASE_MANAGER') || AYSA_ENABLE_LEGACY_LINK_PURCHASE_MANAGER !== true) {
            wp_send_json_error('Legacy QA controls are unavailable.', 404);
            return;
        }
        
        try {
            $task_id = intval(wp_unslash($_POST['task_id'] ?? 0));
            $project_id = $this->resolve_bound_project_id(intval(wp_unslash($_POST['project_id'] ?? 0)));
            if ($project_id <= 0) {
                wp_send_json_error('Invalid project id');
                return;
            }
            
            aysa_debug_log('[AYSA TEST] Test cron payload requested - Task ID: ' . $task_id . ', Project ID: ' . $project_id);
            
            // Create a mock request object for testing
            $mock_request = (object) array(
                'id' => 999,
                'project_id' => $project_id,
                'task_id' => $task_id,
                'task_title' => 'Test Link Purchase',
                'links_requested' => 1,
                'links_purchased' => 0,
                'status' => 'pending'
            );
            
            // Set project ID in context
            $this->current_request_project_id = $project_id;
            
            // Generate the payload that would be sent to API
            $payload = $this->prepare_order_data($mock_request, 1);
            
            // Get URL count for display
            $url_count = count($payload['wordToLinks'] ?? []);
            
            aysa_debug_log('[AYSA TEST] Generated payload: ' . json_encode($payload, JSON_UNESCAPED_SLASHES));
            
            // Optionally trigger actual cron for testing (uncomment if needed)
            // $this->process_single_purchase($mock_request);
            
            wp_send_json_success(array(
                'payload' => $payload,
                'url_count' => $url_count,
                'cron_triggered' => false, // Set to true if you want to actually trigger cron
                'api_response' => 'Test mode - no actual API call made'
            ));
            
        } catch (Exception $e) {
            aysa_debug_log('[AYSA TEST] Error in test: ' . $e->getMessage());
            wp_send_json_error('Test failed: ' . $e->getMessage());
        }
    }
    


}

// Legacy flow only: the Adverlink marketplace is the supported production path.
// Keep this implementation available only for explicit local QA so it cannot
// become a parallel production order executor through a stale database option.
$aysa_legacy_link_host = strtolower((string) wp_parse_url(home_url('/'), PHP_URL_HOST));
$aysa_legacy_link_is_local = in_array($aysa_legacy_link_host, array('localhost', '127.0.0.1', '::1'), true)
    || substr($aysa_legacy_link_host, -6) === '.local'
    || substr($aysa_legacy_link_host, -5) === '.test';
if (
    defined('AYSA_ENABLE_LEGACY_LINK_PURCHASE_MANAGER')
    && AYSA_ENABLE_LEGACY_LINK_PURCHASE_MANAGER === true
    && $aysa_legacy_link_is_local
) {
    new Aysa_Link_Purchase_Manager();
} elseif (defined('WP_DEBUG') && WP_DEBUG && function_exists('aysa_debug_log')) {
    aysa_debug_log('AYSA: Legacy link purchase manager disabled; Adverlink marketplace is authoritative.');
}
