<?php

namespace AIPT\Api\AutomationJobs;

use AIPT\Api\AutomationJobs\Interfaces\JobExecutorInterface;
use AIPT\Api\AutomationJobs\Interfaces\JobSchedulerInterface;
use AIPT\Api\AutomationJobs\Interfaces\JobLoggerInterface;
use AIPT\Api\AutomationJobs\Models\AutomationJob;
use AIPT\Api\Constants\AutomationConstants;
use AIPT\Api\Traits\RateLimitTrait;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

/**
 * Automation Jobs REST Controller
 *
 * Handles REST API endpoints for automation jobs
 * Following WordPress REST API conventions and existing plugin patterns
 *
 * @since 2.3.0
 */
class AutomationJobsController {
	use RateLimitTrait;

    /**
     * REST API namespace
     * @var string
     */
    private $namespace = 'aipt/v1';

    /**
     * REST API base route
     * @var string
     */
    private $rest_base = 'automation-jobs';

    /**
     * Job manager instance
     * @var AutomationJobManager
     */
    private $job_manager;

    /**
     * Job executor instance
     * @var JobExecutorInterface
     */
    private $job_executor;

    /**
     * Job scheduler instance
     * @var JobSchedulerInterface
     */
    private $job_scheduler;

    /**
     * Logger instance
     * @var JobLoggerInterface
     */
    private $logger;

    /**
     * Constructor
     *
     * @param AutomationJobManager $job_manager Job manager instance
     * @param JobExecutorInterface $job_executor Job executor instance
     * @param JobSchedulerInterface $job_scheduler Job scheduler instance
     * @param JobLoggerInterface $logger Logger instance
     */
    public function __construct(
        AutomationJobManager $job_manager,
        JobExecutorInterface $job_executor,
        JobSchedulerInterface $job_scheduler,
        JobLoggerInterface $logger
    ) {
        $this->job_manager = $job_manager;
        $this->job_executor = $job_executor;
        $this->job_scheduler = $job_scheduler;
        $this->logger = $logger;
    }

