<?php
/**
 * SearchFIT Webhook Handler Class
 *
 * Handles incoming webhook requests for article submissions.
 *
 * @package SearchFIT
 * @since   1.0.0
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Class SearchFIT_Webhook
 *
 * @since 1.0.0
 */
class SearchFIT_Webhook {

    /**
     * REST API namespace.
     *
     * @var string
     */
    const API_NAMESPACE = 'searchfit/v1';

    /**
     * Instance of this class.
     *
     * @var SearchFIT_Webhook
     */
    private static $instance = null;

    /**
     * Get instance of this class.
     *
     * @return SearchFIT_Webhook
     */
    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor.
     */
    private function __construct() {
        add_action( 'rest_api_init', array( $this, 'register_routes' ) );
    }

    /**
     * Register REST API routes.
     */
    public function register_routes() {
        // Create article.
        register_rest_route(
            self::API_NAMESPACE,
            '/articles',
            array(
                'methods'             => 'POST',
                'callback'            => array( $this, 'handle_article_submission' ),
                'permission_callback' => array( $this, 'verify_api_key' ),
                'args'                => $this->get_article_args(),
            )
        );

        // List articles.
        register_rest_route(
            self::API_NAMESPACE,
            '/articles',
            array(
                'methods'             => 'GET',
                'callback'            => array( $this, 'handle_article_list' ),
                'permission_callback' => array( $this, 'verify_api_key' ),
                'args'                => $this->get_article_list_args(),
            )
        );

        // Get single article.
        register_rest_route(
            self::API_NAMESPACE,
            '/articles/(?P<post_id>\d+)',
            array(
                'methods'             => 'GET',
                'callback'            => array( $this, 'handle_article_get' ),
                'permission_callback' => array( $this, 'verify_api_key' ),
            )
        );

        // Update article.
        register_rest_route(
            self::API_NAMESPACE,
            '/articles/(?P<post_id>\d+)',
            array(
                'methods'             => array( 'PUT', 'PATCH' ),
                'callback'            => array( $this, 'handle_article_update' ),
                'permission_callback' => array( $this, 'verify_api_key' ),
                'args'                => $this->get_article_update_args(),
            )
        );

        // Upload media.
        register_rest_route(
            self::API_NAMESPACE,
            '/media',
            array(
                'methods'             => 'POST',
                'callback'            => array( $this, 'handle_media_upload' ),
                'permission_callback' => array( $this, 'verify_api_key' ),
                'args'                => $this->get_media_args(),
            )
        );

        // Health check.
        register_rest_route(
            self::API_NAMESPACE,
            '/health',
            array(
                'methods'             => 'GET',
                'callback'            => array( $this, 'health_check' ),
                'permission_callback' => array( $this, 'verify_api_key' ),
            )
        );
    }

    /**
     * Get article endpoint arguments.
     *
     * @return array
     */
    private function get_article_args() {
        return array(
            'title'              => array(
                'required'          => true,
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_text_field',
                'description'       => __( 'The article title.', 'searchfit' ),
            ),
            'content'            => array(
                'required'          => true,
                'type'              => 'string',
                'sanitize_callback' => 'wp_kses_post',
                'description'       => __( 'The article content (HTML allowed).', 'searchfit' ),
            ),
            'status'             => array(
                'required'          => false,
                'type'              => 'string',
                'default'           => 'draft',
                'enum'              => array( 'draft', 'publish', 'pending' ),
                'sanitize_callback' => 'sanitize_text_field',
                'description'       => __( 'The post status.', 'searchfit' ),
            ),
            'excerpt'            => array(
                'required'          => false,
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_textarea_field',
                'description'       => __( 'The article excerpt.', 'searchfit' ),
            ),
            'author_id'          => array(
                'required'          => false,
                'type'              => 'integer',
                'sanitize_callback' => 'absint',
                'description'       => __( 'The author user ID.', 'searchfit' ),
            ),
            'categories'         => array(
                'required'    => false,
                'type'        => 'array',
                'items'       => array( 'type' => 'string' ),
                'description' => __( 'Array of category names or IDs.', 'searchfit' ),
            ),
            'tags'               => array(
                'required'    => false,
                'type'        => 'array',
                'items'       => array( 'type' => 'string' ),
                'description' => __( 'Array of tag names.', 'searchfit' ),
            ),
            'featured_image_url' => array(
                'required'          => false,
                'type'              => 'string',
                'sanitize_callback' => 'esc_url_raw',
                'description'       => __( 'URL of the featured image.', 'searchfit' ),
            ),
            'featured_image'     => array(
                'required'    => false,
                'type'        => 'object',
                'description' => __( 'Featured image with metadata.', 'searchfit' ),
            ),
            'images'             => array(
                'required'    => false,
                'type'        => 'array',
                'description' => __( 'Array of content images.', 'searchfit' ),
            ),
            'meta'               => array(
                'required'    => false,
                'type'        => 'object',
                'description' => __( 'Custom meta fields.', 'searchfit' ),
            ),
        );
    }

