<?php

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

/**
 * SEO Fury Auto Redirect
 *
 * Automatically creates 301 redirects when a post/page URL slug changes.
 * Prevents redirect chains by updating existing redirects that point to the old URL.
 *
 * @since 0.1.9
 */
class SEO_Fury_Auto_Redirect {

    /**
     * Redirects database table name.
     *
     * @var string
     */
    private $redirects_table;

    /**
     * Stores old post data (slug + permalink) captured before the update.
     *
     * @var array Keyed by post ID.
     */
    private $old_post_data = array();

    /**
     * @param string $redirects_table Full table name including prefix.
     */
    public function __construct($redirects_table) {
        $this->redirects_table = $redirects_table;
        $this->init_hooks();
    }

    /**
     * Register WordPress hooks.
     */
    private function init_hooks() {
        // Capture the old slug BEFORE WordPress changes it.
        add_action('pre_post_update', array($this, 'capture_old_slug'), 10, 2);

        // Compare old vs new slug AFTER the change and create redirect if needed.
        add_action('post_updated', array($this, 'maybe_create_redirect'), 10, 3);
    }

    // -------------------------------------------------------------------------
    // Core logic
    // -------------------------------------------------------------------------

    /**
     * Fires just before a post is updated. Stores the old slug and permalink
     * so we can compare after the update completes.
     *
     * @param int   $post_id Post ID.
     * @param array $data    Array of unslashed post data.
     */
    public function capture_old_slug($post_id, $data) {
        if (!$this->is_enabled()) {
            return;
        }

        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }

