<?php

namespace WPSN\Modules\BulkTable;

use WPSN\Core\Container;
use WPSN\Core\Logger;
use WPSN\Core\Queue\QueueInterface;
use WPSN\Modules\BulkTable\Admin\BulkTableImporter;
use WPSN\Modules\BulkTable\Admin\TermBulkTableImporter;

defined('ABSPATH') || exit;

/**
 * Queue jobs for Bulk Table workflows.
 *
 * Motivation:
 * - Large CSV/JSON imports can time out if applied in one request.
 * - Many shared hosts return HTTP 503 for frequent admin-ajax calls.
 *
 * This job applies import preview ops in small batches via the internal queue.
 */
final class BulkTableJobs
{
    public const JOB_IMPORT_APPLY = 'bulk_table_import_apply';

    private Container $container;
    private Logger $logger;
    private QueueInterface $queue;

    public function __construct(Container $container)
    {
        $this->container = $container;
        $this->logger = $container->get('logger');
        $this->queue = $container->get('queue');
    }

    public function register(): void
    {
        add_filter('wpsn_queue_handle_' . self::JOB_IMPORT_APPLY, [$this, 'handleImportApply'], 10, 3);
    }

    private function sessionKey(string $sessionId): string
    {
        return 'wpsn_bulk_table_import_queue_' . sanitize_key($sessionId);
    }

    private function activeKey(int $userId, string $view): string
    {
        $view = in_array($view, ['posts', 'terms'], true) ? $view : 'posts';
        return 'wpsn_bulk_table_import_queue_active_' . $view . '_' . (int) $userId;
    }

    private function previewKey(int $userId, string $token, string $view): string
    {
        $view = in_array($view, ['posts', 'terms'], true) ? $view : 'posts';
        return 'wpsn_bulk_table_preview_' . $view . '_' . (int) $userId . '_' . $token;
    }