    /**
     * Get article list endpoint arguments.
     *
     * @return array
     */
    private function get_article_list_args() {
        return array(
            'per_page' => array(
                'required'          => false,
                'type'              => 'integer',
                'default'           => 10,
                'minimum'           => 1,
                'maximum'           => 100,
                'sanitize_callback' => 'absint',
                'description'       => __( 'Number of articles per page (max 100).', 'searchfit' ),
            ),
            'page'     => array(
                'required'          => false,
                'type'              => 'integer',
                'default'           => 1,
                'minimum'           => 1,
                'sanitize_callback' => 'absint',
                'description'       => __( 'Page number.', 'searchfit' ),
            ),
            'status'   => array(
                'required'          => false,
                'type'              => 'string',
                'default'           => 'any',
                'enum'              => array( 'draft', 'publish', 'pending', 'any' ),
                'sanitize_callback' => 'sanitize_text_field',
                'description'       => __( 'Filter by post status.', 'searchfit' ),
            ),
            'author'   => array(
                'required'          => false,
                'type'              => 'integer',
                'sanitize_callback' => 'absint',
                'description'       => __( 'Filter by author ID.', 'searchfit' ),
            ),
            'search'   => array(
                'required'          => false,
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_text_field',
                'description'       => __( 'Search term.', 'searchfit' ),
            ),
            'orderby'  => array(
                'required'          => false,
                'type'              => 'string',
                'default'           => 'date',
                'enum'              => array( 'date', 'modified', 'title', 'author' ),
                'sanitize_callback' => 'sanitize_text_field',
                'description'       => __( 'Sort by field.', 'searchfit' ),
            ),
            'order'    => array(
                'required'          => false,
                'type'              => 'string',
                'default'           => 'DESC',
                'enum'              => array( 'ASC', 'DESC' ),
                'sanitize_callback' => 'sanitize_text_field',
                'description'       => __( 'Sort order.', 'searchfit' ),
            ),
        );
    }

    /**
     * Get article update endpoint arguments.
     *
     * @return array
     */
    private function get_article_update_args() {
        return array(
            'post_id'            => array(
                'required'          => true,
                'type'              => 'integer',
                'sanitize_callback' => 'absint',
                'description'       => __( 'The post ID to update.', 'searchfit' ),
            ),
            'title'              => array(
                'required'          => false,
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_text_field',
                'description'       => __( 'The article title.', 'searchfit' ),
            ),
            'content'            => array(
                'required'          => false,
                'type'              => 'string',
                'sanitize_callback' => 'wp_kses_post',
                'description'       => __( 'The article content (HTML allowed).', 'searchfit' ),
            ),
            'status'             => array(
                'required'          => false,
                'type'              => 'string',
                'enum'              => array( 'draft', 'publish', 'pending' ),
                'sanitize_callback' => 'sanitize_text_field',
                'description'       => __( 'The post status (preserves existing if not provided).', 'searchfit' ),
            ),
            'excerpt'            => array(
                'required'          => false,
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_textarea_field',
                'description'       => __( 'The article excerpt.', 'searchfit' ),
            ),
            'author_id'          => array(
                'required'          => false,
                'type'              => 'integer',
                'sanitize_callback' => 'absint',
                'description'       => __( 'The author user ID.', 'searchfit' ),
            ),
            'categories'         => array(
                'required'    => false,
                'type'        => 'array',
                'items'       => array( 'type' => 'string' ),
                'description' => __( 'Array of category names or IDs.', 'searchfit' ),
            ),
            'tags'               => array(
                'required'    => false,
                'type'        => 'array',
                'items'       => array( 'type' => 'string' ),
                'description' => __( 'Array of tag names.', 'searchfit' ),
            ),
            'featured_image_url' => array(
                'required'          => false,
                'type'              => 'string',
                'sanitize_callback' => 'esc_url_raw',
                'description'       => __( 'URL of the featured image.', 'searchfit' ),
            ),
            'featured_image'     => array(
                'required'    => false,
                'type'        => 'object',
                'description' => __( 'Featured image with metadata.', 'searchfit' ),
            ),
            'images'             => array(
                'required'    => false,
                'type'        => 'array',
                'description' => __( 'Array of content images.', 'searchfit' ),
            ),
            'meta'               => array(
                'required'    => false,
                'type'        => 'object',
                'description' => __( 'Custom meta fields.', 'searchfit' ),
            ),
        );
    }

    /**
     * Get media endpoint arguments.
     *
     * @return array
     */
    private function get_media_args() {
        return array(
            'file_url'    => array(
                'required'          => true,
                'type'              => 'string',
                'sanitize_callback' => 'esc_url_raw',
                'description'       => __( 'URL of the file to upload.', 'searchfit' ),
            ),
            'title'       => array(
                'required'          => false,
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_text_field',
                'description'       => __( 'Media title.', 'searchfit' ),
            ),
            'alt'         => array(
                'required'          => false,
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_text_field',
                'description'       => __( 'Image alt text.', 'searchfit' ),
            ),
            'caption'     => array(
                'required'          => false,
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_textarea_field',
                'description'       => __( 'Media caption.', 'searchfit' ),
            ),
            'description' => array(
                'required'          => false,
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_textarea_field',
                'description'       => __( 'Media description.', 'searchfit' ),
            ),
            'author_id'   => array(
                'required'          => false,
                'type'              => 'integer',
                'sanitize_callback' => 'absint',
                'description'       => __( 'Author user ID for the media.', 'searchfit' ),
            ),
        );
    }