    /**
     * Register REST API routes
     */
    public function register_routes() {

        register_rest_route($this->namespace, '/' . $this->rest_base, [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'create_job'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => $this->get_create_job_args()
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base, [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_jobs'],
            'permission_callback' => [$this, 'check_permissions'],
            'args' => $this->get_list_jobs_args()
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>\d+)', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_job'],
            'permission_callback' => [$this, 'check_permissions'],
            'args' => [
                'id' => [
                    'required' => true,
                    'type' => 'integer',
                    'sanitize_callback' => 'absint'
                ]
            ]
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>\d+)', [
            'methods' => WP_REST_Server::EDITABLE,
            'callback' => [$this, 'update_job'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => $this->get_update_job_args()
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>\d+)', [
            'methods' => WP_REST_Server::DELETABLE,
            'callback' => [$this, 'delete_job'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'id' => [
                    'required' => true,
                    'type' => 'integer',
                    'sanitize_callback' => 'absint'
                ]
            ]
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>\d+)/pause', [
            'methods' => WP_REST_Server::EDITABLE,
            'callback' => [$this, 'pause_job'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'id' => [
                    'required' => true,
                    'type' => 'integer',
                    'sanitize_callback' => 'absint'
                ]
            ]
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>\d+)/resume', [
            'methods' => WP_REST_Server::EDITABLE,
            'callback' => [$this, 'resume_job'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'id' => [
                    'required' => true,
                    'type' => 'integer',
                    'sanitize_callback' => 'absint'
                ]
            ]
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>\d+)/stop', [
            'methods' => WP_REST_Server::EDITABLE,
            'callback' => [$this, 'stop_job'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'id' => [
                    'required' => true,
                    'type' => 'integer',
                    'sanitize_callback' => 'absint'
                ]
            ]
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>\d+)/logs', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_job_logs'],
            'permission_callback' => [$this, 'check_permissions'],
            'args' => $this->get_job_logs_args()
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/filter-options', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_filter_options'],
            'permission_callback' => [$this, 'check_permissions']
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/test', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'test_endpoint'],
            'permission_callback' => [$this, 'check_permissions']
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/estimate-products', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'estimate_products'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'filters' => [
                    'type' => 'object',
                    'required' => false,
                    'default' => [],
                ],
                'use_saved_settings' => [
                    'type' => 'boolean',
                    'default' => false,
                    'sanitize_callback' => 'rest_sanitize_boolean',
                ],
            ],
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>\d+)/trigger', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'trigger_job'],
            'permission_callback' => [$this, 'check_permissions_write'],
            'args' => [
                'force' => [
                    'type' => 'boolean',
                    'default' => false,
                    'required' => false,
                    'sanitize_callback' => 'rest_sanitize_boolean',
                ],
            ],
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>\d+)/retry-failed', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'retry_failed_products'],
            'permission_callback' => [$this, 'check_permissions_write']
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>\\d+)/monitor', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_job_monitor'],
            'permission_callback' => [$this, 'check_permissions']
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>\\d+)/products', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_job_products'],
            'permission_callback' => [$this, 'check_permissions']
        ]);

        register_rest_route($this->namespace, '/' . $this->rest_base . '/process', [
            'methods' => WP_REST_Server::CREATABLE,
            'callback' => [$this, 'process_jobs'],
            'permission_callback' => [$this, 'check_permissions_write']
        ]);

    }

    /**
     * Create a new job
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error Response object
     */
    public function create_job(WP_REST_Request $request) {
        $rl = $this->check_automation_rate_limit('create_job', AutomationConstants::RATE_LIMIT_CREATE_JOB);
        if (is_wp_error($rl)) { return $rl; }

        try {

            $request_data = $request->get_json_params();

            $job_data = [
                'name' => $request_data['name'] ?? '',
                'description' => $request_data['description'] ?? '',
                'tool_type' => $request_data['toolType'] ?? $request_data['tool_type'] ?? '',
                'settings' => $request_data['settings'] ?? [],
                'schedule_config' => $request_data['schedule'] ?? $request_data['schedule_config'] ?? [],
                'product_filter' => $request_data['productFilter'] ?? $request_data['product_filter'] ?? [],
                'total_products' => $request_data['totalProducts'] ?? $request_data['total_products'] ?? 0,
                'created_by' => get_current_user_id()
            ];

            $content_settings = $request_data['contentSettings'] ?? $request_data['content_settings'] ?? [];
            if (!empty($content_settings)) {
                $job_data['settings']['contentSettings'] = $content_settings;
            }

            $job_data['total_products'] = $this->calculateTotalProducts($job_data['product_filter'] ?? []);

            $job = new AutomationJob($job_data);

            $validation_errors = $job->validate();
            if (!empty($validation_errors)) {
                return new WP_Error(
                    'job_validation_failed',
                    'Job validation failed',
                    [
                        'status' => 400,
                        'errors' => $validation_errors
                    ]
                );
            }

            $job_id = $this->job_manager->createJob($job);

            if (!$job_id) {
                return new WP_Error(
                    'job_creation_failed',
                    'Failed to create job',
                    ['status' => 500]
                );
            }

            $job->id = $job_id;
            $this->job_scheduler->scheduleJob($job);

            $created_job = $this->job_manager->getJob($job_id);

            return new WP_REST_Response([
                'success' => true,
                'data' => $this->prepare_job_for_response($created_job),
                'message' => 'Job created successfully'
            ], 201);

        } catch (\Exception $e) {

            return new WP_Error(
                'job_creation_error',
                $e->getMessage(),
                ['status' => 400]
            );
        }
    }

    /**
     * Get jobs list
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response Response object
     */
    public function get_jobs(WP_REST_Request $request) {
        try {
            $filters = [];

            if ($request->get_param('status')) {
                $filters['status'] = $request->get_param('status');
            }

            if ($request->get_param('tool_type')) {
                $filters['tool_type'] = $request->get_param('tool_type');
            }

            if ($request->get_param('created_by')) {
                $filters['created_by'] = $request->get_param('created_by');
            }

            $per_page = $request->get_param('per_page') ?: 20;
            $page = $request->get_param('page') ?: 1;
            $offset = ($page - 1) * $per_page;

            $jobs = $this->job_manager->getJobs($filters, $per_page, $offset);
            $total = $this->job_manager->getJobCount($filters);

            $prepared_jobs = [];
            foreach ($jobs as $job) {
                $prepared_jobs[] = $this->prepare_job_for_response($job);
            }

            $response = new WP_REST_Response([
                'success' => true,
                'data' => $prepared_jobs,
                'pagination' => [
                    'total' => $total,
                    'per_page' => $per_page,
                    'current_page' => $page,
                    'total_pages' => ceil($total / $per_page)
                ]
            ]);

            $response->header('X-WP-Total', $total);
            $response->header('X-WP-TotalPages', ceil($total / $per_page));

            return $response;

        } catch (\Exception $e) {
            return new WP_Error(
                'jobs_fetch_error',
                $e->getMessage(),
                ['status' => 500]
            );
        }
    }

    /**
     * Get specific job
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error Response object
     */
    public function get_job(WP_REST_Request $request) {
        try {
            $job_id = $request->get_param('id');
            $job = $this->job_manager->getJob($job_id);

            if (!$job) {
                return new WP_Error(
                    'job_not_found',
                    'Job not found',
                    ['status' => 404]
                );
            }

            $current_user_id = get_current_user_id();
            if ((int) $job->created_by !== (int) $current_user_id && !current_user_can('manage_woocommerce')) {
                return new WP_Error(
                    'rest_forbidden',
                    __('You do not own this job.', 'ai-product-tools'),
                    ['status' => 403]
                );
            }

            $job_data = $this->prepare_job_for_response($job);
            $job_data['progress_details'] = $this->job_executor->getJobProgress($job_id);
            $job_data['log_statistics'] = $this->logger->getLogStatistics($job_id);

            return new WP_REST_Response([
                'success' => true,
                'data' => $job_data
            ]);

        } catch (\Exception $e) {
            return new WP_Error(
                'job_fetch_error',
                $e->getMessage(),
                ['status' => 500]
            );
        }
    }

    /**
     * Update job
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error Response object
     */
    public function update_job(WP_REST_Request $request) {
        try {
            $job_id = $request->get_param('id');
            $job = $this->job_manager->getJob($job_id);

            if (!$job) {
                return new WP_Error(
                    'job_not_found',
                    'Job not found',
                    ['status' => 404]
                );
            }

            if ($job->isActive()) {
                return new WP_Error(
                    'job_cannot_be_updated',
                    'Cannot update active job',
                    ['status' => 400]
                );
            }

            if ($job->status === 'completed') {
                return new WP_Error(
                    'job_completed_not_editable',
                    'Cannot update a completed job',
                    ['status' => 400]
                );
            }

            if ($request->get_param('name')) {
                $job->name = $request->get_param('name');
            }

            $tool_type = $request->get_param('tool_type');
            if (!$tool_type && $request->get_param('toolType')) {
                $tool_type = $request->get_param('toolType');
            }
            if ($tool_type) {
                $job->tool_type = sanitize_text_field($tool_type);
            }

            if ($request->get_param('description')) {
                $job->description = $request->get_param('description');
            }

            if ($request->get_param('settings')) {
                $job->settings = $request->get_param('settings');
            }

            if ($request->get_param('schedule_config')) {
                $job->schedule_config = $request->get_param('schedule_config');

                $this->job_scheduler->rescheduleJob($job);
            }

            if ($request->get_param('product_filter')) {
                $job->product_filter = $request->get_param('product_filter');
            }

            $this->job_manager->updateJob($job);

            return new WP_REST_Response([
                'success' => true,
                'data' => $this->prepare_job_for_response($job),
                'message' => 'Job updated successfully'
            ]);

        } catch (\Exception $e) {
            return new WP_Error(
                'job_update_error',
                $e->getMessage(),
                ['status' => 400]
            );
        }
    }

    /**
     * Delete job
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error Response object
     */
    public function delete_job(WP_REST_Request $request) {
        try {
            $job_id = $request->get_param('id');
            $job = $this->job_manager->getJob($job_id);

            if (!$job) {
                return new WP_Error(
                    'job_not_found',
                    'Job not found',
                    ['status' => 404]
                );
            }

            if ($job->isActive()) {
                $this->job_executor->cancelJob($job_id);
            }

            $this->job_scheduler->unscheduleJob($job_id);

            \AIPT\Core\Database\OrphanCleaner::on_job_deleted((int) $job_id);

            $this->job_manager->deleteJob($job_id);

            return new WP_REST_Response([
                'success' => true,
                'message' => 'Job deleted successfully'
            ]);

        } catch (\Exception $e) {
            return new WP_Error(
                'job_delete_error',
                $e->getMessage(),
                ['status' => 500]
            );
        }
    }

    /**
     * Pause job
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error Response object
     */
    public function pause_job(WP_REST_Request $request) {
        try {
            $job_id = $request->get_param('id');

            $this->job_executor->pauseJob($job_id);

            $job = $this->job_manager->getJob($job_id);

            return new WP_REST_Response([
                'success' => true,
                'data' => $this->prepare_job_for_response($job),
                'message' => 'Job paused successfully'
            ]);

        } catch (\Exception $e) {
            return new WP_Error(
                'job_pause_error',
                $e->getMessage(),
                ['status' => 400]
            );
        }
    }

    /**
     * Resume job
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error Response object
     */
    public function resume_job(WP_REST_Request $request) {
        try {
            $job_id = $request->get_param('id');

            $this->job_executor->resumeJob($job_id);

            $job = $this->job_manager->getJob($job_id);

            return new WP_REST_Response([
                'success' => true,
                'data' => $this->prepare_job_for_response($job),
                'message' => 'Job resumed successfully'
            ]);

        } catch (\Exception $e) {
            return new WP_Error(
                'job_resume_error',
                $e->getMessage(),
                ['status' => 400]
            );
        }
    }

    /**
     * Get job logs
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error Response object
     */
    public function get_job_logs(WP_REST_Request $request) {
        try {
            $job_id = $request->get_param('id');

            $job = $this->job_manager->getJob($job_id);
            if (!$job) {
                return new WP_Error(
                    'job_not_found',
                    'Job not found',
                    ['status' => 404]
                );
            }

            $current_user_id = get_current_user_id();
            if ((int) $job->created_by !== (int) $current_user_id && !current_user_can('manage_woocommerce')) {
                return new WP_Error(
                    'rest_forbidden',
                    __('You do not own this job.', 'ai-product-tools'),
                    ['status' => 403]
                );
            }

            $filters = [];

            if ($request->get_param('level')) {
                $filters['level'] = $request->get_param('level');
            }
            if ($request->get_param('type')) {
                $filters['type'] = $request->get_param('type');
            }

            $filters['limit'] = $request->get_param('per_page') ?: 50;
            $filters['offset'] = (($request->get_param('page') ?: 1) - 1) * $filters['limit'];

            $logs = $this->logger->getJobLogs($job_id, $filters);

            $prepared_logs = [];
            foreach ($logs as $log) {
                $prepared_logs[] = [
                    'id' => $log->id,
                    'level' => $log->level,
                    'message' => $log->message,
                    'context' => $log->context,
                    'created_at' => $log->created_at
                ];
            }

            return new WP_REST_Response([
                'success' => true,
                'data' => $prepared_logs,
                'statistics' => $this->logger->getLogStatistics($job_id)
            ]);

        } catch (\Exception $e) {
            return new WP_Error(
                'logs_fetch_error',
                $e->getMessage(),
                ['status' => 500]
            );
        }
    }

    /**
     * Get filter options for job creation
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response Response object
     */
    public function get_filter_options(WP_REST_Request $request) {
        try {

            $categories = get_terms([
                'taxonomy' => 'product_cat',
                'hide_empty' => true,
                'orderby' => 'count',
                'order' => 'DESC',
                'number' => 10
            ]);

            $formatted_categories = [];
            foreach ($categories as $category) {
                $formatted_categories[] = [
                    'id' => $category->term_id,
                    'name' => $category->name,
                    'count' => $category->count
                ];
            }

            $tags = get_terms([
                'taxonomy' => 'product_tag',
                'hide_empty' => true,
                'orderby' => 'count',
                'order' => 'DESC',
                'number' => 10
            ]);

            $formatted_tags = [];
            foreach ($tags as $tag) {
                $formatted_tags[] = [
                    'id' => $tag->term_id,
                    'name' => $tag->name,
                    'count' => $tag->count
                ];
            }

            $product_types = [
                ['id' => 'simple', 'name' => 'Simple Product'],
                ['id' => 'variable', 'name' => 'Variable Product'],
                ['id' => 'grouped', 'name' => 'Grouped Product'],
                ['id' => 'external', 'name' => 'External/Affiliate Product']
            ];

            $stock_statuses = [
                ['id' => 'all', 'name' => 'All Stock Statuses'],
                ['id' => 'instock', 'name' => 'In Stock'],
                ['id' => 'outofstock', 'name' => 'Out of Stock'],
                ['id' => 'onbackorder', 'name' => 'On Backorder']
            ];

            $product_statuses = [
                ['id' => 'all', 'name' => 'All Statuses'],
                ['id' => 'publish', 'name' => 'Published'],
                ['id' => 'draft', 'name' => 'Draft'],
                ['id' => 'private', 'name' => 'Private']
            ];

            return new WP_REST_Response([
                'success' => true,
                'data' => [
                    'categories' => $formatted_categories,
                    'tags' => $formatted_tags,
                    'product_types' => $product_types,
                    'stock_statuses' => $stock_statuses,
                    'product_statuses' => $product_statuses
                ]
            ]);

        } catch (\Exception $e) {
            return new WP_Error(
                'filter_options_error',
                $e->getMessage(),
                ['status' => 500]
            );
        }
    }

    /**
     * Check permissions (read-only endpoints)
     * Requires manage_woocommerce capability.
     *
     * @return bool Permission status
     */
    public function check_permissions() {
        return current_user_can('manage_woocommerce');
    }

    /**
     * Check permissions for write operations.
     * Requires manage_woocommerce and a valid REST nonce.
     *
     * @param WP_REST_Request $request
     * @return bool|WP_Error
     */
    public function check_permissions_write($request) {
        if (!current_user_can('manage_woocommerce')) {
            return new WP_Error('rest_forbidden', __('You do not have permission to manage automation jobs.', 'ai-product-tools'), ['status' => 403]);
        }

        $nonce = $request instanceof \WP_REST_Request ? $request->get_header('X-WP-Nonce') : '';
        if (empty($nonce) && $request instanceof \WP_REST_Request) {
            $nonce = $request->get_param('_wpnonce');
        }
        if (empty($nonce) || !wp_verify_nonce($nonce, 'wp_rest')) {
            return new WP_Error('rest_invalid_nonce', __('Invalid or missing security nonce.', 'ai-product-tools'), ['status' => 403]);
        }

        $job_id = (int) ($request instanceof \WP_REST_Request ? $request->get_param('id') : 0);
        if ($job_id > 0) {
            $job = $this->job_manager->getJob($job_id);
            if (!$job) {
                return new WP_Error('job_not_found', __('Job not found', 'ai-product-tools'), ['status' => 404]);
            }
            $current_user_id = get_current_user_id();
            if ((int) $job->created_by !== (int) $current_user_id && !current_user_can('manage_woocommerce')) {
                return new WP_Error('rest_forbidden', __('You do not own this job.', 'ai-product-tools'), ['status' => 403]);
            }
        }

        return true;
    }

    /**
     * Check the shared fixed-window rate limiter for Automation endpoints.
     * Returns true on success or WP_Error 429 if exceeded.
     */
    private function check_automation_rate_limit(string $scope, int $limit_per_minute) {
        $rate_check = $this->checkRateLimit((int) get_current_user_id(), $scope, $limit_per_minute, 60);
        return $rate_check['allowed'] ? true : $this->rateLimitResponse($rate_check);
    }

    /**

     * Prepare job for response
     *
     * @param AutomationJob $job Job object
     * @return array Prepared job data
     */
    private function prepare_job_for_response(AutomationJob $job) {
        return [
            'id' => $job->id,
            'name' => $job->name,
            'description' => $job->description,
            'tool_type' => $job->tool_type,
            'status' => $job->status,
            'progress' => $job->progress,
            'total_products' => $job->total_products,
            'processed_products' => $job->processed_products,
            'failed_products' => $job->failed_products,
            'settings' => $job->settings,
            'schedule_config' => $job->schedule_config,
            'product_filter' => $job->product_filter,
            'next_run' => $job->next_run,
            'started_at' => $job->started_at,
            'completed_at' => $job->completed_at,
            'error_message' => $job->error_message,
            'created_by' => $job->created_by,
            'created_at' => $job->created_at,
            'updated_at' => $job->updated_at
        ];

    }

    /**
     * Get create job arguments
     *
     * @return array Arguments schema
     */
    private function get_create_job_args() {
        return [
            'name' => [
                'required' => false,
                'type' => 'string',
                'sanitize_callback' => 'sanitize_text_field'
            ],
            'description' => [
                'required' => false,
                'type' => 'string',
                'sanitize_callback' => 'sanitize_textarea_field'
            ],
            'toolType' => [
                'required' => false,
                'type' => 'string',
                'sanitize_callback' => 'sanitize_text_field'
            ],
            'tool_type' => [
                'required' => false,
                'type' => 'string',
                'sanitize_callback' => 'sanitize_text_field'
            ],
            'settings' => [
                'required' => false,
                'type' => 'object'
            ],
            'contentSettings' => [
                'required' => false,
                'type' => 'object'
            ],
            'content_settings' => [
                'required' => false,
                'type' => 'object'
            ],
            'schedule' => [
                'required' => false,
                'type' => 'object'
            ],
            'schedule_config' => [
                'required' => false,
                'type' => 'object'
            ],
            'productFilter' => [
                'required' => false,
                'type' => 'object'
            ],
            'product_filter' => [
                'required' => false,
                'type' => 'object'
            ],
            'totalProducts' => [
                'required' => false,
                'type' => 'integer',
                'sanitize_callback' => 'absint'
            ],
            'total_products' => [
                'required' => false,
                'type' => 'integer',
                'sanitize_callback' => 'absint'
            ]
        ];
    }

    /**
     * Get list jobs arguments
     *
     * @return array Arguments schema
     */
    private function get_list_jobs_args() {
        return [
            'status' => [
                'required' => false,
                'type' => 'string',
                'enum' => ['preparing', 'ready', 'running', 'paused', 'completed', 'failed'],
                'sanitize_callback' => 'sanitize_text_field'
            ],
            'tool_type' => [
                'required' => false,
                'type' => 'string',
                'enum' => AutomationJob::VALID_TOOL_TYPES,
                'sanitize_callback' => 'sanitize_text_field'
            ],
            'created_by' => [
                'required' => false,
                'type' => 'integer',
                'sanitize_callback' => 'absint'
            ],
            'per_page' => [
                'required' => false,
                'type' => 'integer',
                'default' => 20,
                'minimum' => 1,
                'maximum' => 100,
                'sanitize_callback' => 'absint'
            ],
            'page' => [
                'required' => false,
                'type' => 'integer',
                'default' => 1,
                'minimum' => 1,
                'sanitize_callback' => 'absint'
            ]
        ];
    }

    /**
     * Get update job arguments
     *
     * @return array Arguments schema
     */
    private function get_update_job_args() {
        return [
            'id' => [
                'required' => true,
                'type' => 'integer',
                'sanitize_callback' => 'absint'
            ],
            'name' => [
                'required' => false,
                'type' => 'string',
                'sanitize_callback' => 'sanitize_text_field',
                'validate_callback' => function($param) {
                    return empty($param) || strlen($param) <= 255;
                }
            ],
            'tool_type' => [
                'required' => false,
                'type' => 'string',
                'enum' => AutomationJob::VALID_TOOL_TYPES,
                'sanitize_callback' => 'sanitize_text_field'
            ],
            'description' => [
                'required' => false,
                'type' => 'string',
                'sanitize_callback' => 'sanitize_textarea_field'
            ],
            'settings' => [
                'required' => false,
                'type' => 'object'
            ],
            'schedule_config' => [
                'required' => false,
                'type' => 'object'
            ],
            'product_filter' => [
                'required' => false,
                'type' => 'object'
            ]
        ];
    }

    /**
     * Get job logs arguments
     *
     * @return array Arguments schema
     */
    private function get_job_logs_args() {
        return [
            'id' => [
                'required' => true,
                'type' => 'integer',
                'sanitize_callback' => 'absint'
            ],
            'level' => [
                'required' => false,
                'type' => 'string',
                'enum' => ['info', 'warning', 'error', 'debug'],
                'sanitize_callback' => 'sanitize_text_field'
            ],
            'type' => [
                'required' => false,
                'type' => 'string',
                'description' => 'Logical error category (AI_PROVIDER, WC_UPDATE, NETWORK, AUTH, RATE_LIMIT, VALIDATION, DB, SYSTEM)',
                'sanitize_callback' => 'sanitize_text_field'
            ],
            'per_page' => [
                'required' => false,
                'type' => 'integer',
                'default' => 50,
                'minimum' => 1,
                'maximum' => 200,
                'sanitize_callback' => 'absint'
            ],
            'page' => [
                'required' => false,
                'type' => 'integer',
                'default' => 1,
                'minimum' => 1,
                'sanitize_callback' => 'absint'
            ]
        ];
    }

    /**
     * Calculate total products based on filter
     *
     * @param array $product_filter Product filter configuration
     * @return int Total products count
     */
    private function calculateTotalProducts($product_filter) {
        global $wpdb;

        try {

            $pf = is_array($product_filter) ? $product_filter : [];
            $categories = !empty($pf['categories']) && is_array($pf['categories']) ? array_values(array_filter(array_map('absint', $pf['categories']))) : [];
            $tags = !empty($pf['tags']) && is_array($pf['tags']) ? array_values(array_filter(array_map('absint', $pf['tags']))) : [];

            $include_ids = [];
            if (!empty($pf['includeIds'])) {
                if (is_array($pf['includeIds'])) {
                    $include_ids = array_map('absint', $pf['includeIds']);
                } elseif (is_string($pf['includeIds'])) {
                    $include_ids = array_map('absint', array_map('trim', explode(',', $pf['includeIds'])));
                }
            }
            $include_ids = array_values(array_filter($include_ids));

            $exclude_ids = [];
            if (!empty($pf['excludeIds'])) {
                if (is_array($pf['excludeIds'])) {
                    $exclude_ids = array_map('absint', $pf['excludeIds']);
                } elseif (is_string($pf['excludeIds'])) {
                    $exclude_ids = array_map('absint', array_map('trim', explode(',', $pf['excludeIds'])));
                }
            }
            $exclude_ids = array_values(array_filter($exclude_ids));

            $allowed_stock = ['instock','outofstock','onbackorder'];
            $stock_status = !empty($pf['stockStatus']) && in_array($pf['stockStatus'], $allowed_stock, true) ? $pf['stockStatus'] : 'all';

            $min_price = (isset($pf['priceRange']['min']) && $pf['priceRange']['min'] !== '' && is_numeric($pf['priceRange']['min'])) ? (float) $pf['priceRange']['min'] : null;
            $max_price = (isset($pf['priceRange']['max']) && $pf['priceRange']['max'] !== '' && is_numeric($pf['priceRange']['max'])) ? (float) $pf['priceRange']['max'] : null;

            $default_filter_options = array(
                'categories'   => array(),
                'tags'         => array(),
                'stock_status' => 'all',
            );

            /**
             * Filter automation job filter options.
             *
             * Premium module can hook here to enable advanced filtering
             * (categories, tags, stock status) when a valid license is present.
             *
             * @since 2.5.0
             * @param array $default_filter_options Default free filter options.
             * @param array $categories             Requested categories from filter.
             * @param array $tags                   Requested tags from filter.
             * @param string $stock_status          Requested stock status from filter.
             */
            $filtered_options = apply_filters(
                'aipt_automation_filter_options',
                $default_filter_options,
                $categories,
                $tags,
                $stock_status
            );

            $categories   = $filtered_options['categories'];
            $tags         = $filtered_options['tags'];
            $stock_status = $filtered_options['stock_status'];

            if (empty($categories) && empty($tags) && empty($include_ids) && empty($exclude_ids) && ($stock_status === 'all') && ($min_price === null) && ($max_price === null)) {

                $count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish'");
                return (int) $count;
            }

            $cache_key = 'aipt_calc_total_' . md5(wp_json_encode([$categories, $tags, $include_ids, $exclude_ids, $stock_status, $min_price, $max_price]));
            $cached = wp_cache_get($cache_key, 'aipt_automation_jobs');
            if ($cached !== false) { return (int) $cached; }

            $query = "
                SELECT COUNT(DISTINCT p.ID)
                FROM {$wpdb->posts} p
                WHERE p.post_type = 'product'
                AND p.post_status = 'publish'
            ";
            $params = [];

            if (!empty($categories)) {
                $placeholders = implode(',', array_fill(0, count($categories), '%d'));
                $query .= " AND p.ID IN (
                    SELECT object_id FROM {$wpdb->term_relationships} tr
                    INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
                    WHERE tt.taxonomy = 'product_cat' AND tt.term_id IN ({$placeholders})
                )";
                $params = array_merge($params, $categories);
            }

            if (!empty($tags)) {
                $placeholders = implode(',', array_fill(0, count($tags), '%d'));
                $query .= " AND p.ID IN (
                    SELECT object_id FROM {$wpdb->term_relationships} tr
                    INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
                    WHERE tt.taxonomy = 'product_tag' AND tt.term_id IN ({$placeholders})
                )";
                $params = array_merge($params, $tags);
            }

            if ($stock_status !== 'all') {
                $query .= " AND p.ID IN (
                    SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_stock_status' AND meta_value = %s
                )";
                $params[] = $stock_status;
            }

            if ($min_price !== null || $max_price !== null) {
                $conditions = [];
                if ($min_price !== null) { $conditions[] = "CAST(pm.meta_value AS DECIMAL(10,2)) >= %f"; $params[] = $min_price; }
                if ($max_price !== null) { $conditions[] = "CAST(pm.meta_value AS DECIMAL(10,2)) <= %f"; $params[] = $max_price; }
                $query .= " AND p.ID IN (
                    SELECT post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key = '_price' AND " . implode(' AND ', $conditions) . "
                )";
            }

            if (!empty($include_ids)) {
                $placeholders = implode(',', array_fill(0, count($include_ids), '%d'));
                $query .= " AND p.ID IN ({$placeholders})";
                $params = array_merge($params, $include_ids);
            }

            if (!empty($exclude_ids)) {
                $placeholders = implode(',', array_fill(0, count($exclude_ids), '%d'));
                $query .= " AND p.ID NOT IN ({$placeholders})";
                $params = array_merge($params, $exclude_ids);
            }

            $count = !empty($params) ? $wpdb->get_var($wpdb->prepare($query, $params)) : $wpdb->get_var($query);
            wp_cache_set($cache_key, (int) $count, 'aipt_automation_jobs', 300);
            return (int) $count;
        } catch (\Exception $e) {

            return 10;
        }
    }

    /**
     * Test endpoint to verify API is working
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response Response object
     */
    /**
     * Estimate total products for given product filters
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response Response
     */
    public function estimate_products(WP_REST_Request $request) {
        try {
            $product_filter = $request->get_param('productFilter');
            if (empty($product_filter)) {
                $product_filter = $request->get_param('product_filter');
            }
            if (!is_array($product_filter)) {
                $product_filter = [];
            }

            foreach (['categories', 'tags'] as $arr_key) {
                if (!empty($product_filter[$arr_key]) && is_array($product_filter[$arr_key])) {
                    $product_filter[$arr_key] = array_map('absint', $product_filter[$arr_key]);
                }
            }
            foreach (['includeIds', 'excludeIds'] as $csv_key) {
                if (!empty($product_filter[$csv_key]) && is_string($product_filter[$csv_key])) {

                    $ids = array_filter(array_map('absint', array_map('trim', explode(',', $product_filter[$csv_key]))));
                    $product_filter[$csv_key] = implode(',', $ids);
                }
            }

            $count = $this->calculateTotalProducts($product_filter);

            return new WP_REST_Response([
                'success' => true,
                'data' => [ 'count' => (int) $count ],
            ]);
        } catch (\Exception $e) {
            return new WP_REST_Response([
                'success' => false,

                'message' => 'Failed to estimate products',
            ], 500);
        }
    }

    public function test_endpoint(WP_REST_Request $request) {
        return new WP_REST_Response([
            'success' => true,
            'message' => 'Automation Jobs API is working!',
            'timestamp' => current_time('mysql'),
            'user_id' => get_current_user_id(),
            'can_manage_woocommerce' => current_user_can('manage_woocommerce'),
            'can_edit_posts' => current_user_can('edit_posts')
        ]);
    }

    /**
     * Manually trigger a job for testing
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response Response object
     */
    public function trigger_job(WP_REST_Request $request) {
        try {
            $job_id = (int) $request->get_param('id');

            $rl = $this->check_automation_rate_limit('trigger_job_' . $job_id, AutomationConstants::RATE_LIMIT_TRIGGER_JOB);
            if (is_wp_error($rl)) { return $rl; }

            $job = $this->job_manager->getJob($job_id);
            if (!$job) {
                return new WP_Error(
                    'job_not_found',
                    'Job not found',
                    ['status' => 404]
                );
            }

            if (in_array($job->status, ['completed'], true)) {
                return new WP_Error(
                    'trigger_invalid_state',
                    sprintf('Cannot trigger job in status: %s', $job->status),
                    ['status' => 400]
                );
            }

            if ($job->status === 'failed') {
                $this->job_manager->updateJobStatus($job_id, 'ready');
                $job = $this->job_manager->getJob($job_id);
            }

            \AIPT\Api\AutomationJobs\JobExecutionHandler::register_job_hook($job_id);

            $hook = 'aipt_execute_job_' . $job_id;
            $base = time() + AutomationConstants::TRIGGER_EXECUTION_DELAY;
            $scheduled_ts = null;

            for ($i = 0; $i < AutomationConstants::TRIGGER_EXECUTION_ATTEMPTS; $i++) {
                $ts = $base + $i;
                $ok = wp_schedule_single_event($ts, $hook, [$job_id]);
                if ($ok) { $scheduled_ts = $ts; break; }
            }

            if ($scheduled_ts === null) {

                $existing = wp_next_scheduled($hook, [$job_id]);

                if ($existing) {

                    $unscheduled = wp_unschedule_event($existing, $hook, [$job_id]);
                    $this->logger->info($job_id, 'Trigger: unscheduled existing event to requeue earlier', [
                        'existing' => gmdate('Y-m-d H:i:s', $existing),
                        'unscheduled' => (bool) $unscheduled,
                    ]);

                    for ($i = 0; $i < AutomationConstants::TRIGGER_EXECUTION_ATTEMPTS; $i++) {
                        $ts = time() + AutomationConstants::TRIGGER_EXECUTION_DELAY + $i;
                        $ok = wp_schedule_single_event($ts, $hook, [$job_id]);
                        if ($ok) { $scheduled_ts = $ts; break; }
                    }
                }
            }

            if ($scheduled_ts === null) {
                return new WP_Error(
                    'trigger_failed',
                    'Failed to schedule job execution',
                    ['status' => 500]
                );
            }

            if (defined('AIPT_DEV_SYNC_EXECUTION') && AIPT_DEV_SYNC_EXECUTION) {
                try {
                    \AIPT\Api\AutomationJobs\JobExecutionHandler::execute_job_callback($job_id);
                } catch (\Exception $e) {
                }
            }

            return new WP_REST_Response([
                'success' => true,
                'scheduled_only' => ! (defined('AIPT_DEV_SYNC_EXECUTION') && AIPT_DEV_SYNC_EXECUTION),
                'message' => 'Job scheduled for background execution',
                'job_id' => $job_id,
                'hook' => $hook,
                'scheduled_time' => gmdate('Y-m-d H:i:s', $scheduled_ts)
            ]);

        } catch (\Exception $e) {
            return new WP_Error(
                'trigger_error',
                $e->getMessage(),
                ['status' => 500]
            );
        }
    }

    /**
     * Create a new job that retries only the failed products from the source job.
     *
     * Copies the source job's settings, tool_type and schedule_config, overrides
     * the product filter to target only the failed product IDs, then creates and
     * schedules the new job through the standard creation pipeline.
     *
     * @param WP_REST_Request $request Request object containing source job ID
     * @return WP_REST_Response|WP_Error New job data or error
     */
    public function retry_failed_products(WP_REST_Request $request) {
        $rl = $this->check_automation_rate_limit('retry_failed', AutomationConstants::RATE_LIMIT_RETRY_FAILED);
        if (is_wp_error($rl)) { return $rl; }

        try {
            global $wpdb;
            $source_job_id = (int) $request->get_param('id');

            $source_job = $this->job_manager->getJob($source_job_id);
            if (!$source_job) {
                return new WP_Error(
                    'job_not_found',
                    'Source job not found',
                    ['status' => 404]
                );
            }

			$terminalStatuses = array( 'completed', 'failed' );
			$jobStatus = $source_job->status;
			if ( ! in_array( $jobStatus, $terminalStatuses, true ) ) {
                return new WP_Error(
                    'job_not_terminal',
                    sprintf( 'Cannot retry job in "%s" status. Job must be completed or failed.', $jobStatus ),
                    array( 'status' => 400 )
                );
            }

            $failed_ids = $this->job_manager->getFailedProductIdsByJobId($source_job_id);

            if (empty($failed_ids)) {
                return new WP_Error(
                    'no_failed_products',
                    'No failed products found in this job',
                    ['status' => 400]
                );
            }

            $settings = is_array($source_job->settings) ? $source_job->settings : [];
            $job_data = [
                'name'            => sanitize_text_field($source_job->name) . ' (Retry)',
                'description'     => sprintf('Retry of %d failed products from job #%d', count($failed_ids), $source_job_id),
                'tool_type'       => $source_job->tool_type,
                'settings'        => $settings,
                'schedule_config' => ['type' => 'once'], 
                'product_filter'  => [
                    'includeIds' => implode(',', array_map('absint', $failed_ids)),
                ],
                'total_products'  => count($failed_ids),
                'created_by'      => get_current_user_id(),
            ];

            $job = new AutomationJob($job_data);

            $validation_errors = $job->validate();
            if (!empty($validation_errors)) {
                return new WP_Error(
                    'job_validation_failed',
                    'Retry job validation failed',
                    ['status' => 400, 'errors' => $validation_errors]
                );
            }

            $new_job_id = $this->job_manager->createJob($job);
            if (!$new_job_id) {
                return new WP_Error(
                    'job_creation_failed',
                    'Failed to create retry job',
                    ['status' => 500]
                );
            }

            $job->id = $new_job_id;
            $this->job_scheduler->scheduleJob($job);

            $created_job = $this->job_manager->getJob($new_job_id);

            return new WP_REST_Response([
                'success'          => true,
                'data'             => $this->prepare_job_for_response($created_job),
                'message'          => sprintf('Retry job created with %d failed products', count($failed_ids)),
                'source_job_id'    => $source_job_id,
                'failed_count'     => count($failed_ids),
            ], 201);

        } catch (\Exception $e) {
            return new WP_Error(
                'retry_failed_error',
                $e->getMessage(),
                ['status' => 500]
            );
        }
    }

    /**
     * Lightweight monitor endpoint for real-time job progress
     * - Cached for a few seconds to avoid DB pressure
     * - Returns progress, counts, time estimations and current processing items (up to 3)
     */
    public function get_job_monitor(\WP_REST_Request $request) {
        try {
            global $wpdb;
            $job_id = (int) $request->get_param('id');

            $rl = $this->check_automation_rate_limit('monitor_' . $job_id, AutomationConstants::RATE_LIMIT_MONITOR_WINDOW);
            if (is_wp_error($rl)) { return $rl; }

            $owner_id = (int) $wpdb->get_var(
                $wpdb->prepare("SELECT created_by FROM {$wpdb->prefix}aipt_automation_jobs WHERE id = %d", $job_id)
            );

            if ($owner_id <= 0) {
                return new \WP_Error('job_not_found', 'Job not found', ['status' => 404]);
            }
            $current_user_id = get_current_user_id();
            if ($owner_id !== (int) $current_user_id && !current_user_can('manage_woocommerce')) {
                return new \WP_Error('rest_forbidden', __('You do not own this job.', 'ai-product-tools'), ['status' => 403]);
            }

            $cache_key = 'aipt_job_monitor_' . $job_id;
            $cached = get_transient($cache_key);
            if ($cached !== false) {
                return new \WP_REST_Response(['success' => true, 'data' => $cached]);
            }

            $job = $this->job_manager->getJob($job_id);
            if (!$job) {
                return new \WP_Error('job_not_found', 'Job not found', ['status' => 404]);
            }

            if ($job->status !== 'running') {
                $data = [
                    'id' => (int) $job->id,
                    'status' => $job->status,
                    'progress_percent' => (int) $job->progress,
                    'processed_products' => (int) $job->processed_products,
                    'total_products' => (int) $job->total_products,
                    'failed_products' => (int) $job->failed_products,
                    'started_at' => $job->started_at,
                    'estimated_completion' => null,
                    'elapsed_seconds' => null,
                    'elapsed_human' => null,
                    'remaining_seconds' => null,
                    'remaining_human' => null,
                    'current_processing' => [ 'count' => 0, 'items' => [] ],
                    'updated_at' => current_time('mysql'),
                ];
                set_transient($cache_key, $data, 5);
                return new \WP_REST_Response(['success' => true, 'data' => $data]);
            }

            $progress = $this->job_executor->getJobProgress($job_id);

            $started_at_ts = $job->started_at ? strtotime($job->started_at) : null;
            $now = time();
            $elapsed_seconds = $started_at_ts ? max(0, $now - $started_at_ts) : null;

            $eta_str = $progress['estimated_completion'] ?? null;
            $eta_ts = $eta_str ? strtotime($eta_str) : null;
            $remaining_seconds = ($eta_ts && $eta_ts > $now) ? ($eta_ts - $now) : null;

            $human = function($seconds) {
                if ($seconds === null) return null;
                if ($seconds < 60) return '~' . $seconds . 's';
                $m = floor($seconds / 60);
                if ($m < 60) return '~' . $m . ' min';
                $h = floor($m / 60); $rm = $m % 60;
                return '~' . $h . 'h ' . ($rm ? $rm . 'm' : '');
            };

            $job_products_table = $wpdb->prefix . 'aipt_job_products';

            $processing_rows = $wpdb->get_results(
                $wpdb->prepare(

                    "SELECT product_id FROM {$job_products_table} WHERE job_id = %d AND status = 'processing' ORDER BY processing_started_at DESC, id DESC LIMIT 3",
                    $job_id
                )
            );

            $current_items = [];
            if (!empty($processing_rows)) {
                foreach ($processing_rows as $row) {
                    $pid = (int) $row->product_id;
                    $title = get_the_title($pid);
                    $current_items[] = [
                        'product_id' => $pid,
                        'title' => is_string($title) ? $title : ''
                    ];
                }
            }

            $data = [
                'id' => (int) $job->id,
                'status' => $job->status,
                'progress_percent' => (int) ($progress['progress'] ?? $job->progress),
                'processed_products' => (int) ($progress['processed_products'] ?? $job->processed_products),
                'total_products' => (int) ($progress['total_products'] ?? $job->total_products),
                'failed_products' => (int) ($progress['failed_products'] ?? $job->failed_products),
                'started_at' => $job->started_at,
                'estimated_completion' => $eta_str,
                'elapsed_seconds' => $elapsed_seconds,
                'elapsed_human' => $human($elapsed_seconds),
                'remaining_seconds' => $remaining_seconds,
                'remaining_human' => $human($remaining_seconds),
                'current_processing' => [
                    'count' => count($current_items),
                    'items' => $current_items
                ],
                'updated_at' => current_time('mysql'),
            ];

            set_transient($cache_key, $data, 5);

            return new \WP_REST_Response(['success' => true, 'data' => $data]);
        } catch (\Exception $e) {
            return new \WP_Error('monitor_error', $e->getMessage(), ['status' => 500]);
        }
    }

    /**
     * Stop job (cancel execution)
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error Response object
     */
    public function stop_job(WP_REST_Request $request) {
        try {
            $job_id = $request->get_param('id');

            $this->job_executor->cancelJob($job_id);

            $job = $this->job_manager->getJob($job_id);

            return new WP_REST_Response([
                'success' => true,
                'data' => $this->prepare_job_for_response($job),
                'message' => 'Job stopped successfully'
            ]);

        } catch (\Exception $e) {
            return new WP_Error(
                'job_stop_error',
                $e->getMessage(),
                ['status' => 400]
            );
        }
    }

    /**
     * Get paginated list of job products with statuses and titles (compact)
     *
     * @param WP_REST_Request $request
     * @return WP_REST_Response|WP_Error
     */
    public function get_job_products(WP_REST_Request $request) {
        global $wpdb;
        try {
            $job_id = (int) $request->get_param('id');
            $page = max(1, (int) $request->get_param('page')) ?: 1;
            $per_page = (int) $request->get_param('per_page');
            if ($per_page <= 0) { $per_page = 50; }
            if ($per_page > 200) { $per_page = 200; }
            $offset = ($page - 1) * $per_page;

            $owner_id = (int) $wpdb->get_var(
                $wpdb->prepare("SELECT created_by FROM {$wpdb->prefix}aipt_automation_jobs WHERE id = %d", $job_id)
            );

            if ($owner_id <= 0) {
                return new \WP_Error('job_not_found', 'Job not found', ['status' => 404]);
            }
            $current_user_id = get_current_user_id();
            if ($owner_id !== (int) $current_user_id && !current_user_can('manage_woocommerce')) {
                return new \WP_Error('rest_forbidden', __('You do not own this job.', 'ai-product-tools'), ['status' => 403]);
            }

            $status = $request->get_param('status');
            $allowed_status = ['pending','processing','completed','failed'];
            $where = " WHERE jp.job_id = %d";
            $params = [$job_id];
            if (is_string($status) && in_array($status, $allowed_status, true)) {
                $where .= " AND jp.status = %s";
                $params[] = $status;
            }

            $job_products_table = $wpdb->prefix . 'aipt_job_products';

            $count_sql = "SELECT COUNT(*) FROM {$job_products_table} jp" . $where;

            if (count($params) === 1) {
                $prepared_count = $wpdb->prepare($count_sql, $params[0]);
            } else {
                $prepared_count = $wpdb->prepare($count_sql, $params[0], $params[1]);
            }
            $total = (int) $wpdb->get_var($prepared_count);

            $results_params = array_merge($params, [$per_page, $offset]);
            $results_sql = "SELECT jp.product_id, jp.status, COALESCE(p.post_title, '') as title
                     FROM {$job_products_table} jp
                     LEFT JOIN {$wpdb->posts} p ON p.ID = jp.product_id
                     " . $where . "
                     ORDER BY jp.id ASC
                     LIMIT %d OFFSET %d";

            if (count($results_params) === 3) {
                $prepared_results = $wpdb->prepare($results_sql, $results_params[0], $results_params[1], $results_params[2]);
            } else {
                $prepared_results = $wpdb->prepare($results_sql, $results_params[0], $results_params[1], $results_params[2], $results_params[3]);
            }
            $rows = $wpdb->get_results($prepared_results, ARRAY_A);

            $items = [];
            if (!empty($rows)) {
                foreach ($rows as $r) {
                    $items[] = [
                        'product_id' => (int) $r['product_id'],
                        'status' => (string) $r['status'],
                        'title' => (string) $r['title'],
                    ];
                }
            }

            $pages = $per_page > 0 ? (int) ceil($total / $per_page) : 1;

            return new \WP_REST_Response([
                'success' => true,
                'data' => [
                    'total' => $total,
                    'page' => $page,
                    'per_page' => $per_page,
                    'pages' => $pages,
                    'items' => $items,
                ]
            ]);
        } catch (\Exception $e) {
            return new \WP_Error('job_products_error', $e->getMessage(), ['status' => 500]);
        }
    }

    /**
     * Process running/ready jobs - browser-based fallback for WP-Cron disabled environments
     *
     * This endpoint allows the frontend to trigger job execution when WP-Cron is disabled.
     * Similar to Store Index's /process endpoint, it processes a single tick of work.
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response Response with processing status
     */
    public function process_jobs(WP_REST_Request $request) {
        $rl = $this->check_automation_rate_limit('process_jobs', AutomationConstants::RATE_LIMIT_PROCESS_JOBS);
        if (is_wp_error($rl)) {
            return $rl;
        }

        $lock_key = 'aipt_browser_job_processing';
        $lock_token = $this->acquireBrowserLock($lock_key);

        if ($lock_token === null) {
            return new WP_REST_Response([
                'success' => true,
                'needs_manual_process' => true,
                'processed_job_id' => null,
                'processed_job_name' => null,
                'execution_result' => null,
                'active_jobs' => [],
                'active_jobs_count' => 0,
                'message' => 'Another process is running',
            ], 200);
        }

        try {
            global $wpdb;
            $jobs_table = $wpdb->prefix . 'aipt_automation_jobs';

            if (!$wpdb->get_var("SHOW TABLES LIKE '{$jobs_table}'")) {
                return new WP_REST_Response([
                    'success' => false,
                    'message' => 'Automation jobs table not found',
                    'needs_manual_process' => false,
                ], 500);
            }

            $active_jobs = $wpdb->get_results(
                "SELECT id, status, name, progress, total_products, processed_products 
                 FROM {$jobs_table} 
                 WHERE status IN ('running', 'ready', 'preparing') 
                 ORDER BY updated_at ASC 
                 LIMIT 1"
            );

            $processed_job_id = null;
            $processed_job_name = null;
            $execution_result = null;
            $execution_error = null;

            if (!empty($active_jobs)) {
                $job_to_process = $active_jobs[0];
                $processed_job_id = (int) $job_to_process->id;
                $processed_job_name = $job_to_process->name;

                try {
                    $execution_result = $this->job_executor->executeJob($processed_job_id);
                } catch (\Exception $e) {
                    $this->logger->error($processed_job_id, 'Browser-based execution failed', [
                        'error' => $e->getMessage(),
                        'trace' => $e->getTraceAsString()
                    ]);
                    $execution_result = false;
                    $execution_error = __('Job execution failed. Please check the logs for details.', 'ai-product-tools');
                }
            }

            $remaining_jobs = $wpdb->get_results(
                "SELECT id, status, name, progress, total_products, processed_products, failed_products
                 FROM {$jobs_table} 
                 WHERE status IN ('running', 'ready', 'preparing') 
                 ORDER BY updated_at ASC"
            );

            $needs_manual_process = !empty($remaining_jobs);

            $jobs_summary = array_map(function($job) {
                return [
                    'id' => (int) $job->id,
                    'name' => $job->name,
                    'status' => $job->status,
                    'progress' => (int) $job->progress,
                    'total_products' => (int) $job->total_products,
                    'processed_products' => (int) $job->processed_products,
                    'failed_products' => (int) $job->failed_products,
                ];
            }, $remaining_jobs);

            return new WP_REST_Response([
                'success' => true,
                'needs_manual_process' => $needs_manual_process,
                'processed_job_id' => $processed_job_id,
                'processed_job_name' => $processed_job_name,
                'execution_result' => $execution_result,
                'execution_error' => $execution_error,
                'active_jobs' => $jobs_summary,
                'active_jobs_count' => count($remaining_jobs),
            ], 200);

        } catch (\Exception $e) {
            return new WP_REST_Response([
                'success' => false,
                'message' => $e->getMessage(),
                'needs_manual_process' => false,
            ], 500);
        } finally {
            $this->releaseBrowserLock($lock_key, $lock_token);
        }
    }

    /**
     * Atomically acquire a browser processing lock using wp_options.
     *
     * Uses add_option() which performs a single INSERT — atomic by DB constraint.
     * Falls back to force-acquire when the existing lock's TTL has expired.
     *
     * @param string $lock_key   Option name to use as the lock.
     * @param int    $ttl_seconds Lock TTL in seconds (default 120).
     * @return string|null UUID token on success; null if lock is held.
     */
    private function acquireBrowserLock(string $lock_key, int $ttl_seconds = 120): ?string {
        $token      = wp_generate_uuid4();
        $expires_at = time() + $ttl_seconds;
        $value      = wp_json_encode(['token' => $token, 'expires_at' => $expires_at]);

        $acquired = add_option($lock_key, $value, '', 'no');

        if ($acquired) {
            return $token;
        }

        $existing      = get_option($lock_key);
        $existing_data = json_decode((string) $existing, true);
        if (is_array($existing_data) && (int) $existing_data['expires_at'] < time()) {

            delete_option($lock_key);

            $acquired = add_option($lock_key, $value, '', 'no');
            return $acquired ? $token : null;
        }

        return null; 
    }

    /**
     * Release a browser processing lock only when the token matches.
     *
     * Token matching prevents a late-arriving process from releasing a lock
     * it never owned (e.g. after a force-acquire by an expired-lock path).
     *
     * @param string $lock_key Option name used as the lock.
     * @param string $token    Token returned by acquireBrowserLock().
     * @return bool True on successful release; false if token mismatch or key missing.
     */
    private function releaseBrowserLock(string $lock_key, string $token): bool {
        $existing      = get_option($lock_key);
        $existing_data = json_decode((string) $existing, true);
        if (is_array($existing_data) && ($existing_data['token'] ?? null) === $token) {
            return delete_option($lock_key);
        }
        return false;
    }

}
