<?php

namespace WPSN\Modules\IndexNow;

use WPSN\Core\Logger;
use WPSN\Core\Settings;

defined('ABSPATH') || exit;

final class IndexNowJobs
{
    public const JOB_SUBMIT = 'indexnow_submit';
    public const JOB_PING_SITEMAP = 'indexnow_ping_sitemap';

    private Settings $settings;
    private Logger $logger;

    private IndexNowSecretsRepository $secrets;
    private IndexNowClient $client;
    private IndexNowLogRepository $log;

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

        $this->secrets = new IndexNowSecretsRepository();
        $this->client = new IndexNowClient($logger);
        $this->log = new IndexNowLogRepository();
    }

    /**
     * Queue handler: indexnow_submit
     *
     * Payload:
     * - url_list: array<string>
     * - reason: string (optional)
     *
     * @param mixed $unused
     * @param array<string,mixed> $payload
     * @param array<string,mixed> $jobRow
     * @return array<string,mixed>
     */
    public function handleSubmit($unused, array $payload, array $jobRow): array
    {
        // If the feature is disabled, silently drop jobs.
        if (!$this->settings->get('indexnow.enabled', false)) {
            return ['status' => 'done'];
        }

        if (get_option('blog_public') === '0') {
            return ['status' => 'done'];
        }

        $key = $this->secrets->getKey();
        if ($key === '') {
            return ['status' => 'failed', 'error' => 'IndexNow key is not set.'];
        }

        $urls = $payload['url_list'] ?? [];
        if (!is_array($urls) || empty($urls)) {
            return ['status' => 'done'];
        }

        $clean = [];
        foreach ($urls as $u) {
            $u = is_string($u) ? trim($u) : '';
            if ($u === '') {
                continue;
            }
            $u = esc_url_raw($u);
            if ($u === '') {
                continue;
            }
            $clean[] = $u;
        }

        $clean = array_values(array_unique($clean));
        if (empty($clean)) {
            return ['status' => 'done'];
        }

        $batchSize = (int) $this->settings->get('indexnow.batch_size', 100);
        $batchSize = max(1, min(10000, $batchSize));
        if (count($clean) > $batchSize) {
            $clean = array_slice($clean, 0, $batchSize);
        }

        $endpoint = (string) $this->settings->get('indexnow.endpoint', 'https://api.indexnow.org/indexnow');
        if ($endpoint === '') {
            $endpoint = 'https://api.indexnow.org/indexnow';
        }

        $host = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        $keyLoc = home_url('/' . rawurlencode($key) . '.txt');

        $body = [
            'host' => $host,
            'key' => $key,
            'keyLocation' => $keyLoc,
            'urlList' => $clean,
        ];

        $resp = $this->client->submit($endpoint, $body);

        if (!empty($resp['ok'])) {
            $this->log->insert([
                'status' => 'ok',
                'response_code' => (int) ($resp['status'] ?? 0),
                'url_count' => count($clean),
                'endpoint' => $endpoint,
                'payload' => wp_json_encode(['url_list' => $this->sampleUrls($clean)]),
                'error' => '',
            ]);

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

        $status = (int) ($resp['status'] ?? 0);
        $err = (string) ($resp['error'] ?? 'IndexNow error');

        // Soft backoff for rate limits.
        if ($status === 429 || $status === 503) {
            $attempts = isset($jobRow['attempts']) ? (int) $jobRow['attempts'] : 1;
            $attempts = max(1, min(8, $attempts));

            $base = (int) $this->settings->get('indexnow.base_backoff', 30);
            $base = max(5, min(3600, $base));

            $max = (int) $this->settings->get('indexnow.max_backoff', 600);
            $max = max($base, min(7200, $max));

            $retryIn = (int) ($base * pow(2, $attempts - 1));
            $retryIn = max($base, min($max, $retryIn));

            $this->log->insert([
                'status' => 'retry',
                'response_code' => $status,
                'url_count' => count($clean),
                'endpoint' => $endpoint,
                'payload' => wp_json_encode(['url_list' => $this->sampleUrls($clean)]),
                'error' => 'Rate limited (HTTP ' . $status . '): ' . $err,
            ]);

            return [
                'status' => 'retry',
                'retry_in' => $retryIn,
                'error' => 'Rate limited (HTTP ' . $status . '): ' . $err,
            ];
        }

        $this->log->insert([
            'status' => 'failed',
            'response_code' => $status,
            'url_count' => count($clean),
            'endpoint' => $endpoint,
            'payload' => wp_json_encode(['url_list' => $this->sampleUrls($clean)]),
            'error' => $err,
        ]);

        return ['status' => 'failed', 'error' => $err];
    }

    /**
     * @param array<int,string> $urls
     * @return array<int,string>
     */
    private function sampleUrls(array $urls): array
    {
        if (count($urls) <= 10) {
            return $urls;
        }
        return array_slice($urls, 0, 10);
    }


    /**
     * Queue handler: indexnow_ping_sitemap
     *
     * Payload:
     * - provider: 'bing'|'google'
     * - sitemap_url: string
     * - reason: string (optional)
     *
     * @param mixed $unused
     * @param array<string,mixed> $payload
     * @param array<string,mixed> $jobRow
     * @return array<string,mixed>
     */
    public function handleSitemapPing($unused, array $payload, array $jobRow): array
    {
        $enabled = (bool) $this->settings->get('indexnow.enabled', false);
        if (!$enabled) {
            return ['status' => 'done', 'message' => 'IndexNow disabled'];
        }

        $pingEnabled = (bool) $this->settings->get('indexnow.sitemap_pings.enabled', false);
        if (!$pingEnabled) {
            return ['status' => 'done', 'message' => 'Sitemap pings disabled'];
        }

        // Respect WordPress privacy setting.
        if (get_option('blog_public') === '0') {
            return ['status' => 'done', 'message' => 'blog_public=0'];
        }

        $provider = isset($payload['provider']) ? strtolower((string) $payload['provider']) : '';
        $sitemapUrl = isset($payload['sitemap_url']) ? trim((string) $payload['sitemap_url']) : '';
        $reason = isset($payload['reason']) ? (string) $payload['reason'] : '';

        if ($provider !== 'bing' && $provider !== 'google') {
            $this->log->insertLog('failed', 0, 1, '', ['kind' => 'sitemap_ping', 'provider' => $provider, 'sitemap_url' => $sitemapUrl, 'reason' => $reason], 'Invalid provider');
            return ['status' => 'failed', 'error' => 'Invalid provider'];
        }

        if ($sitemapUrl === '' || !wp_http_validate_url($sitemapUrl)) {
            $this->log->insertLog('failed', 0, 1, '', ['kind' => 'sitemap_ping', 'provider' => $provider, 'sitemap_url' => $sitemapUrl, 'reason' => $reason], 'Invalid sitemap URL');
            return ['status' => 'failed', 'error' => 'Invalid sitemap URL'];
        }

        $pingUrl = $this->buildPingUrl($provider, $sitemapUrl);
        if ($pingUrl === '') {
            $this->log->insertLog('failed', 0, 1, '', ['kind' => 'sitemap_ping', 'provider' => $provider, 'sitemap_url' => $sitemapUrl, 'reason' => $reason], 'Cannot build ping URL');
            return ['status' => 'failed', 'error' => 'Cannot build ping URL'];
        }

        $resp = $this->client->ping($pingUrl);
        $status = (int) ($resp['status'] ?? 0);
        $err = (string) ($resp['error'] ?? '');

        if (!empty($resp['ok'])) {
            $this->log->insertLog('ok', $status, 1, $pingUrl, ['kind' => 'sitemap_ping', 'provider' => $provider, 'sitemap_url' => $sitemapUrl, 'reason' => $reason], '');
            return ['status' => 'done', 'message' => 'Ping OK'];
        }

        // Soft retry on rate limits / temporary backend errors.
        if ($status === 429 || $status === 503) {
            $attempts = isset($jobRow['attempts']) ? (int) $jobRow['attempts'] : 1;
            $attempts = max(1, min(8, $attempts));

            $base = (int) $this->settings->get('indexnow.base_backoff', 30);
            $base = max(5, min(3600, $base));

            $max = (int) $this->settings->get('indexnow.max_backoff', 600);
            $max = max($base, min(7200, $max));

            $retryIn = (int) ($base * pow(2, $attempts - 1));
            $retryIn = max($base, min($max, $retryIn));

            $this->log->insertLog(
                'retry',
                $status,
                1,
                $pingUrl,
                ['kind' => 'sitemap_ping', 'provider' => $provider, 'sitemap_url' => $sitemapUrl, 'reason' => $reason, 'attempts' => $attempts],
                $err !== '' ? $err : ('HTTP ' . $status)
            );

            return [
                'status' => 'retry',
                'retry_in' => $retryIn,
                'error' => $err !== '' ? $err : ('HTTP ' . $status),
            ];
        }

        $this->log->insertLog('failed', $status, 1, $pingUrl, ['kind' => 'sitemap_ping', 'provider' => $provider, 'sitemap_url' => $sitemapUrl, 'reason' => $reason], $err !== '' ? $err : ('HTTP ' . $status));
        return ['status' => 'failed', 'error' => $err !== '' ? $err : ('HTTP ' . $status)];
    }

    private function buildPingUrl(string $provider, string $sitemapUrl): string
    {
        $sitemapUrl = trim($sitemapUrl);
        if ($sitemapUrl === '') {
            return '';
        }

        // Standard lightweight ping endpoints.
        if ($provider === 'google') {
            return 'https://www.google.com/ping?sitemap=' . rawurlencode($sitemapUrl);
        }
        if ($provider === 'bing') {
            return 'https://www.bing.com/ping?sitemap=' . rawurlencode($sitemapUrl);
        }

        return '';
    }
}