    /**
     * Verify API key from request header.
     *
     * @param WP_REST_Request $request The request object.
     * @return bool|WP_Error
     */
    public function verify_api_key( $request ) {
        $api_key = $request->get_header( 'X-SearchFIT-API-Key' );

        // Also check Authorization header with Bearer token.
        if ( empty( $api_key ) ) {
            $auth_header = $request->get_header( 'Authorization' );
            if ( $auth_header && 0 === strpos( $auth_header, 'Bearer ' ) ) {
                $api_key = substr( $auth_header, 7 );
            }
        }

        if ( empty( $api_key ) ) {
            return new WP_Error(
                'missing_api_key',
                __( 'API key is required. Include it in the X-SearchFIT-API-Key header.', 'searchfit' ),
                array( 'status' => 401 )
            );
        }

        $stored_api_key = get_option( 'searchfit_webhook_api_key', '' );

        if ( empty( $stored_api_key ) ) {
            return new WP_Error(
                'api_key_not_configured',
                __( 'API key has not been configured on this site.', 'searchfit' ),
                array( 'status' => 500 )
            );
        }

        if ( ! hash_equals( $stored_api_key, $api_key ) ) {
            return new WP_Error(
                'invalid_api_key',
                __( 'Invalid API key.', 'searchfit' ),
                array( 'status' => 401 )
            );
        }

        return true;
    }

    /**
     * Handle article list request.
     *
     * @param WP_REST_Request $request The request object.
     * @return WP_REST_Response
     */
    public function handle_article_list( $request ) {
        $per_page = $request->get_param( 'per_page' );
        $page     = $request->get_param( 'page' );
        $status   = $request->get_param( 'status' );
        $author   = $request->get_param( 'author' );
        $search   = $request->get_param( 'search' );
        $orderby  = $request->get_param( 'orderby' );
        $order    = $request->get_param( 'order' );

        // Build query args.
        $args = array(
            'post_type'      => 'post',
            'posts_per_page' => $per_page,
            'paged'          => $page,
            'post_status'    => $status,
            'orderby'        => $orderby,
            'order'          => $order,
        );

        if ( ! empty( $author ) ) {
            $args['author'] = $author;
        }

        if ( ! empty( $search ) ) {
            $args['s'] = $search;
        }

        // Execute query.
        $query = new WP_Query( $args );

        // Format posts.
        $articles = array();
        foreach ( $query->posts as $post ) {
            $articles[] = $this->format_article_response( $post );
        }

        return new WP_REST_Response(
            array(
                'success'    => true,
                'articles'   => $articles,
                'pagination' => array(
                    'total'       => $query->found_posts,
                    'total_pages' => $query->max_num_pages,
                    'current'     => $page,
                    'per_page'    => $per_page,
                ),
            ),
            200
        );
    }

    /**
     * Handle get single article request.
     *
     * @param WP_REST_Request $request The request object.
     * @return WP_REST_Response|WP_Error
     */
    public function handle_article_get( $request ) {
        $post_id = $request->get_param( 'post_id' );

        // Check if post exists.
        $post = get_post( $post_id );
        if ( ! $post || 'post' !== $post->post_type ) {
            return new WP_Error(
                'post_not_found',
                __( 'Post not found.', 'searchfit' ),
                array( 'status' => 404 )
            );
        }

        return new WP_REST_Response(
            array(
                'success' => true,
                'article' => $this->format_article_response( $post ),
            ),
            200
        );
    }

    /**
     * Format article for API response.
     *
     * @param WP_Post $post The post object.
     * @return array
     */
    private function format_article_response( $post ) {
        // Get categories.
        $categories = get_the_category( $post->ID );
        $category_names = array();
        foreach ( $categories as $cat ) {
            $category_names[] = $cat->name;
        }

        // Get tags.
        $tags = get_the_tags( $post->ID );
        $tag_names = array();
        if ( $tags ) {
            foreach ( $tags as $tag ) {
                $tag_names[] = $tag->name;
            }
        }

        // Get featured image.
        $featured_image = null;
        if ( has_post_thumbnail( $post->ID ) ) {
            $attachment_id = get_post_thumbnail_id( $post->ID );
            $featured_image = array(
                'attachment_id' => $attachment_id,
                'url'           => wp_get_attachment_url( $attachment_id ),
                'alt'           => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
            );
        }

        // Get custom meta.
        $all_meta = get_post_meta( $post->ID );
        $custom_meta = array();
        foreach ( $all_meta as $key => $values ) {
            // Only include SearchFIT meta fields.
            if ( 0 === strpos( $key, 'searchfit_' ) || 0 === strpos( $key, '_searchfit_' ) ) {
                $clean_key = str_replace( array( 'searchfit_', '_searchfit_' ), '', $key );
                $custom_meta[ $clean_key ] = is_array( $values ) && count( $values ) === 1 ? $values[0] : $values;
            }
        }

        return array(
            'id'             => $post->ID,
            'title'          => $post->post_title,
            'content'        => apply_filters( 'searchfit_the_content', $post->post_content ),
            'excerpt'        => $post->post_excerpt,
            'status'         => $post->post_status,
            'author_id'      => $post->post_author,
            'author_name'    => get_the_author_meta( 'display_name', $post->post_author ),
            'categories'     => $category_names,
            'tags'           => $tag_names,
            'featured_image' => $featured_image,
            'url'            => get_permalink( $post->ID ),
            'edit_url'       => get_edit_post_link( $post->ID, 'raw' ),
            'created_at'     => $post->post_date,
            'updated_at'     => $post->post_modified,
            'meta'           => $custom_meta,
        );
    }

