<?php
defined('ABSPATH') || exit;

class FixLinks_Redirects {

    public static function serve_redirect(): void {
        $uri      = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '';
        $raw_path = (string) wp_parse_url( $uri, PHP_URL_PATH );
        // Strip WordPress base path (handles subdirectory installs e.g. /mysite/)
        $base = rtrim( (string) wp_parse_url( home_url(), PHP_URL_PATH ), '/' );
        if ($base && str_starts_with($raw_path, $base)) {
            $raw_path = substr($raw_path, strlen($base));
        }
        $path = '/' . ltrim(rtrim($raw_path, '/'), '/');
        if ($path === '/') return;

        $map = get_transient('fixlinks_redirect_map');
        if (false === $map) {
            global $wpdb;
            $rows = $wpdb->get_results("SELECT from_path,to_url FROM {$wpdb->prefix}fixlinks_redirects");
            $map  = [];
            foreach ($rows as $row) $map[$row->from_path] = $row->to_url;
            set_transient('fixlinks_redirect_map', $map, HOUR_IN_SECONDS);
        }

        if (!isset($map[$path])) return;

        $hits         = get_option('fixlinks_pending_hits', []);
        $hits[$path]  = ($hits[$path] ?? 0) + 1;
        update_option('fixlinks_pending_hits', $hits, false);

        wp_redirect(esc_url_raw($map[$path]), 301); exit;
    }

    public static function flush_hit_counts(): void {
        global $wpdb;
        $pending = get_option('fixlinks_pending_hits', []);
        if (empty($pending)) return;
        foreach ($pending as $path => $count) {
            $wpdb->query($wpdb->prepare(
                "UPDATE {$wpdb->prefix}fixlinks_redirects SET hit_count=hit_count+%d WHERE from_path=%s",
                $count, $path
            ));
        }
        delete_option('fixlinks_pending_hits');
    }