        if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
            return;
        }

        if (!current_user_can('edit_post', $post_id)) {
            return;
        }

        $post = get_post($post_id);
        if (!$post) {
            return;
        }

        if (!$this->is_post_type_eligible($post->post_type)) {
            return;
        }

        // Only track published posts (unpublished posts have no public URL).
        if ($post->post_status !== 'publish') {
            return;
        }

        // Store old slug and the full relative permalink.
        $this->old_post_data[$post_id] = array(
            'slug'      => $post->post_name,
            'permalink' => $this->get_relative_url($post_id),
        );
    }

    /**
     * Fires after a post has been updated. Compares old and new slugs and
     * creates a 301 redirect when the slug has changed.
     *
     * @param int     $post_id     Post ID.
     * @param WP_Post $post_after  Post object after the update.
     * @param WP_Post $post_before Post object before the update.
     */
    public function maybe_create_redirect($post_id, $post_after, $post_before) {
        if (!$this->is_enabled()) {
            return;
        }

        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }

        if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
            return;
        }

        if (!current_user_can('edit_post', $post_id)) {
            return;
        }

        if (!$this->is_post_type_eligible($post_after->post_type)) {
            return;
        }

        // We only redirect published posts.
        if ($post_after->post_status !== 'publish') {
            return;
        }

        // We need the old data captured in pre_post_update.
        if (!isset($this->old_post_data[$post_id])) {
            return;
        }

        $old_slug      = $this->old_post_data[$post_id]['slug'];
        $old_permalink = $this->old_post_data[$post_id]['permalink'];
        $new_slug      = $post_after->post_name;
        $new_permalink = $this->get_relative_url($post_id);

        // Clean up stored data.
        unset($this->old_post_data[$post_id]);

        // Slug did not change -- nothing to do.
        if ($old_slug === $new_slug) {
            return;
        }

        // Sanity: don't redirect to the same URL (edge case with hierarchical slugs).
        if ($old_permalink === $new_permalink) {
            return;
        }

        // Ensure the redirects table exists before inserting.
        if (!$this->table_exists()) {
            return;
        }

        global $wpdb;

        // Normalize URLs.
        $source_url = $this->normalize_url($old_permalink);
        $target_url = $this->normalize_url($new_permalink);

        // ---- Chain prevention ----
        // If any existing redirects already point TO the old URL as their target,
        // update them to point to the new URL instead. This handles A->B->C chains.
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is not user input
        $chained = $wpdb->get_results($wpdb->prepare(
            "SELECT id, source_url FROM {$this->redirects_table} WHERE target_url = %s OR target_url = %s",
            $source_url,
            untrailingslashit($source_url)
        ));

        if (!empty($chained)) {
            foreach ($chained as $row) {
                // Don't create a self-redirect (source == new target).
                if ($this->normalize_url($row->source_url) === $target_url) {
                    // This redirect would become a self-redirect; delete it.
                    $wpdb->delete(
                        $this->redirects_table,
                        array('id' => $row->id),
                        array('%d')
                    );

                    $this->log_activity(sprintf(
                        /* translators: %s: source URL that was removed */
                        __('Auto-redirect removed (would be self-redirect): %s', 'seo-fury'),
                        $row->source_url
                    ));

                    continue;
                }

                $wpdb->update(
                    $this->redirects_table,
                    array('target_url' => $target_url),
                    array('id' => $row->id),
                    array('%s'),
                    array('%d')
                );

                $this->log_activity(sprintf(
                    /* translators: %1$s: source URL, %2$s: new target URL */
                    __('Auto-redirect chain updated: %1$s now points to %2$s', 'seo-fury'),
                    $row->source_url,
                    $target_url
                ));
            }
        }

        // ---- Duplicate check ----
        // If a redirect with this exact source already exists, update its target instead of inserting.
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $existing_id = $wpdb->get_var($wpdb->prepare(
            "SELECT id FROM {$this->redirects_table} WHERE source_url = %s LIMIT 1",
            $source_url
        ));

        if ($existing_id) {
            $wpdb->update(
                $this->redirects_table,
                array(
                    'target_url'    => $target_url,
                    'redirect_type' => 301,
                    'active'        => 1,
                ),
                array('id' => $existing_id),
                array('%s', '%d', '%d'),
                array('%d')
            );

            $this->log_activity(sprintf(
                /* translators: %1$s: source URL, %2$s: new target URL, %3$d: post ID, %4$s: old slug, %5$s: new slug */
                __('Auto-redirect updated: %1$s -> %2$s (post ID %3$d, slug changed from "%4$s" to "%5$s")', 'seo-fury'),
                $source_url,
                $target_url,
                $post_id,
                $old_slug,
                $new_slug
            ));

            return;
        }

        // ---- Insert new redirect ----
        $wpdb->insert(
            $this->redirects_table,
            array(
                'source_url'    => $source_url,
                'target_url'    => $target_url,
                'redirect_type' => 301,
                'hits'          => 0,
                'active'        => 1,
                'created'       => current_time('mysql'),
            ),
            array('%s', '%s', '%d', '%d', '%d', '%s')
        );

        $this->log_activity(sprintf(
            /* translators: %1$s: old URL, %2$s: new URL, %3$d: post ID, %4$s: old slug, %5$s: new slug */
            __('Auto-redirect created: %1$s -> %2$s (post ID %3$d, slug changed from "%4$s" to "%5$s")', 'seo-fury'),
            $source_url,
            $target_url,
            $post_id,
            $old_slug,
            $new_slug
        ));
    }

    // -------------------------------------------------------------------------
    // Settings registration (WordPress Settings API)
    // -------------------------------------------------------------------------

    /**
     * Register settings for the auto-redirect feature.
     * Should be called from admin_init (via the admin class).
     */
    public function register_settings() {
        register_setting('seo_fury_general_settings', 'seo_fury_auto_redirect_enabled', array(
            'type'              => 'boolean',
            'default'           => 1,
            'sanitize_callback' => array($this, 'sanitize_checkbox'),
        ));

        register_setting('seo_fury_general_settings', 'seo_fury_auto_redirect_post_types', array(
            'type'              => 'array',
            'default'           => array(),
            'sanitize_callback' => array($this, 'sanitize_post_types'),
        ));

        add_settings_section(
            'seo_fury_auto_redirect_section',
            __('Auto Redirects', 'seo-fury'),
            array($this, 'section_callback'),
            'seo_fury_general_settings'
        );

        add_settings_field(
            'seo_fury_auto_redirect_enabled',
            __('Enable Auto Redirects', 'seo-fury'),
            array($this, 'render_enabled_field'),
            'seo_fury_general_settings',
            'seo_fury_auto_redirect_section'
        );

        add_settings_field(
            'seo_fury_auto_redirect_post_types',
            __('Post Types', 'seo-fury'),
            array($this, 'render_post_types_field'),
            'seo_fury_general_settings',
            'seo_fury_auto_redirect_section'
        );
    }

    /**
     * Section description callback.
     */
    public function section_callback() {
        echo '<p class="description">';
        esc_html_e('Automatically create 301 redirects when a post or page URL slug is changed. This prevents broken links and preserves SEO value.', 'seo-fury');
        echo '</p>';
    }

    /**
     * Render the enable/disable checkbox.
     */
    public function render_enabled_field() {
        $value = get_option('seo_fury_auto_redirect_enabled', 1);
        ?>
        <label>
            <input type="checkbox"
                   name="seo_fury_auto_redirect_enabled"
                   value="1"
                   <?php checked(1, $value); ?> />
            <?php esc_html_e('Automatically create a 301 redirect when a URL slug changes', 'seo-fury'); ?>
        </label>
        <?php
    }

    /**
     * Render the post types multi-checkbox.
     */
    public function render_post_types_field() {
        $saved      = get_option('seo_fury_auto_redirect_post_types', array());
        $post_types = $this->get_public_post_types();

        if (empty($post_types)) {
            echo '<p class="description">';
            esc_html_e('No public post types found.', 'seo-fury');
            echo '</p>';
            return;
        }

        // When the saved value is empty, treat it as "all post types enabled" (default).
        $all_selected = empty($saved);

        echo '<fieldset>';
        foreach ($post_types as $pt) {
            $checked = $all_selected || in_array($pt->name, (array) $saved, true);
            ?>
            <label style="display: block; margin-bottom: 6px;">
                <input type="checkbox"
                       name="seo_fury_auto_redirect_post_types[]"
                       value="<?php echo esc_attr($pt->name); ?>"
                       <?php checked(true, $checked); ?> />
                <?php echo esc_html($pt->labels->singular_name); ?>
                <code style="font-size: 11px; color: var(--sf-gray-400, #999);">(<?php echo esc_html($pt->name); ?>)</code>
            </label>
            <?php
        }
        echo '</fieldset>';
        echo '<p class="description">';
        esc_html_e('Select which post types should get automatic redirects when their URL slug changes. If none are selected, all public post types are included.', 'seo-fury');
        echo '</p>';
    }

    /**
     * Render the settings block for embedding in the admin settings page.
     * This outputs a self-contained HTML block that can be placed inside an
     * existing <form> or as a standalone card on the General tab.
     */
    public function render_settings() {
        ?>
        <div class="seo-card" style="margin-top: 20px;">
            <h3 style="margin-top: 0;">
                <?php echo esc_html__('Auto Redirects', 'seo-fury'); ?>
            </h3>
            <p style="color: var(--sf-gray-500, #64748b); font-size: 13px; margin-bottom: 12px;">
                <?php esc_html_e('Automatically create 301 redirects when a post or page URL slug is changed. This prevents broken links and preserves SEO value.', 'seo-fury'); ?>
            </p>

            <table class="form-table" role="presentation">
                <tr>
                    <th scope="row"><?php esc_html_e('Enable Auto Redirects', 'seo-fury'); ?></th>
                    <td>
                        <?php $this->render_enabled_field(); ?>
                    </td>
                </tr>
                <tr>
                    <th scope="row"><?php esc_html_e('Post Types', 'seo-fury'); ?></th>
                    <td>
                        <?php $this->render_post_types_field(); ?>
                    </td>
                </tr>
            </table>

            <?php $this->render_recent_log(); ?>
        </div>
        <?php
    }

    // -------------------------------------------------------------------------
    // Activity log
    // -------------------------------------------------------------------------

    /**
     * Log an auto-redirect activity message.
     *
     * Stored as a limited-size option so it doesn't grow unbounded.
     *
     * @param string $message Human-readable log message.
     */
    private function log_activity($message) {
        $log = get_option('seo_fury_auto_redirect_log', array());

        // Prepend the new entry.
        array_unshift($log, array(
            'time'    => current_time('mysql'),
            'message' => $message,
        ));

        // Keep only the last 50 entries.
        $log = array_slice($log, 0, 50);

        update_option('seo_fury_auto_redirect_log', $log, false);
    }

    /**
     * Render the recent activity log in the settings area.
     */
    private function render_recent_log() {
        $log = get_option('seo_fury_auto_redirect_log', array());

        if (empty($log)) {
            return;
        }

        $recent = array_slice($log, 0, 10);
        ?>
        <div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--sf-gray-200, #e2e8f0);">
            <h4 style="margin-top: 0; margin-bottom: 8px;">
                <?php esc_html_e('Recent Auto-Redirect Activity', 'seo-fury'); ?>
            </h4>
            <ul style="margin: 0; padding: 0; list-style: none;">
                <?php foreach ($recent as $entry): ?>
                    <li style="padding: 4px 0; font-size: 13px; color: var(--sf-gray-700, #475569); border-bottom: 1px solid var(--sf-gray-200, #f1f5f9);">
                        <span style="color: var(--sf-gray-400, #94a3b8); font-size: 12px;">
                            <?php echo esc_html($entry['time']); ?>
                        </span>
                        &mdash;
                        <?php echo esc_html($entry['message']); ?>
                    </li>
                <?php endforeach; ?>
            </ul>
        </div>
        <?php
    }

    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    /**
     * Whether the auto-redirect feature is enabled.
     *
     * @return bool
     */
    private function is_enabled() {
        return (bool) get_option('seo_fury_auto_redirect_enabled', 1);
    }

    /**
     * Check if a post type is eligible for auto-redirects.
     *
     * @param string $post_type Post type slug.
     * @return bool
     */
    private function is_post_type_eligible($post_type) {
        $allowed = get_option('seo_fury_auto_redirect_post_types', array());

        // Empty array means "all public post types".
        if (empty($allowed)) {
            $public_types = get_post_types(array('public' => true), 'names');
            return in_array($post_type, $public_types, true);
        }

        return in_array($post_type, (array) $allowed, true);
    }

    /**
     * Get all public post types as objects.
     *
     * @return WP_Post_Type[]
     */
    private function get_public_post_types() {
        return get_post_types(array('public' => true), 'objects');
    }

    /**
     * Get the relative URL path for a post (e.g. /my-page/ or /blog/my-post/).
     *
     * @param int $post_id Post ID.
     * @return string Relative URL path.
     */
    private function get_relative_url($post_id) {
        $permalink = get_permalink($post_id);
        if (!$permalink) {
            return '';
        }

        $path = wp_parse_url($permalink, PHP_URL_PATH);
        return $path ? $path : '/';
    }

    /**
     * Normalize a URL path for storage.
     * Ensures leading slash and trailing slash for consistency with the redirects table.
     *
     * @param string $url URL path.
     * @return string Normalized path.
     */
    private function normalize_url($url) {
        $url = '/' . trim($url, '/');

        // Add trailing slash (WordPress default permalink style).
        if ($url !== '/') {
            $url = trailingslashit($url);
        }

        return $url;
    }

    /**
     * Check if the redirects table exists in the database.
     *
     * @return bool
     */
    private function table_exists() {
        global $wpdb;

        // Use the same transient that class-redirects.php uses.
        $cache_key = 'seo_fury_redirects_table_ok';
        if (get_transient($cache_key)) {
            return true;
        }

        $exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $this->redirects_table));
        if ($exists) {
            set_transient($cache_key, 1, DAY_IN_SECONDS);
            return true;
        }

        return false;
    }

    /**
     * Sanitize a checkbox value.
     *
     * @param mixed $input Input value.
     * @return int 1 or 0.
     */
    public function sanitize_checkbox($input) {
        return ($input == 1) ? 1 : 0;
    }

    /**
     * Sanitize the post types array.
     *
     * @param mixed $input Raw input.
     * @return array Sanitized array of post type slugs.
     */
    public function sanitize_post_types($input) {
        if (!is_array($input)) {
            return array();
        }

        return array_map('sanitize_key', $input);
    }
}