    /**
     * Handle article submission webhook.
     *
     * @param WP_REST_Request $request The request object.
     * @return WP_REST_Response|WP_Error
     */
    public function handle_article_submission( $request ) {
        $title   = $request->get_param( 'title' );
        $content = $request->get_param( 'content' );
        $status  = $request->get_param( 'status' ) ? $request->get_param( 'status' ) : 'draft';
        $excerpt = $request->get_param( 'excerpt' );

        // Get author ID.
        $author_id = $request->get_param( 'author_id' );
        if ( empty( $author_id ) ) {
            $author_id = $this->get_default_author_id();
        } else {
            // Validate the author exists.
            $author = get_user_by( 'id', $author_id );
            if ( ! $author ) {
                return new WP_Error(
                    'invalid_author',
                    __( 'Invalid author ID specified.', 'searchfit' ),
                    array( 'status' => 400 )
                );
            }
        }

        // Track uploaded images for response.
        $uploaded_images = array();

        // Process content images first (replace placeholders).
        $images = $request->get_param( 'images' );
        if ( ! empty( $images ) && is_array( $images ) ) {
            $content = $this->process_content_images( $content, $images, 0, $uploaded_images );
        }

        // Prepare post data.
        $post_data = array(
            'post_title'   => $title,
            'post_content' => $content,
            'post_status'  => $status,
            'post_type'    => 'post',
            'post_author'  => $author_id,
        );

        if ( ! empty( $excerpt ) ) {
            $post_data['post_excerpt'] = $excerpt;
        }

        // Insert the post.
        $post_id = wp_insert_post( $post_data, true );

        if ( is_wp_error( $post_id ) ) {
            return new WP_Error(
                'post_creation_failed',
                $post_id->get_error_message(),
                array( 'status' => 500 )
            );
        }

        // Re-process content images now that we have post_id (to attach them).
        if ( ! empty( $images ) && is_array( $images ) ) {
            $uploaded_images = array();
            $updated_content = $this->process_content_images(
                $request->get_param( 'content' ),
                $images,
                $post_id,
                $uploaded_images
            );
            wp_update_post(
                array(
                    'ID'           => $post_id,
                    'post_content' => $updated_content,
                )
            );
        }

        // Handle categories.
        $categories = $request->get_param( 'categories' );
        if ( ! empty( $categories ) && is_array( $categories ) ) {
            $category_ids = $this->process_categories( $categories );
            if ( ! empty( $category_ids ) ) {
                wp_set_post_categories( $post_id, $category_ids );
            }
        }

        // Handle tags.
        $tags = $request->get_param( 'tags' );
        if ( ! empty( $tags ) && is_array( $tags ) ) {
            wp_set_post_tags( $post_id, $tags );
        }

        // Handle featured image.
        $featured_image_result = $this->handle_featured_image( $post_id, $request );

        // Handle custom meta.
        $meta = $request->get_param( 'meta' );
        if ( ! empty( $meta ) && is_array( $meta ) ) {
            foreach ( $meta as $key => $value ) {
                // Support both prefixed and unprefixed meta keys.
                $meta_key = 0 === strpos( $key, '_' ) ? $key : 'searchfit_' . sanitize_key( $key );
                update_post_meta( $post_id, $meta_key, $value );
            }
        }

        // Mark post as created via SearchFIT webhook.
        update_post_meta( $post_id, '_searchfit_webhook_created', true );
        update_post_meta( $post_id, '_searchfit_webhook_created_at', current_time( 'mysql' ) );

        // Store image count.
        $total_images = count( $uploaded_images ) + ( $featured_image_result ? 1 : 0 );
        update_post_meta( $post_id, '_searchfit_images_count', $total_images );

        // Log the creation.
        $this->log_webhook_activity(
            'article_created',
            array(
                'post_id'      => $post_id,
                'title'        => $title,
                'status'       => $status,
                'author_id'    => $author_id,
                'images_count' => $total_images,
            )
        );

        // Get the created post.
        $post = get_post( $post_id );

        return new WP_REST_Response(
            array(
                'success'    => true,
                'message'    => __( 'Article created successfully.', 'searchfit' ),
                'post_id'    => $post_id,
                'post_url'   => get_permalink( $post_id ),
                'edit_url'   => get_edit_post_link( $post_id, 'raw' ),
                'status'     => $post->post_status,
                'author_id'  => $author_id,
                'created_at' => $post->post_date,
                'images'     => array(
                    'featured'       => $featured_image_result,
                    'content_images' => $uploaded_images,
                    'total_count'    => $total_images,
                ),
            ),
            201
        );
    }

