<?php
declare(strict_types=1);

namespace ProRank\SEO\Modules\Content;

use ProRank\SEO\Modules\BaseModule;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

if (!defined('ABSPATH')) {
    exit;
}

/**
 * SEO metadata revision history.
 */
class SEORevisionsModule extends BaseModule {

    private const HISTORY_META_KEY = '_prorank_seo_revision_history';
    private const MAX_REVISIONS = 50;

    private const TRACKED_META_KEYS = [
        '_prorank_seo_title',
        '_prorank_seo_description',
        '_prorank_seo_canonical_url',
        '_prorank_meta_robots',
        '_prorank_seo_noindex',
        '_prorank_seo_robots_noindex',
        '_prorank_robots_noindex',
        '_prorank_seo_focus_keywords',
        '_prorank_seo_focus_keyword',
        '_prorank_focus_keyword',
        '_prorank_og_title',
        '_prorank_og_description',
        '_prorank_og_image_id',
        '_prorank_twitter_title',
        '_prorank_twitter_description',
        '_prorank_twitter_image_id',
        '_prorank_twitter_card_type',
        '_prorank_schema_data',
        '_prorank_sitemap_priority_override',
        '_prorank_is_pillar_content',
    ];

    /** @var array<int,bool> */
    private array $pending_posts = [];

    /** @var array<int,array<string,mixed>> */
    private array $pre_change_snapshots = [];

    public function __construct() {
        $this->slug = 'seo_revisions';
        $this->name = 'SEO Revisions';
        $this->description = 'Track and restore ProRank SEO metadata changes.';
        $this->parent_slug = 'content';
    }

    public function init_hooks(): void {
        add_action('rest_api_init', [$this, 'register_rest_routes']);
        add_filter('update_post_metadata', [$this, 'capture_pre_change_snapshot'], 10, 5);
        add_filter('add_post_metadata', [$this, 'capture_pre_change_snapshot'], 10, 5);
        add_filter('delete_post_metadata', [$this, 'capture_pre_change_snapshot'], 10, 5);
        add_action('updated_post_meta', [$this, 'maybe_queue_revision'], 10, 4);
        add_action('added_post_meta', [$this, 'maybe_queue_revision'], 10, 4);
        add_action('deleted_post_meta', [$this, 'maybe_queue_revision'], 10, 4);
        add_action('shutdown', [$this, 'flush_pending_revisions'], 20);
    }

    public function register_rest_routes(): void {
        register_rest_route('prorank-seo/v1', '/seo-revisions/(?P<post_id>\d+)', [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_history_response'],
                'permission_callback' => [$this, 'can_edit_post'],
            ],
        ]);

