<?php
/**
 * Authenticated preview/convert/rollback for Rank Math / Yoast FAQ & HowTo blocks.
 *
 * @package SEOvaultSEO\Blocks
 */

namespace SEOvaultSEO\Blocks;

use SEOvaultSEO\Api\RouteCapabilities;
use SEOvaultSEO\Api\Router;
use SEOvaultSEO\Utils\Logger;

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

/**
 * AJAX + connector REST endpoints for third-party block conversion.
 */
class ThirdPartyBlockEndpoints {

	/**
	 * Nonce action for editor / admin AJAX (per-post).
	 */
	const NONCE_ACTION = 'seovaultseo_thirdparty_block_convert';

	/**
	 * Whether init has run.
	 *
	 * @var bool
	 */
	private static $initialized = false;

	/**
	 * Register hooks.
	 *
	 * @return void
	 */
	public static function init() {
		if ( self::$initialized ) {
			return;
		}
		self::$initialized = true;

		add_action( 'wp_ajax_seovaultseo_thirdparty_block_preview', array( __CLASS__, 'ajax_preview' ) );
		add_action( 'wp_ajax_seovaultseo_thirdparty_block_convert', array( __CLASS__, 'ajax_convert' ) );
		add_action( 'wp_ajax_seovaultseo_thirdparty_block_rollback', array( __CLASS__, 'ajax_rollback' ) );
		add_action( 'wp_ajax_seovaultseo_thirdparty_block_scan', array( __CLASS__, 'ajax_scan' ) );
		add_action( 'wp_ajax_seovaultseo_thirdparty_block_convert_batch', array( __CLASS__, 'ajax_convert_batch' ) );

		add_action( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ) );
	}

	/**
	 * Register connector REST routes.
	 *
	 * @return void
	 */
	public static function register_rest_routes() {
		$ns      = Router::NAMESPACE;
		$post_id = '(?P<id>\d+)';

		register_rest_route(
			$ns,
			'/posts/' . $post_id . '/third-party-blocks/preview',
			array(
				'methods'             => 'GET',
				'callback'            => array( __CLASS__, 'rest_preview' ),
				'permission_callback' => Router::require_caps( RouteCapabilities::CAP_POSTS_READ ),
			)
		);

		register_rest_route(
			$ns,
			'/posts/' . $post_id . '/third-party-blocks/convert',
			array(
				'methods'             => 'POST',
				'callback'            => array( __CLASS__, 'rest_convert' ),
				'permission_callback' => Router::require_caps( RouteCapabilities::CAP_POSTS_WRITE ),
			)
		);

		register_rest_route(
			$ns,
			'/posts/' . $post_id . '/third-party-blocks/rollback',
			array(
				'methods'             => 'POST',
				'callback'            => array( __CLASS__, 'rest_rollback' ),
				'permission_callback' => Router::require_caps( RouteCapabilities::CAP_POSTS_WRITE ),
			)
		);

		register_rest_route(
			$ns,
			'/seo/third-party-blocks/scan',
			array(
				'methods'             => 'GET',
				'callback'            => array( __CLASS__, 'rest_scan' ),
				'permission_callback' => Router::require_caps( RouteCapabilities::CAP_SEO_READ ),
			)
		);

		register_rest_route(
			$ns,
			'/seo/third-party-blocks/convert-batch',
			array(
				'methods'             => 'POST',
				'callback'            => array( __CLASS__, 'rest_convert_batch' ),
				'permission_callback' => Router::require_caps( RouteCapabilities::CAP_SEO_IMPORT_WRITE ),
			)
		);
	}

	/**
	 * AJAX preview.
	 *
	 * @return void
	 */
	public static function ajax_preview() {
		self::ajax_dispatch( 'preview' );
	}

	/**
	 * AJAX convert.
	 *
	 * @return void
	 */
	public static function ajax_convert() {
		self::ajax_dispatch( 'convert' );
	}

	/**
	 * AJAX rollback.
	 *
	 * @return void
	 */
	public static function ajax_rollback() {
		self::ajax_dispatch( 'rollback' );
	}

	/**
	 * AJAX site scan (manage_options).
	 *
	 * @return void
	 */
	public static function ajax_scan() {
		if ( ! check_ajax_referer( 'seovaultseo_admin', 'nonce', false ) ) {
			wp_send_json_error(
				array(
					'code'    => 'invalid_nonce',
					'message' => __( 'Invalid security token.', 'seovault-ai' ),
				),
				403
			);
		}
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error(
				array(
					'code'    => 'forbidden',
					'message' => __( 'You do not have permission to scan for third-party blocks.', 'seovault-ai' ),
				),
				403
			);
		}

		wp_send_json_success( ThirdPartyBlockConverter::scan_site( 200 ) );
	}

	/**
	 * AJAX batch convert (manage_options).
	 *
	 * @return void
	 */
	public static function ajax_convert_batch() {
		if ( ! check_ajax_referer( 'seovaultseo_admin', 'nonce', false ) ) {
			wp_send_json_error(
				array(
					'code'    => 'invalid_nonce',
					'message' => __( 'Invalid security token.', 'seovault-ai' ),
				),
				403
			);
		}
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error(
				array(
					'code'    => 'forbidden',
					'message' => __( 'You do not have permission to convert third-party blocks.', 'seovault-ai' ),
				),
				403
			);
		}

		$confirm = ! empty( $_POST['confirm'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		$offset  = isset( $_POST['offset'] ) ? absint( $_POST['offset'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
		$limit   = isset( $_POST['limit'] ) ? absint( $_POST['limit'] ) : 10; // phpcs:ignore WordPress.Security.NonceVerification.Missing
		if ( $limit < 1 ) {
			$limit = 10;
		}
		if ( $limit > 50 ) {
			$limit = 50;
		}

		$result = ThirdPartyBlockConverter::convert_batch(
			$limit,
			$offset,
			array(
				'confirm' => $confirm,
			)
		);

		if ( empty( $result['ok'] ) ) {
			wp_send_json_error(
				array(
					'code'    => isset( $result['code'] ) ? $result['code'] : 'error',
					'message' => isset( $result['message'] ) ? $result['message'] : __( 'Batch conversion failed.', 'seovault-ai' ),
					'data'    => $result,
				),
				self::status_for_code( isset( $result['code'] ) ? (string) $result['code'] : '' )
			);
		}

		wp_send_json_success( $result );
	}

	/**
	 * REST preview.
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public static function rest_preview( $request ) {
		$post_id = (int) $request['id'];
		$gate    = self::assert_post_accessible( $post_id, 'rest', 'preview' );
		if ( empty( $gate['ok'] ) ) {
			return self::rest_error( $gate );
		}
		return rest_ensure_response( ThirdPartyBlockConverter::preview( $post_id ) );
	}

	/**
	 * REST convert (authorized connector migration path).
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public static function rest_convert( $request ) {
		$post_id = (int) $request['id'];
		$gate    = self::assert_post_accessible( $post_id, 'rest', 'convert' );
		if ( empty( $gate['ok'] ) ) {
			return self::rest_error( $gate );
		}

		$params = self::rest_params( $request );
		$args   = array(
			'confirm'                        => ! empty( $params['confirm'] ),
			'authorized_connector_migration' => ! empty( $params['authorized_connector_migration'] ),
			'content_hash'                   => isset( $params['content_hash'] ) ? (string) $params['content_hash'] : '',
		);

		if ( empty( $args['confirm'] ) && empty( $args['authorized_connector_migration'] ) ) {
			return self::rest_error(
				array(
					'ok'      => false,
					'code'    => 'confirmation_required',
					'message' => 'Third-party block conversion requires confirm=true or authorized_connector_migration=true.',
					'status'  => 400,
				)
			);
		}

		$result = ThirdPartyBlockConverter::convert( $post_id, $args );
		if ( empty( $result['ok'] ) ) {
			$result['status'] = self::status_for_code( isset( $result['code'] ) ? (string) $result['code'] : '' );
			return self::rest_error( $result );
		}

		return rest_ensure_response( $result );
	}

	/**
	 * REST rollback.
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public static function rest_rollback( $request ) {
		$post_id = (int) $request['id'];
		$gate    = self::assert_post_accessible( $post_id, 'rest', 'rollback' );
		if ( empty( $gate['ok'] ) ) {
			return self::rest_error( $gate );
		}

		$params = self::rest_params( $request );
		$args   = array(
			'confirm'                        => ! empty( $params['confirm'] ),
			'authorized_connector_migration' => ! empty( $params['authorized_connector_migration'] ),
		);

		$result = ThirdPartyBlockConverter::rollback( $post_id, $args );
		if ( empty( $result['ok'] ) ) {
			$result['status'] = self::status_for_code( isset( $result['code'] ) ? (string) $result['code'] : '' );
			return self::rest_error( $result );
		}

		return rest_ensure_response( $result );
	}

	/**
	 * REST site scan.
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response
	 */
	public static function rest_scan( $request ) {
		$limit = (int) $request->get_param( 'limit' );
		if ( $limit <= 0 ) {
			$limit = 200;
		}
		return rest_ensure_response( ThirdPartyBlockConverter::scan_site( $limit ) );
	}

	/**
	 * REST batch convert.
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public static function rest_convert_batch( $request ) {
		$params = self::rest_params( $request );
		$args   = array(
			'confirm'                        => ! empty( $params['confirm'] ),
			'authorized_connector_migration' => ! empty( $params['authorized_connector_migration'] ),
		);

		if ( empty( $args['confirm'] ) && empty( $args['authorized_connector_migration'] ) ) {
			return self::rest_error(
				array(
					'ok'      => false,
					'code'    => 'confirmation_required',
					'message' => 'Batch conversion requires confirm=true or authorized_connector_migration=true.',
					'status'  => 400,
				)
			);
		}

		$offset = isset( $params['offset'] ) ? absint( $params['offset'] ) : 0;
		$limit  = isset( $params['limit'] ) ? absint( $params['limit'] ) : 10;
		if ( $limit < 1 ) {
			$limit = 10;
		}
		if ( $limit > 50 ) {
			$limit = 50;
		}

		$result = ThirdPartyBlockConverter::convert_batch( $limit, $offset, $args );
		if ( empty( $result['ok'] ) ) {
			$result['status'] = self::status_for_code( isset( $result['code'] ) ? (string) $result['code'] : '' );
			return self::rest_error( $result );
		}

		return rest_ensure_response( $result );
	}

	/**
	 * Shared AJAX dispatcher for per-post ops.
	 *
	 * @param string $op Operation.
	 * @return void
	 */
	private static function ajax_dispatch( $op ) {
		if ( ! check_ajax_referer( self::NONCE_ACTION, 'nonce', false ) ) {
			wp_send_json_error(
				array(
					'code'    => 'invalid_nonce',
					'message' => __( 'Invalid security token.', 'seovault-ai' ),
				),
				403
			);
		}

		$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified above.
		$gate    = self::assert_post_accessible( $post_id, 'ajax', $op );
		if ( empty( $gate['ok'] ) ) {
			wp_send_json_error(
				array(
					'code'    => isset( $gate['code'] ) ? $gate['code'] : 'forbidden',
					'message' => isset( $gate['message'] ) ? $gate['message'] : __( 'Forbidden.', 'seovault-ai' ),
				),
				isset( $gate['status'] ) ? (int) $gate['status'] : 403
			);
		}

		if ( 'preview' === $op ) {
			wp_send_json_success( ThirdPartyBlockConverter::preview( $post_id ) );
		}

		$confirm      = ! empty( $_POST['confirm'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		$content_hash = isset( $_POST['content_hash'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['content_hash'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing

		if ( 'convert' === $op ) {
			$result = ThirdPartyBlockConverter::convert(
				$post_id,
				array(
					'confirm'      => $confirm,
					'content_hash' => $content_hash,
				)
			);
		} else {
			$result = ThirdPartyBlockConverter::rollback(
				$post_id,
				array(
					'confirm' => $confirm,
				)
			);
		}

		if ( empty( $result['ok'] ) ) {
			wp_send_json_error(
				array(
					'code'    => isset( $result['code'] ) ? $result['code'] : 'error',
					'message' => isset( $result['message'] ) ? $result['message'] : __( 'Conversion failed.', 'seovault-ai' ),
					'data'    => $result,
				),
				self::status_for_code( isset( $result['code'] ) ? (string) $result['code'] : '' )
			);
		}

		wp_send_json_success( $result );
	}

	/**
	 * @param \WP_REST_Request $request Request.
	 * @return array<string,mixed>
	 */
	private static function rest_params( $request ) {
		$params = $request->get_json_params();
		if ( ! is_array( $params ) ) {
			$params = $request->get_params();
		}
		return is_array( $params ) ? $params : array();
	}

	/**
	 * @param int    $post_id Post ID.
	 * @param string $channel ajax|rest.
	 * @param string $op      Operation.
	 * @return array<string,mixed>
	 */
	private static function assert_post_accessible( $post_id, $channel, $op ) {
		if ( $post_id <= 0 ) {
			return array(
				'ok'      => false,
				'code'    => 'invalid_post',
				'message' => __( 'Invalid post.', 'seovault-ai' ),
				'status'  => 400,
			);
		}

		$post = get_post( $post_id );
		if ( ! $post ) {
			return array(
				'ok'      => false,
				'code'    => 'invalid_post',
				'message' => __( 'Post not found.', 'seovault-ai' ),
				'status'  => 404,
			);
		}

		if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
			return array(
				'ok'      => false,
				'code'    => 'revision_or_autosave_not_allowed',
				'message' => __( 'Cannot convert third-party blocks on revisions or autosaves.', 'seovault-ai' ),
				'status'  => 400,
			);
		}

		if ( 'ajax' === $channel ) {
			if ( ! current_user_can( 'edit_post', $post_id ) ) {
				Logger::error(
					'ThirdPartyBlockEndpoints: edit_post denied',
					array(
						'post_id' => $post_id,
						'op'      => $op,
					)
				);
				return array(
					'ok'      => false,
					'code'    => 'forbidden',
					'message' => __( 'You do not have permission to edit this post.', 'seovault-ai' ),
					'status'  => 403,
				);
			}
		}

		return array( 'ok' => true );
	}

	/**
	 * @param array $gate Gate result.
	 * @return \WP_Error
	 */
	private static function rest_error( array $gate ) {
		$code    = isset( $gate['code'] ) ? (string) $gate['code'] : 'error';
		$message = isset( $gate['message'] ) ? (string) $gate['message'] : 'Error';
		$status  = isset( $gate['status'] ) ? (int) $gate['status'] : self::status_for_code( $code );
		return new \WP_Error( $code, $message, array( 'status' => $status, 'data' => $gate ) );
	}

	/**
	 * @param string $code Code.
	 * @return int
	 */
	private static function status_for_code( $code ) {
		$map = array(
			'confirmation_required' => 400,
			'invalid_post'          => 404,
			'stale_content'         => 409,
			'not_convertible'       => 422,
			'backup_missing'        => 404,
			'forbidden'             => 403,
			'update_failed'         => 500,
			'transform_incomplete'  => 500,
		);
		return isset( $map[ $code ] ) ? $map[ $code ] : 400;
	}
}