    /**
     * Handle article update webhook.
     *
     * @param WP_REST_Request $request The request object.
     * @return WP_REST_Response|WP_Error
     */
    public function handle_article_update( $request ) {
        $post_id = $request->get_param( 'post_id' );

        // Check if post exists.
        $existing_post = get_post( $post_id );
        if ( ! $existing_post || 'post' !== $existing_post->post_type ) {
            return new WP_Error(
                'post_not_found',
                __( 'Post not found.', 'searchfit' ),
                array( 'status' => 404 )
            );
        }

        // Track what was changed.
        $changes = array();
        $post_data = array( 'ID' => $post_id );

        // Update title if provided.
        $title = $request->get_param( 'title' );
        if ( ! empty( $title ) ) {
            $post_data['post_title'] = $title;
            $changes[] = 'title';
        }

        // Update content if provided.
        $content = $request->get_param( 'content' );
        $updated_content = null;
        if ( ! empty( $content ) ) {
            // Track uploaded images for response.
            $uploaded_images = array();

            // Process content images first (replace placeholders).
            $images = $request->get_param( 'images' );
            if ( ! empty( $images ) && is_array( $images ) ) {
                $content = $this->process_content_images( $content, $images, $post_id, $uploaded_images );
            }

            $post_data['post_content'] = $content;
            $updated_content = $content;
            $changes[] = 'content';
        }

        // Update status if provided.
        $status = $request->get_param( 'status' );
        if ( ! empty( $status ) ) {
            $post_data['post_status'] = $status;
            $changes[] = 'status';
        }

        // Update excerpt if provided.
        $excerpt = $request->get_param( 'excerpt' );
        if ( ! empty( $excerpt ) ) {
            $post_data['post_excerpt'] = $excerpt;
            $changes[] = 'excerpt';
        }

        // Update author if provided.
        $author_id = $request->get_param( 'author_id' );
        if ( ! empty( $author_id ) ) {
            // Validate the author exists.
            $author = get_user_by( 'id', $author_id );
            if ( ! $author ) {
                return new WP_Error(
                    'invalid_author',
                    __( 'Invalid author ID specified.', 'searchfit' ),
                    array( 'status' => 400 )
                );
            }
            $post_data['post_author'] = $author_id;
            $changes[] = 'author';
        }

        // Update the post.
        $result = wp_update_post( $post_data, true );

        if ( is_wp_error( $result ) ) {
            return new WP_Error(
                'post_update_failed',
                $result->get_error_message(),
                array( 'status' => 500 )
            );
        }

        // Handle categories if provided.
        $categories = $request->get_param( 'categories' );
        if ( ! empty( $categories ) && is_array( $categories ) ) {
            $category_ids = $this->process_categories( $categories );
            if ( ! empty( $category_ids ) ) {
                wp_set_post_categories( $post_id, $category_ids );
                $changes[] = 'categories';
            }
        }

        // Handle tags if provided.
        $tags = $request->get_param( 'tags' );
        if ( ! empty( $tags ) && is_array( $tags ) ) {
            wp_set_post_tags( $post_id, $tags );
            $changes[] = 'tags';
        }

        // Handle featured image if provided.
        $featured_image_result = null;
        if ( $request->get_param( 'featured_image_url' ) || $request->get_param( 'featured_image' ) ) {
            $featured_image_result = $this->handle_featured_image( $post_id, $request );
            if ( $featured_image_result ) {
                $changes[] = 'featured_image';
            }
        }

        // Handle custom meta if provided.
        $meta = $request->get_param( 'meta' );
        if ( ! empty( $meta ) && is_array( $meta ) ) {
            foreach ( $meta as $key => $value ) {
                // Support both prefixed and unprefixed meta keys.
                $meta_key = 0 === strpos( $key, '_' ) ? $key : 'searchfit_' . sanitize_key( $key );
                update_post_meta( $post_id, $meta_key, $value );
            }
            $changes[] = 'meta';
        }

        // Update modification timestamp.
        update_post_meta( $post_id, '_searchfit_webhook_updated_at', current_time( 'mysql' ) );
        update_post_meta( $post_id, '_searchfit_webhook_update_count', (int) get_post_meta( $post_id, '_searchfit_webhook_update_count', true ) + 1 );

        // Log the update.
        $this->log_webhook_activity(
            'article_updated',
            array(
                'post_id' => $post_id,
                'changes' => $changes,
                'status'  => get_post_status( $post_id ),
            )
        );

        // Get the updated post.
        $post = get_post( $post_id );

        return new WP_REST_Response(
            array(
                'success'    => true,
                'message'    => __( 'Article updated successfully.', 'searchfit' ),
                'post_id'    => $post_id,
                'post_url'   => get_permalink( $post_id ),
                'edit_url'   => get_edit_post_link( $post_id, 'raw' ),
                'status'     => $post->post_status,
                'author_id'  => $post->post_author,
                'updated_at' => $post->post_modified,
                'changes'    => $changes,
            ),
            200
        );
    }

