<?php
declare(strict_types=1);

namespace Ekesto\CacheWarmup\Settings;

use Ekesto\CacheWarmup\Multilingual\AdapterFactory;
use Ekesto\CacheWarmup\WarmupRuntime;
use Ekesto\CacheWarmup\Settings;

/**
 * Snapshot storage and post-type classification derived from sitemap URLs.
 */
final class SitemapSnapshotService
{
    /**
     * Build a deterministic hash from configured sitemap URLs.
     *
     * @param array<int,string> $sitemaps Raw sitemap URL list.
     * @return string Stable hash of normalized sitemap URLs.
     */
    public static function hashSitemapUrls(array $sitemaps): string
    {
        $normalized = [];
        foreach ($sitemaps as $url) {
            $url = trim((string) $url);
            if ($url === '') {
                continue;
            }
            $url = esc_url_raw($url);
            if ($url === '') {
                continue;
            }
            $normalized[] = strtolower($url);
        }

        $normalized = array_values(array_unique($normalized));
        sort($normalized);
        return md5(wp_json_encode($normalized) ?: '[]');
    }

    /**
     * Mark current snapshot as potentially outdated without recomputing counts.
     */
    public static function markOutdated(): void
    {
        $snapshot = self::getSnapshot();
        $snapshot['warning'] = null;
        $snapshot['last_error'] = null;
        self::saveSnapshot($snapshot);
    }

    /**
     * Store freshness metadata from full warmup runtime resolution.
     *
     * @param array<int,string> $sitemaps Sitemaps used during full warmup.
     * @param int $urlCount Resolved URL count during full warmup.
     */
    public static function recordRuntimeMetadata(array $sitemaps, int $urlCount): void
    {
        $snapshot = self::getSnapshot();
        $snapshot['last_full_warm_hash'] = self::hashSitemapUrls($sitemaps);
        $snapshot['last_full_warm_at'] = gmdate('c');
        $snapshot['last_full_warm_url_count'] = max(0, $urlCount);
        self::saveSnapshot($snapshot);
    }

    /**
     * Read and sanitize the persisted sitemap snapshot option.
     *
     * @return array<string,mixed> Sanitized snapshot payload.
     */
    public static function getSnapshot(): array
    {
        $saved = get_option(Settings::SITEMAP_OPTION, []);
        $saved = is_array($saved) ? $saved : [];
        $snapshot = array_merge(self::defaultSnapshot(), $saved);

        $snapshot['sitemaps_hash'] = isset($snapshot['sitemaps_hash']) ? sanitize_text_field((string) $snapshot['sitemaps_hash']) : '';
        $snapshot['generated_at'] = self::sanitizeIsoTimestamp($snapshot['generated_at'] ?? null);
        $snapshot['warning'] = isset($snapshot['warning']) && is_string($snapshot['warning']) && $snapshot['warning'] !== ''
            ? sanitize_text_field($snapshot['warning'])
            : null;
        $snapshot['last_error'] = isset($snapshot['last_error']) && is_string($snapshot['last_error']) && $snapshot['last_error'] !== ''
            ? sanitize_text_field($snapshot['last_error'])
            : null;
        $snapshot['source'] = isset($snapshot['source']) ? sanitize_key((string) $snapshot['source']) : '';
        $snapshot['last_full_warm_hash'] = isset($snapshot['last_full_warm_hash']) ? sanitize_text_field((string) $snapshot['last_full_warm_hash']) : '';
        $snapshot['last_full_warm_at'] = self::sanitizeIsoTimestamp($snapshot['last_full_warm_at'] ?? null);
        $snapshot['last_full_warm_url_count'] = isset($snapshot['last_full_warm_url_count']) ? max(0, (int) $snapshot['last_full_warm_url_count']) : 0;

        $postTypes = isset($snapshot['post_types']) && is_array($snapshot['post_types']) ? $snapshot['post_types'] : [];
        $snapshot['post_types'] = [];
        foreach ($postTypes as $item) {
            if (!is_array($item)) {
                continue;
            }
            $name = isset($item['name']) ? sanitize_key((string) $item['name']) : '';
            if ($name === '') {
                continue;
            }
            $label = isset($item['label']) ? sanitize_text_field((string) $item['label']) : $name;
            $count = isset($item['count']) ? max(0, (int) $item['count']) : 0;
            $snapshot['post_types'][] = [
                'name' => $name,
                'label' => $label,
                'count' => $count,
            ];
        }

        return $snapshot;
    }

    /**
     * Persist sitemap snapshot data with autoload disabled.
     *
     * @param array<string,mixed> $snapshot Snapshot payload to store.
     */
    public static function saveSnapshot(array $snapshot): void
    {
        $exists = get_option(Settings::SITEMAP_OPTION, null);
        if ($exists === null) {
            add_option(Settings::SITEMAP_OPTION, $snapshot, '', 'no');
            return;
        }

        update_option(Settings::SITEMAP_OPTION, $snapshot, false);
    }

    /**
     * Return the default sitemap snapshot structure.
     *
     * @return array<string,mixed> Default snapshot payload.
     */
    public static function defaultSnapshot(): array
    {
        return [
            'sitemaps_hash' => '',
            'generated_at' => null,
            'post_types' => [],
            'warning' => null,
            'last_error' => null,
            'source' => '',
            'last_full_warm_hash' => '',
            'last_full_warm_at' => null,
            'last_full_warm_url_count' => 0,
        ];
    }