    /**
     * Queue handler: bulk_table_import_apply
     *
     * @param mixed $unused
     * @param array<string,mixed> $payload
     * @param array<string,mixed> $jobRow
     * @return array<string,mixed>
     */
    public function handleImportApply($unused, array $payload, array $jobRow): array
    {
        $sessionId = isset($payload['session_id']) ? sanitize_key((string) $payload['session_id']) : '';
        if ($sessionId === '') {
            return ['status' => 'done'];
        }

        $session = get_transient($this->sessionKey($sessionId));
        if (!is_array($session)) {
            return ['status' => 'done'];
        }

        $status = isset($session['status']) ? (string) $session['status'] : '';
        if (in_array($status, ['canceled', 'done', 'failed'], true)) {
            return ['status' => 'done'];
        }

        $userId = isset($session['user_id']) ? (int) $session['user_id'] : 0;
        $token = isset($session['token']) ? sanitize_key((string) $session['token']) : '';
        $view = isset($session['view']) ? sanitize_key((string) $session['view']) : 'posts';
        $view = in_array($view, ['posts', 'terms'], true) ? $view : 'posts';
        $batchSize = isset($session['batch_size']) ? (int) $session['batch_size'] : 50;
        $batchSize = max(10, min(500, $batchSize));

        $offset = isset($session['offset']) ? (int) $session['offset'] : 0;
        if ($offset < 0) {
            $offset = 0;
        }

        $appliedFields = isset($session['applied_fields']) ? (int) $session['applied_fields'] : 0;
        if ($appliedFields < 0) {
            $appliedFields = 0;
        }

        if ($userId <= 0 || $token === '') {
            $session['status'] = 'failed';
            $session['last_error'] = 'Invalid import session (missing user/token).';
            $session['updated_at'] = time();
            set_transient($this->sessionKey($sessionId), $session, 6 * HOUR_IN_SECONDS);
            return ['status' => 'done'];
        }

        $preview = get_transient($this->previewKey($userId, $token, $view));
        if (!is_array($preview) || empty($preview['ops']) || !is_array($preview['ops'])) {
            $session['status'] = 'failed';
            $session['last_error'] = 'Import preview expired or invalid.';
            $session['updated_at'] = time();
            set_transient($this->sessionKey($sessionId), $session, 6 * HOUR_IN_SECONDS);
            return ['status' => 'done'];
        }

        $ops = $preview['ops'];
        $total = count($ops);

        if ($total <= 0) {
            $session['status'] = 'done';
            $session['offset'] = 0;
            $session['total_ops'] = 0;
            $session['updated_at'] = time();
            $session['finished_at'] = time();
            set_transient($this->sessionKey($sessionId), $session, 6 * HOUR_IN_SECONDS);
            // Keep a small summary so the UI can show "Done".
            $preview['ops'] = [];
            if (isset($preview['changes']) && is_array($preview['changes']) && count($preview['changes']) > 200) {
                $preview['changes'] = array_slice($preview['changes'], 0, 200);
            }
            $preview['applied_at'] = time();
            $preview['applied_fields'] = 0;
            set_transient($this->previewKey($userId, $token, $view), $preview, 2 * HOUR_IN_SECONDS);
            return ['status' => 'done'];
        }

        if ($offset >= $total) {
            // Already done (should be rare).
            $session['status'] = 'done';
            $session['offset'] = $total;
            $session['total_ops'] = $total;
            $session['updated_at'] = time();
            $session['finished_at'] = time();
            set_transient($this->sessionKey($sessionId), $session, 6 * HOUR_IN_SECONDS);
            // Keep a small summary so the UI can show "Done".
            $preview['ops'] = [];
            if (isset($preview['changes']) && is_array($preview['changes']) && count($preview['changes']) > 200) {
                $preview['changes'] = array_slice($preview['changes'], 0, 200);
            }
            $preview['applied_at'] = time();
            $preview['applied_fields'] = $appliedFields;
            set_transient($this->previewKey($userId, $token, $view), $preview, 2 * HOUR_IN_SECONDS);
            return ['status' => 'done'];
        }

        // Apply next batch.
        $batch = array_slice($ops, $offset, $batchSize);

        // Run as the initiating user to respect permissions.
        $prevUserId = function_exists('get_current_user_id') ? (int) get_current_user_id() : 0;
        if (function_exists('wp_set_current_user')) {
            wp_set_current_user($userId);
        }

        try {
            if ($view === 'terms') {
                $importer = new TermBulkTableImporter($this->container);
            } else {
                $importer = new BulkTableImporter($this->container);
            }

            $fieldsThisStep = (int) $importer->applyOps($batch);
            $appliedFields += $fieldsThisStep;
        } catch (\Throwable $e) {
            $this->logger->error('Bulk Table import apply failed: ' . $e->getMessage());

            $session['status'] = 'failed';
            $session['last_error'] = $e->getMessage();
            $session['updated_at'] = time();
            set_transient($this->sessionKey($sessionId), $session, 6 * HOUR_IN_SECONDS);
            return ['status' => 'done'];
        } finally {
            if (function_exists('wp_set_current_user') && $prevUserId !== $userId) {
                wp_set_current_user($prevUserId);
            }
        }

        $offset += count($batch);

        $session['status'] = 'running';
        $session['offset'] = $offset;
        $session['total_ops'] = $total;
        $session['applied_fields'] = $appliedFields;
        $session['updated_at'] = time();
        $session['last_error'] = '';
        set_transient($this->sessionKey($sessionId), $session, 6 * HOUR_IN_SECONDS);

        $done = $offset >= $total;
        if ($done) {
            $session['status'] = 'done';
            $session['finished_at'] = time();
            set_transient($this->sessionKey($sessionId), $session, 6 * HOUR_IN_SECONDS);
            // Keep a small summary so the UI can show "Done".
            $preview['ops'] = [];
            if (isset($preview['changes']) && is_array($preview['changes']) && count($preview['changes']) > 200) {
                $preview['changes'] = array_slice($preview['changes'], 0, 200);
            }
            $preview['applied_at'] = time();
            $preview['applied_fields'] = $appliedFields;
            set_transient($this->previewKey($userId, $token, $view), $preview, 2 * HOUR_IN_SECONDS);
            return ['status' => 'done'];
        }

        // Re-enqueue the next step.
        $this->queue->enqueue(self::JOB_IMPORT_APPLY, [
            'session_id' => $sessionId,
        ], time() + 1);

        return ['status' => 'done'];
    }
}