    /**
     * Handle media upload webhook.
     *
     * @param WP_REST_Request $request The request object.
     * @return WP_REST_Response|WP_Error
     */
    public function handle_media_upload( $request ) {
        require_once ABSPATH . 'wp-admin/includes/media.php';
        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';

        $file_url = $request->get_param( 'file_url' );
        $title    = $request->get_param( 'title' );
        $alt      = $request->get_param( 'alt' );
        $caption  = $request->get_param( 'caption' );
        $desc     = $request->get_param( 'description' );

        // Get author ID.
        $author_id = $request->get_param( 'author_id' );
        if ( empty( $author_id ) ) {
            $author_id = $this->get_default_author_id();
        } else {
            // Validate the author exists.
            $author = get_user_by( 'id', $author_id );
            if ( ! $author ) {
                return new WP_Error(
                    'invalid_author',
                    __( 'Invalid author ID specified.', 'searchfit' ),
                    array( 'status' => 400 )
                );
            }
        }

        // Download the file.
        $tmp = download_url( $file_url );

        if ( is_wp_error( $tmp ) ) {
            return new WP_Error(
                'download_failed',
                sprintf(
                    // translators: %s: error message.
                    __( 'Failed to download file: %s', 'searchfit' ),
                    $tmp->get_error_message()
                ),
                array( 'status' => 400 )
            );
        }

        // Get file info.
        $filename = basename( wp_parse_url( $file_url, PHP_URL_PATH ) );

        // If no extension, try to detect from file.
        if ( ! pathinfo( $filename, PATHINFO_EXTENSION ) ) {
            $filetype = wp_check_filetype( $tmp );
            if ( $filetype['ext'] ) {
                $filename .= '.' . $filetype['ext'];
            } else {
                $filename .= '.jpg';
            }
        }

        $file_array = array(
            'name'     => $filename,
            'tmp_name' => $tmp,
        );

        // Prepare attachment data.
        $attachment_data = array(
            'post_author' => $author_id,
        );

        if ( ! empty( $title ) ) {
            $attachment_data['post_title'] = sanitize_text_field( $title );
        }

        if ( ! empty( $caption ) ) {
            $attachment_data['post_excerpt'] = sanitize_textarea_field( $caption );
        }

        if ( ! empty( $desc ) ) {
            $attachment_data['post_content'] = sanitize_textarea_field( $desc );
        }

        // Upload the file.
        $attachment_id = media_handle_sideload( $file_array, 0, null, $attachment_data );

        // Clean up temp file.
        if ( file_exists( $tmp ) ) {
            wp_delete_file( $tmp );
        }

        if ( is_wp_error( $attachment_id ) ) {
            return new WP_Error(
                'upload_failed',
                sprintf(
                    // translators: %s: error message.
                    __( 'Failed to upload file: %s', 'searchfit' ),
                    $attachment_id->get_error_message()
                ),
                array( 'status' => 500 )
            );
        }

        // Set alt text if provided.
        if ( ! empty( $alt ) ) {
            update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $alt ) );
        }

        // Mark as uploaded via SearchFIT.
        update_post_meta( $attachment_id, '_searchfit_webhook_uploaded', true );
        update_post_meta( $attachment_id, '_searchfit_webhook_uploaded_at', current_time( 'mysql' ) );

        // Get attachment info.
        $attachment = get_post( $attachment_id );
        $metadata   = wp_get_attachment_metadata( $attachment_id );

        // Log the upload.
        $this->log_webhook_activity(
            'media_uploaded',
            array(
                'attachment_id' => $attachment_id,
                'file_url'      => $file_url,
                'author_id'     => $author_id,
                'mime_type'     => $attachment->post_mime_type,
            )
        );

        return new WP_REST_Response(
            array(
                'success'       => true,
                'message'       => __( 'Media uploaded successfully.', 'searchfit' ),
                'attachment_id' => $attachment_id,
                'url'           => wp_get_attachment_url( $attachment_id ),
                'file_path'     => get_attached_file( $attachment_id ),
                'mime_type'     => $attachment->post_mime_type,
                'title'         => $attachment->post_title,
                'alt'           => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
                'caption'       => $attachment->post_excerpt,
                'description'   => $attachment->post_content,
                'author_id'     => $author_id,
                'uploaded_at'   => $attachment->post_date,
                'metadata'      => $metadata,
            ),
            201
        );
    }

    /**
     * Handle featured image upload.
     *
     * @param int             $post_id The post ID.
     * @param WP_REST_Request $request The request object.
     * @return array|false
     */
    private function handle_featured_image( $post_id, $request ) {
        $featured_image     = $request->get_param( 'featured_image' );
        $featured_image_url = $request->get_param( 'featured_image_url' );

        // Handle featured_image object with attachment_id.
        if ( ! empty( $featured_image ) && is_array( $featured_image ) ) {
            if ( ! empty( $featured_image['attachment_id'] ) ) {
                // Use existing attachment ID.
                $attachment_id = absint( $featured_image['attachment_id'] );
                if ( wp_attachment_is_image( $attachment_id ) ) {
                    set_post_thumbnail( $post_id, $attachment_id );
                    return array(
                        'attachment_id' => $attachment_id,
                        'url'           => wp_get_attachment_url( $attachment_id ),
                    );
                }
            } elseif ( ! empty( $featured_image['url'] ) ) {
                // Download and upload image from URL.
                return $this->upload_image_with_metadata( $post_id, $featured_image, true );
            }
        } elseif ( ! empty( $featured_image_url ) ) {
            // Handle simple URL string.
            return $this->upload_image_with_metadata( $post_id, array( 'url' => $featured_image_url ), true );
        }

        return false;
    }

    /**
     * Upload an image with metadata.
     *
     * @param int   $post_id      The post ID to attach to.
     * @param array $image_data   Image data with url, alt, caption, title, description.
     * @param bool  $set_featured Whether to set as featured image.
     * @return array|false Array with attachment info on success, false on failure.
     */
    private function upload_image_with_metadata( $post_id, $image_data, $set_featured = false ) {
        require_once ABSPATH . 'wp-admin/includes/media.php';
        require_once ABSPATH . 'wp-admin/includes/file.php';
        require_once ABSPATH . 'wp-admin/includes/image.php';

        $url = isset( $image_data['url'] ) ? $image_data['url'] : '';

        if ( empty( $url ) ) {
            return false;
        }

        // Download the image.
        $tmp = download_url( $url );

        if ( is_wp_error( $tmp ) ) {
            return false;
        }

        // Get file info.
        $filename = basename( wp_parse_url( $url, PHP_URL_PATH ) );

        // If no extension, default to jpg.
        if ( ! pathinfo( $filename, PATHINFO_EXTENSION ) ) {
            $filename .= '.jpg';
        }

        $file_array = array(
            'name'     => $filename,
            'tmp_name' => $tmp,
        );

        // Prepare attachment data.
        $attachment_data = array();

        if ( ! empty( $image_data['title'] ) ) {
            $attachment_data['post_title'] = sanitize_text_field( $image_data['title'] );
        }

        if ( ! empty( $image_data['caption'] ) ) {
            $attachment_data['post_excerpt'] = sanitize_textarea_field( $image_data['caption'] );
        }

        if ( ! empty( $image_data['description'] ) ) {
            $attachment_data['post_content'] = sanitize_textarea_field( $image_data['description'] );
        }

        // Upload the image.
        $attachment_id = media_handle_sideload( $file_array, $post_id, null, $attachment_data );

        // Clean up temp file.
        if ( file_exists( $tmp ) ) {
            wp_delete_file( $tmp );
        }

        if ( is_wp_error( $attachment_id ) ) {
            return false;
        }

        // Set alt text.
        if ( ! empty( $image_data['alt'] ) ) {
            update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $image_data['alt'] ) );
        }

        // Set as featured image if requested.
        if ( $set_featured ) {
            set_post_thumbnail( $post_id, $attachment_id );
        }

        return array(
            'attachment_id' => $attachment_id,
            'url'           => wp_get_attachment_url( $attachment_id ),
            'alt'           => isset( $image_data['alt'] ) ? $image_data['alt'] : '',
            'caption'       => isset( $image_data['caption'] ) ? $image_data['caption'] : '',
            'title'         => isset( $image_data['title'] ) ? $image_data['title'] : '',
        );
    }

    /**
     * Process content images and replace placeholders.
     *
     * @param string $content         The post content.
     * @param array  $images          Array of image data.
     * @param int    $post_id         The post ID (0 if not yet created).
     * @param array  $uploaded_images Reference to track uploaded images.
     * @return string Modified content with image HTML.
     */
    private function process_content_images( $content, $images, $post_id, &$uploaded_images ) {
        foreach ( $images as $image ) {
            if ( empty( $image['placeholder'] ) || empty( $image['url'] ) ) {
                continue;
            }

            $placeholder = $image['placeholder'];

            // Upload the image.
            $result = $this->upload_image_with_metadata( $post_id, $image, false );

            if ( ! $result ) {
                // If upload failed, remove placeholder.
                $content = str_replace( $placeholder, '', $content );
                continue;
            }

            $uploaded_images[] = $result;

            // Build image HTML.
            $img_html = $this->build_image_html( $result, $image );

            // Replace placeholder with image HTML.
            $content = str_replace( $placeholder, $img_html, $content );
        }

        return $content;
    }

    /**
     * Build image HTML with optional figure/figcaption.
     *
     * @param array $result     Upload result with attachment info.
     * @param array $image_data Original image data.
     * @return string Image HTML.
     */
    private function build_image_html( $result, $image_data ) {
        $attachment_id = $result['attachment_id'];
        $alt           = esc_attr( $result['alt'] );
        $caption       = isset( $image_data['caption'] ) ? $image_data['caption'] : '';
        $link          = isset( $image_data['link'] ) ? esc_url( $image_data['link'] ) : '';
        $align         = isset( $image_data['align'] ) ? $image_data['align'] : 'none';
        $size          = isset( $image_data['size'] ) ? $image_data['size'] : 'large';

        // Get image at specified size.
        $img_src    = wp_get_attachment_image_src( $attachment_id, $size );
        $img_url    = $img_src ? $img_src[0] : $result['url'];
        $img_width  = $img_src ? $img_src[1] : '';
        $img_height = $img_src ? $img_src[2] : '';

        // Build img tag.
        $img_attrs = array(
            'src'   => esc_url( $img_url ),
            'alt'   => $alt,
            'class' => 'wp-image-' . $attachment_id,
        );

        if ( $img_width ) {
            $img_attrs['width'] = $img_width;
        }
        if ( $img_height ) {
            $img_attrs['height'] = $img_height;
        }

        $img_tag = '<img';
        foreach ( $img_attrs as $attr => $value ) {
            $img_tag .= ' ' . $attr . '="' . esc_attr( $value ) . '"';
        }
        $img_tag .= ' />';

        // Wrap in link if provided.
        if ( $link ) {
            $img_tag = '<a href="' . $link . '">' . $img_tag . '</a>';
        }

        // Build block HTML.
        $align_class = 'none' !== $align ? ' align' . $align : '';

        $html  = '<!-- wp:image {"id":' . $attachment_id . ',"sizeSlug":"' . esc_attr( $size ) . '"} -->';
        $html .= '<figure class="wp-block-image size-' . esc_attr( $size ) . $align_class . '">';
        $html .= $img_tag;

        if ( ! empty( $caption ) ) {
            $html .= '<figcaption class="wp-element-caption">' . wp_kses_post( $caption ) . '</figcaption>';
        }

        $html .= '</figure>';
        $html .= '<!-- /wp:image -->';

        return $html;
    }

    /**
     * Health check endpoint.
     *
     * @param WP_REST_Request $request The request object.
     * @return WP_REST_Response
     */
    public function health_check( $request ) {
        return new WP_REST_Response(
            array(
                'success' => true,
                'message' => __( 'SearchFIT webhook is operational.', 'searchfit' ),
                'version' => SEARCHFIT_VERSION,
                'site'    => get_bloginfo( 'name' ),
                'time'    => current_time( 'c' ),
            ),
            200
        );
    }

    /**
     * Get default author ID for webhook-created posts.
     *
     * @return int
     */
    private function get_default_author_id() {
        // Check if a default author is set in options.
        $default_author = get_option( 'searchfit_default_author', 0 );
        
        if ( ! empty( $default_author ) ) {
            $user = get_user_by( 'id', $default_author );
            if ( $user ) {
                return $default_author;
            }
        }

        // Fall back to first administrator.
        $admins = get_users(
            array(
                'role'    => 'administrator',
                'number'  => 1,
                'orderby' => 'ID',
                'order'   => 'ASC',
            )
        );

        if ( ! empty( $admins ) ) {
            return $admins[0]->ID;
        }

        return 1;
    }

    /**
     * Process category names/IDs and return category IDs.
     *
     * @param array $categories Array of category names or IDs.
     * @return array Category IDs.
     */
    private function process_categories( $categories ) {
        $category_ids = array();

        foreach ( $categories as $category ) {
            if ( is_numeric( $category ) ) {
                $term = get_term( (int) $category, 'category' );
                if ( $term && ! is_wp_error( $term ) ) {
                    $category_ids[] = (int) $category;
                }
            } else {
                $term = get_term_by( 'name', $category, 'category' );
                if ( $term ) {
                    $category_ids[] = $term->term_id;
                } else {
                    $new_term = wp_insert_term( $category, 'category' );
                    if ( ! is_wp_error( $new_term ) ) {
                        $category_ids[] = $new_term['term_id'];
                    }
                }
            }
        }

        return $category_ids;
    }

    /**
     * Log webhook activity.
     *
     * @param string $action The action being logged.
     * @param array  $data   Additional data to log.
     */
    private function log_webhook_activity( $action, $data = array() ) {
        $log = get_option( 'searchfit_webhook_log', array() );

        // Keep only the last 100 entries.
        if ( count( $log ) >= 100 ) {
            $log = array_slice( $log, -99 );
        }

        $log[] = array(
            'action'    => $action,
            'data'      => $data,
            'timestamp' => current_time( 'mysql' ),
            'ip'        => $this->get_client_ip(),
        );

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

    /**
     * Get client IP address.
     *
     * @return string
     */
    private function get_client_ip() {
        $ip = '';

        // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated
        if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
            $ip = sanitize_text_field( wp_unslash( $_SERVER['HTTP_CLIENT_IP'] ) );
        } elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
            $ip = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) );
        } elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
            $ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
        }
        // phpcs:enable

        return $ip;
    }

    /**
     * Generate a secure API key.
     *
     * @return string
     */
    public static function generate_api_key() {
        $bytes = random_bytes( 32 );
        return 'sf_' . bin2hex( $bytes );
    }

    /**
     * Get the webhook URL.
     *
     * @return string
     */
    public static function get_webhook_url() {
        return rest_url( self::API_NAMESPACE . '/articles' );
    }

    /**
     * Get the media upload URL.
     *
     * @return string
     */
    public static function get_media_url() {
        return rest_url( self::API_NAMESPACE . '/media' );
    }

    /**
     * Get the health check URL.
     *
     * @return string
     */
    public static function get_health_url() {
        return rest_url( self::API_NAMESPACE . '/health' );
    }
}

// Initialize the webhook handler.
SearchFIT_Webhook::get_instance();