    /**
     * Build the REST payload for sitemap snapshot status and post-type counts.
     *
     * @param array<string,mixed> $snapshot Stored snapshot payload.
     * @param array<int,string> $sitemaps Configured sitemap URLs.
     * @param bool $hasSitemap Whether any sitemap URL is configured.
     * @return array<string,mixed> Endpoint response payload.
     */
    public static function buildEndpointPayload(array $snapshot, array $sitemaps, bool $hasSitemap): array
    {
        $currentHash = self::hashSitemapUrls($sitemaps);
        $generatedAt = (string) ($snapshot['generated_at'] ?? '');
        $isGenerated = ($generatedAt !== '');
        $isStale = $hasSitemap && (!$isGenerated || (string) ($snapshot['sitemaps_hash'] ?? '') !== $currentHash);

        $statusMessage = __('Sitemap data is up to date.', 'cache-autopilot');
        if (!$hasSitemap) {
            $statusMessage = __('No sitemap URL configured yet.', 'cache-autopilot');
        } elseif (!$isGenerated) {
            $statusMessage = __('No sitemap snapshot yet. Run a refresh to load detected post types.', 'cache-autopilot');
        } elseif ($isStale) {
            $statusMessage = __('Sitemap data may be outdated. Refresh to sync post types.', 'cache-autopilot');
        } elseif (
            !empty($snapshot['last_full_warm_at']) &&
            !empty($snapshot['last_full_warm_hash']) &&
            (string) $snapshot['last_full_warm_hash'] === $currentHash &&
            strcmp((string) $snapshot['last_full_warm_at'], $generatedAt) > 0
        ) {
            $statusMessage = __('Sitemap data is up to date. Full warmups are using fresh runtime sitemap data.', 'cache-autopilot');
        }

        $warning = null;
        if (!$hasSitemap) {
            $warning = __('No sitemap configured. Add sitemap URL(s) to detect available post types. Install an SEO plugin like Yoast SEO or Rank Math for automatic sitemap generation.', 'cache-autopilot');
        } elseif (!empty($snapshot['warning'])) {
            $warning = (string) $snapshot['warning'];
        }

        return [
            'post_types' => $snapshot['post_types'],
            'has_sitemap' => $hasSitemap,
            'warning' => $warning,
            'sitemap_urls' => $sitemaps,
            'is_stale' => $isStale,
            'generated_at' => $generatedAt !== '' ? $generatedAt : null,
            'snapshot_source' => (string) ($snapshot['source'] ?? ''),
            'last_error' => !empty($snapshot['last_error']) ? (string) $snapshot['last_error'] : null,
            'status_message' => $statusMessage,
            'last_full_warm_at' => !empty($snapshot['last_full_warm_at']) ? (string) $snapshot['last_full_warm_at'] : null,
        ];
    }

    /**
     * Classify sitemap URLs into post-type counts.
     *
     * When a multilingual adapter is active and resolves default language,
     * only posts in that language are counted. Unknown language stays fail-open.
     *
     * @param array<int,string> $urls Resolved sitemap URLs.
     * @return array<int,array{name:string,label:string,count:int}> Classified post-type counts.
     */
    public static function classifyPostTypesFromUrls(array $urls): array
    {
        $excludedPostTypes = ['attachment'];
        $typeCount = [];
        $languageAdapter = AdapterFactory::create();
        $defaultLanguage = $languageAdapter?->getDefaultLanguage();

        foreach ($urls as $url) {
            $postId = url_to_postid((string) $url);
            if ($postId <= 0) {
                continue;
            }

            $postType = get_post_type($postId);
            if (!$postType || in_array($postType, $excludedPostTypes, true)) {
                continue;
            }

            if ($languageAdapter !== null && is_string($defaultLanguage) && $defaultLanguage !== '') {
                $postLanguage = $languageAdapter->getUrlLanguage((string) $url);
                if (!is_string($postLanguage) || $postLanguage === '') {
                    $postLanguage = $languageAdapter->getPostLanguage($postId, $postType);
                }

                if (is_string($postLanguage) && $postLanguage !== '' && strtolower($postLanguage) !== strtolower($defaultLanguage)) {
                    continue;
                }
            }

            if (!isset($typeCount[$postType])) {
                $typeCount[$postType] = 0;
            }
            $typeCount[$postType]++;
        }

        $result = [];
        foreach ($typeCount as $postType => $count) {
            $typeObj = get_post_type_object($postType);
            $result[] = [
                'name' => $postType,
                'label' => $typeObj ? ($typeObj->label ?? $postType) : $postType,
                'count' => (int) $count,
            ];
        }

        usort($result, static fn(array $a, array $b): int => $b['count'] <=> $a['count']);
        return $result;
    }

    /**
     * Normalize timestamps to an ISO-8601 UTC string.
     *
     * @param mixed $value Raw timestamp input.
     * @return string|null Normalized timestamp or null when invalid.
     */
    private static function sanitizeIsoTimestamp($value): ?string
    {
        if (!is_string($value) || trim($value) === '') {
            return null;
        }

        $value = trim($value);
        $ts = strtotime($value);
        if ($ts === false) {
            return null;
        }

        return gmdate('c', $ts);
    }
}