    public static function ajax_apply(): void {
        check_ajax_referer('fixlinks_nonce', 'nonce');
        if (!current_user_can('manage_options')) wp_die('Forbidden');
        $link_id  = intval($_POST['link_id']  ?? 0);
        $from_url = sanitize_url(wp_unslash($_POST['from_url'] ?? ''));
        $to_url   = sanitize_url(wp_unslash($_POST['to_url']   ?? ''));
        if (!$link_id || !$from_url || !$to_url) {
            wp_send_json_error(__('Missing parameters.', 'fixlinks-ai'));
        }
        try {
            if (self::add_redirect($from_url, $to_url, $link_id)) {
                FixLinks_DB::mark_applied($link_id);
                // Look up the source page for this specific broken link
                global $wpdb;
                $source_page = $wpdb->get_var($wpdb->prepare(
                    "SELECT source_page FROM {$wpdb->prefix}fixlinks_broken WHERE id=%d",
                    $link_id
                ));
                $fixed = self::fix_in_content($from_url, $to_url, $source_page ?: null);
                wp_send_json_success([
                    'message'      => __('Redirect applied.', 'fixlinks-ai'),
                    'fixed_posts'  => $fixed,
                ]);
            } else {
                wp_send_json_error(__('Could not save redirect.', 'fixlinks-ai'));
            }
        } catch (\Throwable $e) {
            error_log('[FixLinks] ajax_apply error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
            wp_send_json_error(__('Something went wrong. Please try again.', 'fixlinks-ai'));
        }
    }

    public static function ajax_rollback(): void {
        check_ajax_referer('fixlinks_nonce', 'nonce');
        if (!current_user_can('manage_options')) wp_die('Forbidden');
        $from_url = sanitize_url(wp_unslash($_POST['from_url'] ?? ''));
        if (!$from_url) {
            wp_send_json_error(__('Missing URL.', 'fixlinks-ai'));
        }
        try {
            global $wpdb;
            // Look up the to_url BEFORE deleting so we can revert post content
            $from_path = self::to_path($from_url);
            $to_url    = $wpdb->get_var($wpdb->prepare(
                "SELECT to_url FROM {$wpdb->prefix}fixlinks_redirects WHERE from_path=%s",
                $from_path
            ));
            self::remove_redirect($from_url);
            // Revert post content: swap to_url → from_url (only on source page)
            $reverted = 0;
            if ($to_url) {
                $source_page = $wpdb->get_var($wpdb->prepare(
                    "SELECT source_page FROM {$wpdb->prefix}fixlinks_broken WHERE broken_url=%s LIMIT 1",
                    $from_url
                ));
                $reverted = self::fix_in_content($to_url, $from_url, $source_page ?: null);
            }
            // Reset broken link status back to pending
            $wpdb->update(
                $wpdb->prefix . 'fixlinks_broken',
                ['status' => 'pending', 'applied_at' => null],
                ['broken_url' => $from_url],
                ['%s', null],
                ['%s']
            );
            wp_send_json_success([
                'message'  => __('Redirect removed.', 'fixlinks-ai'),
                'reverted' => $reverted,
            ]);
        } catch (\Throwable $e) {
            error_log('[FixLinks] ajax_rollback error: ' . $e->getMessage());
            wp_send_json_error(__('Something went wrong. Please try again.', 'fixlinks-ai'));
        }
    }

    public static function add_redirect(string $from, string $to, ?int $link_id = null): bool {
        global $wpdb;
        $from_path = self::to_path($from);
        $result    = $wpdb->replace($wpdb->prefix . 'fixlinks_redirects', [
            'from_path' => $from_path,
            'to_url'    => $to,
            'link_id'   => $link_id,
        ]);
        if ($result === false) {
            error_log('[FixLinks] add_redirect DB error: ' . $wpdb->last_error);
        }
        delete_transient('fixlinks_redirect_map');
        // .htaccess write only on Apache (not Nginx/LiteSpeed) and only outside AJAX context
        if (!defined('DOING_AJAX') || !DOING_AJAX) {
            if (function_exists('get_home_path')) {
                $htaccess = get_home_path() . '.htaccess';
                global $wp_filesystem;
                if ( ! $wp_filesystem ) {
                    require_once ABSPATH . 'wp-admin/includes/file.php';
                    WP_Filesystem();
                }
                if ( $wp_filesystem && $wp_filesystem->is_writable( $htaccess ) ) {
                    self::write_htaccess_rule( $from_path, $to, $htaccess );
                }
            }
        }
        return $result !== false;
    }

    public static function remove_redirect(string $from): void {
        global $wpdb;
        $from_path = self::to_path($from);
        $wpdb->delete($wpdb->prefix . 'fixlinks_redirects', ['from_path' => $from_path], ['%s']);
        delete_transient('fixlinks_redirect_map');
        // .htaccess write only on Apache and only outside AJAX context
        if (!defined('DOING_AJAX') || !DOING_AJAX) {
            if (function_exists('get_home_path')) {
                $htaccess = get_home_path() . '.htaccess';
                global $wp_filesystem;
                if ( ! $wp_filesystem ) {
                    require_once ABSPATH . 'wp-admin/includes/file.php';
                    WP_Filesystem();
                }
                if ( $wp_filesystem && file_exists( $htaccess ) && $wp_filesystem->is_writable( $htaccess ) ) {
                    $c = file_get_contents( $htaccess );
                    $c = preg_replace('/\nRedirect 301 ' . preg_quote($from_path, '/') . ' .+/', '', $c);
                    file_put_contents($htaccess, $c);
                }
            }
        }
    }

    /**
     * Replace all occurrences of $from_url in post_content with $to_url.
     * Returns number of posts updated.
     */
    public static function fix_in_content(string $from, string $to, ?string $source_page = null): int {
        global $wpdb;
        $count = 0;

        // If we know the source page, find the post by its URL
        $post_id = null;
        if ($source_page) {
            $post_id = url_to_postid($source_page);
        }

        if ($post_id) {
            // Only fix the specific post
            $post = $wpdb->get_row($wpdb->prepare(
                "SELECT ID, post_content FROM {$wpdb->posts} WHERE ID=%d",
                $post_id
            ));
            $posts = $post ? [$post] : [];
        } else {
            // Fallback: search all published posts
            $posts = $wpdb->get_results($wpdb->prepare(
                "SELECT ID, post_content FROM {$wpdb->posts}
                 WHERE post_status IN ('publish','draft','private')
                 AND post_content LIKE %s",
                '%' . $wpdb->esc_like($from) . '%'
            ));
        }

        foreach ($posts as $post) {
            $new = str_replace($from, $to, $post->post_content);
            if ($new !== $post->post_content) {
                $wpdb->update($wpdb->posts, ['post_content' => $new], ['ID' => $post->ID]);
                clean_post_cache($post->ID);
                // Purge LiteSpeed cache for this post if available
                if (function_exists('do_action')) {
                    do_action('litespeed_purge_post', $post->ID);
                }
                $count++;
                error_log("[FixLinks] fix_in_content: post {$post->ID} — replaced '{$from}' with '{$to}'.");
            }
        }
        return $count;
    }

    public static function get_all(): array {
        global $wpdb;
        return $wpdb->get_results("SELECT * FROM {$wpdb->prefix}fixlinks_redirects ORDER BY created_at DESC") ?: [];
    }

    private static function to_path(string $url): string {
        $path = (string) wp_parse_url( $url, PHP_URL_PATH );
        $path = '/' . ltrim( rtrim( $path, '/' ), '/' );
        // If this is an external URL and the path is just '/', use the hostname as path
        // to create a unique, meaningful redirect entry
        if ( $path === '/' ) {
            $host = (string) wp_parse_url( $url, PHP_URL_HOST );
            $path = '/' . ltrim($host, 'www.');
        }
        return $path;
    }

    private static function write_htaccess_rule(string $fp, string $to, string $file): void {
        $c = (string)file_get_contents($file);
        if (str_contains($c, " {$fp} ")) return;
        $rule = "\nRedirect 301 {$fp} {$to}";
        if (str_contains($c, '# BEGIN FixLinks AI')) {
            $c = str_replace('# END FixLinks AI', $rule . "\n# END FixLinks AI", $c);
        } else {
            $c .= "\n# BEGIN FixLinks AI{$rule}\n# END FixLinks AI\n";
        }
        file_put_contents($file, $c);
    }
}