<?php

namespace WPSN\Modules\IndexationRisk;

use WPSN\Core\Logger;
use WPSN\Core\Queue\QueueInterface;
use WPSN\Core\Settings;

defined('ABSPATH') || exit;

final class IndexationRiskJobs
{
    public const JOB_SCAN = 'indexation_risk_scan';

    public const PROGRESS_OPTION = 'wpsn_indexation_risk_progress';
    public const LAST_SCAN_OPTION = 'wpsn_indexation_risk_last_scan';

    private Settings $settings;
    private Logger $logger;
    private QueueInterface $queue;

    private IndexationRiskRepository $repo;
    private IndexationRiskCalculator $calc;

    public function __construct(Settings $settings, Logger $logger, QueueInterface $queue)
    {
        $this->settings = $settings;
        $this->logger = $logger;
        $this->queue = $queue;

        $this->repo = new IndexationRiskRepository($logger);
        $this->calc = new IndexationRiskCalculator($settings);
    }

    /**
     * Queue handler: indexation_risk_scan
     *
     * Payload:
     * - post_types: array<string>
     * - offset: int
     * - limit: int
     * - total: int
     * - scan_id: string
     *
     * @param mixed $unused
     * @param array<string,mixed> $payload
     * @param array<string,mixed> $jobRow
     * @return array<string,mixed>
     */
    public function handleScan($unused, array $payload, array $jobRow): array
    {
        IndexationRiskInstaller::maybeInstall();

        $offset = isset($payload['offset']) ? max(0, (int) $payload['offset']) : 0;
        $limit  = isset($payload['limit']) ? max(1, min(200, (int) $payload['limit'])) : 25;
        $total  = isset($payload['total']) ? max(0, (int) $payload['total']) : 0;

        $scanId = isset($payload['scan_id']) ? sanitize_text_field((string) $payload['scan_id']) : '';
        if ($scanId === '') {
            $scanId = (string) time();
        }

        $postTypes = $payload['post_types'] ?? [];
        if (is_string($postTypes)) {
            $postTypes = array_filter(array_map('sanitize_key', array_map('trim', explode(',', $postTypes))));
        }
        if (!is_array($postTypes) || empty($postTypes)) {
            $postTypes = $this->defaultPostTypes();
        }

        // Fetch a batch of post IDs.
        $q = new \WP_Query([
            'post_type' => $postTypes,
            'post_status' => 'publish',
            'fields' => 'ids',
            'posts_per_page' => $limit,
            'offset' => $offset,
            'orderby' => 'ID',
            'order' => 'ASC',
            'no_found_rows' => true,
            'ignore_sticky_posts' => true,
        ]);

        $ids = is_array($q->posts) ? array_map('intval', $q->posts) : [];
        $processedNow = 0;

        foreach ($ids as $postId) {
            if ($postId <= 0) {
                continue;
            }

            try {
                $row = $this->calc->calculateForPost($postId);
                $row['scanned_at'] = current_time('mysql', true);
                $this->repo->upsert($row);
                $processedNow++;
            } catch (\Throwable $e) {
                $this->logger->error('IndexationRisk scan failed for post ' . $postId . ': ' . $e->getMessage());
            }
        }

        // Update progress option.
        $progress = get_option(self::PROGRESS_OPTION, []);
        if (!is_array($progress)) {
            $progress = [];
        }

        $prevProcessed = isset($progress['processed']) ? (int) $progress['processed'] : 0;
        $newProcessed = $prevProcessed + $processedNow;

        $progress['status'] = 'running';
        $progress['scan_id'] = $scanId;
        $progress['post_types'] = array_values($postTypes);
        $progress['total'] = $total;
        $progress['processed'] = $newProcessed;
        $progress['updated_at'] = current_time('mysql', true);
        $progress['offset'] = $offset;
        $progress['limit'] = $limit;

        if (!isset($progress['started_at'])) {
            $progress['started_at'] = current_time('mysql', true);
        }

        update_option(self::PROGRESS_OPTION, $progress, false);

        // Decide if we continue.
        $hasMore = count($ids) === $limit;
        if ($total > 0 && $newProcessed >= $total) {
            $hasMore = false;
        }

        if ($hasMore) {
            // Enqueue next batch.
            $this->queue->enqueue(self::JOB_SCAN, [
                'post_types' => $postTypes,
                'offset' => $offset + $limit,
                'limit' => $limit,
                'total' => $total,
                'scan_id' => $scanId,
            ]);


            return [
                'status' => 'done',
                'processed' => $processedNow,
                'offset' => $offset,
                'next_offset' => $offset + $limit,
            ];
        }

        // Finalize.
        $progress['status'] = 'done';
        $progress['finished_at'] = current_time('mysql', true);
        update_option(self::PROGRESS_OPTION, $progress, false);

        update_option(self::LAST_SCAN_OPTION, [
            'finished_at' => $progress['finished_at'],
            'started_at' => $progress['started_at'] ?? '',
            'total' => $total,
            'processed' => $newProcessed,
            'scan_id' => $scanId,
        ], false);

        return [
            'status' => 'done',
            'processed' => $processedNow,
            'finished' => true,
            'total_processed' => $newProcessed,
        ];
    }

    /**
     * @return string[]
     */
    public function defaultPostTypes(): array
    {
        $pts = get_post_types(['public' => true], 'names');
        $out = [];
        foreach ($pts as $pt) {
            if ($pt === 'attachment') {
                continue;
            }
            $out[] = (string) $pt;
        }
        return $out;
    }
}