        register_rest_route('prorank-seo/v1', '/seo-revisions/(?P<post_id>\d+)/restore', [
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [$this, 'restore_revision_response'],
                'permission_callback' => [$this, 'can_edit_post'],
                'args' => [
                    'revision_id' => [
                        'required' => true,
                        'type' => 'string',
                        'sanitize_callback' => 'sanitize_text_field',
                    ],
                ],
            ],
        ]);
    }

    public function can_edit_post(WP_REST_Request $request): bool {
        $post_id = absint($request->get_param('post_id'));
        return $post_id > 0 && current_user_can('edit_post', $post_id);
    }

    public function capture_pre_change_snapshot($check, int $post_id, string $meta_key, $meta_value = null, $prev_value = null) {
        if (!$this->should_track_meta_change($post_id, $meta_key)) {
            return $check;
        }

        if (!isset($this->pre_change_snapshots[$post_id])) {
            $this->pre_change_snapshots[$post_id] = $this->build_snapshot($post_id);
        }

        return $check;
    }

    public function maybe_queue_revision($meta_id, int $post_id, string $meta_key, $meta_value): void {
        if (!$this->should_track_meta_change($post_id, $meta_key)) {
            return;
        }

        $this->pending_posts[$post_id] = true;
    }

    public function flush_pending_revisions(): void {
        if (empty($this->pending_posts)) {
            return;
        }

        foreach (array_keys($this->pending_posts) as $post_id) {
            $post_id = (int) $post_id;
            $this->record_revision($post_id, $this->pre_change_snapshots[$post_id] ?? null);
        }

        $this->pending_posts = [];
        $this->pre_change_snapshots = [];
    }

    public function get_history_response(WP_REST_Request $request): WP_REST_Response {
        $post_id = absint($request->get_param('post_id'));

        return new WP_REST_Response([
            'success' => true,
            'data' => [
                'post_id' => $post_id,
                'current' => $this->build_snapshot($post_id),
                'history' => $this->get_history($post_id),
            ],
        ]);
    }

    public function restore_revision_response(WP_REST_Request $request): WP_REST_Response {
        $post_id = absint($request->get_param('post_id'));
        $revision_id = (string) $request->get_param('revision_id');
        $history = $this->get_history($post_id);

        foreach ($history as $revision) {
            if ((string) ($revision['id'] ?? '') !== $revision_id) {
                continue;
            }

            $before = $this->build_snapshot($post_id);
            $this->apply_snapshot($post_id, (array) ($revision['meta'] ?? []));
            $this->append_history($post_id, $before, 'pre_restore');
            $this->append_history($post_id, $this->build_snapshot($post_id), 'restore');

            return new WP_REST_Response([
                'success' => true,
                'data' => [
                    'post_id' => $post_id,
                    'restored_revision_id' => $revision_id,
                    'current' => $this->build_snapshot($post_id),
                ],
            ]);
        }

        return new WP_REST_Response([
            'success' => false,
            'message' => __('SEO revision not found.', 'prorank-seo'),
        ], 404);
    }

    private function record_revision(int $post_id, ?array $before_snapshot = null): void {
        if (!get_post($post_id)) {
            return;
        }

        $current_snapshot = $this->build_snapshot($post_id);
        $snapshot = $before_snapshot;
        if (!is_array($snapshot) || ($snapshot['hash'] ?? '') === ($current_snapshot['hash'] ?? '')) {
            $snapshot = $current_snapshot;
        }

        $history = $this->get_history($post_id);
        $last = $history[0] ?? null;
        if (is_array($last) && ($last['hash'] ?? '') === $snapshot['hash']) {
            return;
        }

        $this->append_history($post_id, $snapshot, 'change');
    }

    private function append_history(int $post_id, array $snapshot, string $source): void {
        $history = $this->get_history($post_id);
        array_unshift($history, [
            'id' => md5((string) $post_id . '|' . microtime(true) . '|' . wp_rand()),
            'created_at' => current_time('mysql'),
            'user_id' => get_current_user_id(),
            'source' => $source,
            'hash' => (string) ($snapshot['hash'] ?? ''),
            'meta' => (array) ($snapshot['meta'] ?? []),
        ]);

        $history = array_slice($history, 0, self::MAX_REVISIONS);
        update_post_meta($post_id, self::HISTORY_META_KEY, $history);
    }

    private function should_track_meta_change(int $post_id, string $meta_key): bool {
        if ($post_id <= 0 || !in_array($meta_key, self::TRACKED_META_KEYS, true)) {
            return false;
        }

        return !wp_is_post_revision($post_id) && !wp_is_post_autosave($post_id);
    }

    private function build_snapshot(int $post_id): array {
        $meta = [];
        foreach (self::TRACKED_META_KEYS as $key) {
            $meta[$key] = get_post_meta($post_id, $key, true);
        }

        $hash = md5(wp_json_encode($meta));

        return [
            'hash' => $hash,
            'meta' => $meta,
        ];
    }

    private function apply_snapshot(int $post_id, array $meta): void {
        foreach (self::TRACKED_META_KEYS as $key) {
            if (!array_key_exists($key, $meta) || $meta[$key] === '' || $meta[$key] === null || $meta[$key] === []) {
                delete_post_meta($post_id, $key);
                continue;
            }

            update_post_meta($post_id, $key, $meta[$key]);
        }
    }

    private function get_history(int $post_id): array {
        $history = get_post_meta($post_id, self::HISTORY_META_KEY, true);
        return is_array($history) ? array_values($history) : [];
    }
}
