<?php
/**
 * Postomatic Plugin - Post Maker API endpoint handler.
 *
 *  PHP Version 5.6
 *
 * @category Plugin
 * @package  Postomatic
 * @license  GPL-2.0-or-later https://www.gnu.org/licenses/gpl-2.0.html
 * @link     https://buildomatics.com/postomatics
 * @since    1.0.0
 */

namespace Postomatic\Api;

// Exit if accessed directly.
use Exception;
use Postomatic\Codemanager;
use Postomatic\Constants;
use Postomatic\Core\Activator;
use Postomatic\Utils;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use wpdb;

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


/**
 * Handles the creation of posts through the REST API endpoint.
 *
 * This class manages the Post Maker API endpoint for programmatic WordPress post
 * creation. Includes SEO plugin support, media attachments, API validation and
 * one-time codes with data sanitization.
 *
 * @category   Plugin
 * @subpackage Api
 * @license    GPL-2.0-or-later https://www.gnu.org/licenses/gpl-2.0.html
 * @link       https://buildomatics.com/postomatics
 * @since      1.0.0
 */
class Post_Maker {

	/**
	 * Code manager instance.
	 *
	 * @var Codemanager
	 */
	private $code_manager;

	/**
	 * WordPress database instance.
	 *
	 * @var wpdb
	 */
	private $wpdb;

	/**
	 * Constructor.
	 *
	 * @param Codemanager $code_manager Code manager instance.
	 */
	public function __construct( ?Codemanager $code_manager = null ) {
		$this->code_manager = $code_manager ?? new Codemanager();
		global $wpdb;
		$this->wpdb = $wpdb;

		add_action( 'rest_api_init', array( $this, 'register_post_maker_endpoint' ) );

		// Register AJAX hook for post generation.
		add_action( 'wp_ajax_postomatic_generate_post', array( $this, 'handle_ajax_generate_post' ) );
	}

	/**
	 * Initialize the Post_Maker class.
	 *
	 * @return void
	 */
	public static function init() {
		new self();
	}

	/**
	 * Register the post maker REST API endpoint.
	 *
	 * @return void
	 */
	public function register_post_maker_endpoint() {
		register_rest_route(
			Constants::API_WP_ENDPOINT_BASE,
			Constants::API_WP_ENDPOINT_POST_CREATOR,
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_post_maker' ),
				'permission_callback' => array( $this, 'validate_api_request' ),
			)
		);
	}

	/**
	 * Validate the API request by checking the API key.
	 *
	 * @param WP_REST_Request $request The request object.
	 *
	 * @return true|WP_Error True if valid, WP_Error otherwise.
	 */
	public function validate_api_request( WP_REST_Request $request ) {
		$api_key    = sanitize_text_field(
			$request->get_header( 'Authorization-Code' )
		);
		$stored_key = Utils::get_wp_api_secure_key();

		if ( ! $api_key || ! hash_equals( $stored_key, $api_key ) ) {
			return new WP_Error(
				'invalid_key',
				'Invalid API key',
				array( 'status' => 401 )
			);
		}

		return true;
	}

	/**
	 * Handle the post maker API request.
	 *
	 * @param WP_REST_Request $request The request object.
	 *
	 * @throws Exception                 When post creation fails.
	 * @return WP_REST_Response|WP_Error Response or error.
	 */
	public function handle_post_maker( WP_REST_Request $request ) {
		// Require WordPress media handling functions.
		if ( ! function_exists( 'media_sideload_image' ) ) {
			include_once ABSPATH . 'wp-admin/includes/media.php';
			include_once ABSPATH . 'wp-admin/includes/file.php';
			include_once ABSPATH . 'wp-admin/includes/image.php';
		}

		$params = $request->get_params();

		$allowed_html = array(
			'a'          => array(
				'href'   => array(),
				'title'  => array(),
				'target' => array(),
				'rel'    => array(),
			),
			'b'          => array(),
			'strong'     => array(),
			'i'          => array(),
			'em'         => array(),
			'u'          => array(),
			'p'          => array(),
			'br'         => array(),
			'ul'         => array(),
			'ol'         => array(),
			'li'         => array(),
			'span'       => array(
				'style' => array(),
				'class' => array(),
			),
			'div'        => array(
				'class' => array(),
				'id'    => array(),
				'style' => array(),
			),
			'img'        => array(
				'src'    => array(),
				'alt'    => array(),
				'width'  => array(),
				'height' => array(),
				'class'  => array(),
			),
			'h1'         => array( 'id' => array() ),
			'h2'         => array( 'id' => array() ),
			'h3'         => array( 'id' => array() ),
			'h4'         => array( 'id' => array() ),
			'h5'         => array( 'id' => array() ),
			'h6'         => array( 'id' => array() ),
			'table'      => array(),
			'thead'      => array(),
			'tbody'      => array(),
			'tr'         => array(),
			'td'         => array(),
			'th'         => array(),
			'blockquote' => array(),
			'code'       => array(),
			'pre'        => array(),
		);

		$params['content'] = wp_kses( $params['content'], $allowed_html );

		// Validate required fields.
		$required = array(
			'title',
			'content',
			'author',
			'category',
			'one_time_code',
		);
		foreach ( $required as $field ) {
			if ( empty( $params[ $field ] ) ) {
				return new WP_Error(
					'missing_field',
					"Missing required field: $field",
					array( 'status' => 400 )
				);
			}
		}

		// Validate one-time code.
		$one_time_code = $params['one_time_code'];
		if ( ! $this->code_manager->code_exists( $one_time_code ) ) {
			return new WP_Error(
				'invalid_code',
				'The provided code is invalid or has expired.',
				array( 'status' => 400 )
			);
		}

		// Prepare post data.
		$post_data = $this->prepare_post_data( $params );

		// Temporarily remove content filtering.
		remove_filter( 'content_save_pre', 'wp_filter_post_kses' );
		remove_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );

		// Create post.
		$post_id = wp_insert_post( $post_data );

		if ( $post_id && ! is_wp_error( $post_id ) ) {
			// Add tags to the post.
			$tags = isset( $params['tags'] ) ? explode( ',', $params['tags'] ) : array();
			wp_set_post_tags( $post_id, $tags, false );

			$params['seo_title']           = $params['title'] ?? '';
			$params['seo_description']     = $params['meta_seo_desc'] ?? '';
			$params['seo_focus_keyphrase'] = $params['one_focused_keyword'] ?? '';

			$this->fill_seo_plugin_info( $post_id, $params );
			$this->handle_feature_image( $post_id, $params );
			$this->handle_categories( $post_id, $params );

			return rest_ensure_response(
				array(
					'success'          => true,
					'post_id'          => $post_id,
					'link'             => get_permalink( $post_id ),
					'final_title'      => get_the_title( $post_id ),
					'message'          => 'Post created successfully',
					'categories'       => array_map(
						function ( $category ) {
							return $category->name;
						},
						get_categories( array( 'hide_empty' => false ) )
					),
					'last_post_titles' => $this->get_last_post_titles( 150 ),
				)
			);
		}

		// Restore content filtering.
		add_filter( 'content_save_pre', 'wp_filter_post_kses' );
		add_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );

		// Handle post creation error.
		if ( is_wp_error( $post_id ) ) {
			$error_message = $post_id->get_error_message();
			throw new Exception(
				'Post Creation error:.' .
				wp_json_encode( $error_message )
			);
		}

		return new WP_Error(
			'post_creation_failed',
			'Unable to create post. Please check provided data or try again later.',
			array(
				'status'        => 500,
				'post_data'     => $post_data,
				'error_message' => 'Post ID returned 0 or false.',
				'timestamp'     => current_time( 'mysql' ),
			)
		);
	}

	/**
	 * Prepare post data for insertion.
	 *
	 * @param array $params Request parameters.
	 *
	 * @return array Post data array.
	 */
	private function prepare_post_data( $params ): array {
		$post_data = array(
			'post_title'     => sanitize_text_field( $params['title'] ),
			'post_content'   => wp_kses_post( $params['content'] ),
			'post_author'    => absint( $params['author'] ),
			'post_status'    => sanitize_text_field( $params['post_status'] ?? 'draft' ),
			'post_type'      => 'post',
			'post_excerpt'   => sanitize_text_field( $params['excerpt'] ?? '' ),
			'comment_status' => isset( $params['is_comment_enabled'] ) && $params['is_comment_enabled'] ? 'open' : 'closed',
			'feature_image'  => $params['feature_image'] ?? '',
		);

		// Handle publish date if provided.
		if ( isset( $params['publish_date'] ) && ! empty( $params['publish_date'] ) ) {
			$publish_date_string = sprintf(
				'%s-%s-%s %s:%s:%s',
				$params['year'],
				str_pad( $params['month'], 2, '0', STR_PAD_LEFT ),
				str_pad( $params['day'], 2, '0', STR_PAD_LEFT ),
				str_pad( $params['hour'], 2, '0', STR_PAD_LEFT ),
				str_pad( $params['minute'], 2, '0', STR_PAD_LEFT ),
				'00'
			);

			$post_data['post_date']     = $publish_date_string;
			$post_data['post_date_gmt'] = get_gmt_from_date( $publish_date_string );
		}

		return $post_data;
	}

	/**
	 * Fill SEO plugin metadata based on installed plugins.
	 *
	 * @param int   $post_id  Post ID.
	 * @param array $seo_data SEO data parameters.
	 *
	 * @return void
	 */
	private function fill_seo_plugin_info( $post_id, $seo_data ) {
		// Yoast SEO.
		if ( defined( 'WPSEO_VERSION' ) && class_exists( 'WPSEO_Meta' ) ) {
			update_post_meta(
				$post_id,
				'_yoast_wpseo_title',
				sanitize_text_field( $seo_data['seo_title'] ?? $seo_data['title'] )
			);
			update_post_meta(
				$post_id,
				'_yoast_wpseo_metadesc',
				sanitize_text_field( $seo_data['seo_description'] ?? '' )
			);

			// Optional: Set focus keyphrase.
			if ( isset( $seo_data['seo_focus_keyphrase'] ) ) {
				update_post_meta(
					$post_id,
					'_yoast_wpseo_focuskw',
					sanitize_text_field( $seo_data['seo_focus_keyphrase'] )
				);
			}
		}

		// Rank Math SEO.
		if ( defined( 'RANKMATH_VERSION' ) && class_exists( 'RankMath\Post' ) ) {
			update_post_meta(
				$post_id,
				'rank_math_title',
				sanitize_text_field( $seo_data['seo_title'] ?? $seo_data['title'] )
			);
			update_post_meta(
				$post_id,
				'rank_math_description',
				sanitize_text_field( $seo_data['seo_description'] ?? '' )
			);

			// Optional: Set focus keyword.
			if ( isset( $seo_data['seo_focus_keyphrase'] ) ) {
				update_post_meta(
					$post_id,
					'rank_math_focus_keyword',
					sanitize_text_field( $seo_data['seo_focus_keyphrase'] )
				);
			}
		}

		// All in One SEO.
		if ( defined( 'AIOSEO_VERSION' )
			&& class_exists( 'AIOSEO\Plugin\Common\Models\Post' )
		) {
			update_post_meta(
				$post_id,
				'_aioseo_title',
				sanitize_text_field( $seo_data['seo_title'] ?? $seo_data['title'] )
			);
			update_post_meta(
				$post_id,
				'_aioseo_description',
				sanitize_text_field( $seo_data['seo_description'] ?? '' )
			);
		}

		// SEOPress.
		if ( defined( 'SEOPRESS_VERSION' )
			&& function_exists( 'seopress_get_service' )
		) {
			update_post_meta(
				$post_id,
				'_seopress_titles_title',
				sanitize_text_field( $seo_data['seo_title'] ?? $seo_data['title'] )
			);
			update_post_meta(
				$post_id,
				'_seopress_titles_desc',
				sanitize_text_field( $seo_data['seo_description'] ?? '' )
			);
		}

		// WP Meta SEO.
		if ( defined( 'WPMETA_SEO_VERSION' ) && class_exists( 'WPMeta_Seo' ) ) {
			update_post_meta(
				$post_id,
				'_metaseo_metatitle',
				sanitize_text_field( $seo_data['seo_title'] ?? $seo_data['title'] )
			);
			update_post_meta(
				$post_id,
				'_metaseo_metadesc',
				sanitize_text_field( $seo_data['seo_description'] ?? '' )
			);
		}

		// The SEO Framework.
		if ( defined( 'WPSEO_PLUGIN_FILE' ) ) {
			update_post_meta(
				$post_id,
				'_genesis_title',
				sanitize_text_field( $seo_data['seo_title'] ?? $seo_data['title'] )
			);
			update_post_meta(
				$post_id,
				'_genesis_description',
				sanitize_text_field( $seo_data['seo_description'] ?? '' )
			);
		}
	}

	/**
	 * Handle feature image upload and attachment.
	 *
	 * @param int   $post_id Post ID.
	 * @param array $params  Request parameters.
	 *
	 * @return void
	 */
	private function handle_feature_image( $post_id, $params ) {
		$image_id = null;

		// Check if feature_image parameter is provided and is numeric (existing media ID).
		if ( ! empty( $params['feature_image'] ) && is_numeric( $params['feature_image'] ) ) {
			$image_id = $params['feature_image'];
		} elseif ( ! empty( $params['feature_image'] ) ) { // Check if feature_image parameter is provided and is a URL.
			// Check if the image URL is from the current blog.
			$site_url = site_url();
			if ( strpos( $params['feature_image'], $site_url ) === 0 ) {
				// Image is from current blog, try to get the attachment ID.
				$existing_image_id = attachment_url_to_postid( $params['feature_image'] );
				if ( $existing_image_id ) {
					$image_id = $existing_image_id;
				} else {
					// If not found, upload it.
					try {
						// Upload to media and get ID.
						$image_id = media_sideload_image(
							$params['feature_image'],
							$post_id,
							null,
							'id'
						);
					} catch ( Exception $e ) {
						$image_id = null;
					}
				}
			} else {
				// Image is not from current blog, upload it.
				try {
					// Upload to media and get ID.
					$image_id = media_sideload_image(
						$params['feature_image'],
						$post_id,
						null,
						'id'
					);
				} catch ( Exception $e ) {
					$image_id = null;
				}
			}
		} elseif ( ! empty( $params['feature_image_url'] ) ) { // Check if feature_image_url parameter is provided.
			// Check if the image URL is from the current blog.
			$site_url = site_url();
			if ( strpos( $params['feature_image_url'], $site_url ) === 0 ) {
				// Image is from current blog, try to get the attachment ID.
				$existing_image_id = attachment_url_to_postid( $params['feature_image_url'] );
				if ( $existing_image_id ) {
					$image_id = $existing_image_id;
				} else {
					// If not found, upload it.
					try {
						// Upload to media and get ID.
						$image_id = media_sideload_image(
							$params['feature_image_url'],
							$post_id,
							null,
							'id'
						);
					} catch ( Exception $e ) {
						$image_id = null;
					}
				}
			} else {
				// Image is not from current blog, upload it.
				try {
					// Upload to media and get ID.
					$image_id = media_sideload_image(
						$params['feature_image_url'],
						$post_id,
						null,
						'id'
					);
				} catch ( Exception $e ) {
					// Handle exception, e.g., log error or display message.
					$image_id = null;
				}
			}
		}

		// If we have a valid image ID, set it as the post thumbnail.
		if ( ! is_wp_error( $image_id ) && $image_id && wp_attachment_is_image( $image_id ) ) {
			set_post_thumbnail( $post_id, $image_id );
		}
	}

	/**
	 * Handle categories assignment for the created post.
	 *
	 * @param int   $post_id Post ID.
	 * @param array $params  Request parameters.
	 *
	 * @return void
	 */
	private function handle_categories( int $post_id, array $params ): void {
		// 1. nothing to do if no category sent.
		if ( empty( $params['category'] ) ) {
			return;
		}

		// 2. check if category is an array and get the first element if it exists.
		$category = $params['category'];
		if ( is_array( $category ) && ! empty( $category[0] ) ) {
			$category = $category[0];
		}

		// 3. clean up the name & build a slug.
		$raw_name = sanitize_text_field( $category );
		$slug     = sanitize_title( $raw_name );

		// 4. skip "uncategorized" altogether.
		if ( 'uncategorized' === $slug ) {
			return;
		}

		// 5. find or create the term.
		$term = get_category_by_slug( $slug );
		if ( ! $term ) {
			$insert = wp_insert_term(
				ucfirst( $raw_name ),
				'category',
				array( 'slug' => $slug )
			);
			if ( is_wp_error( $insert ) ) {
				return; // bail if something went wrong.
			}
			$term_id = (int) $insert['term_id'];
		} else {
			$term_id = (int) $term->term_id;
		}

		wp_set_post_categories( $post_id, array( $term_id ) );
	}

	/**
	 * Get the titles of the last N posts (published or draft).
	 *
	 * @param int $limit Number of post titles to retrieve.
	 *
	 * @return array Array of post titles
	 */
	private function get_last_post_titles( $limit = 150 ) {
		$post_titles = array();

		$args = array(
			'post_type'      => 'post',
			'post_status'    => array( 'publish', 'draft' ),
			'posts_per_page' => $limit,
			'orderby'        => 'date',
			'order'          => 'DESC',
			'fields'         => 'all',
		);

		$posts = get_posts( $args );

		if ( ! empty( $posts ) ) {
			foreach ( $posts as $post ) {
				$post_titles[] = $post->post_title;
			}
		}

		return $post_titles;
	}

	/**
	 * AJAX handler for generating posts.
	 *
	 * This method receives form data from the client and processes it
	 * to generate a post.
	 *
	 * @return void
	 */
	public function handle_ajax_generate_post() {
		// Get the raw POST data.

		// Check if we're receiving form data.
		$nonce = isset( $_POST['nonce'] ) ? sanitize_key( wp_unslash( $_POST['nonce'] ) ) : '';

		// Verify nonce before processing form data.
		if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'postomatic_admin_nonce' ) ) {
			wp_send_json_error( array( 'message' => 'Security verification failed.' ), 403 );
			return;
		}
		$data = $_POST;

		// Convert string boolean values to actual booleans recursively.
		if ( is_array( $data ) ) {
			$data = $this->convert_string_booleans_to_actual( $data );
		}

		// Check if we have valid data.
		if ( empty( $data ) || ! is_array( $data ) ) {
			wp_send_json_error( array( 'message' => 'Invalid or empty request data' ), 400 );

			return;
		}

		// Call the generate_post method.
		$result = $this->generate_post( $data );

		// Handle the response.
		if ( ! $result['success'] ) {
			wp_send_json_error( $result['data'], $result['status'] );
		} else {
			wp_send_json_success( $result['data'] );
		}
		wp_die();
	}

	/**
	 * Recursively convert string boolean values to actual booleans.
	 *
	 * @param mixed $data The data to convert.
	 *
	 * @return mixed The converted data.
	 */
	private function convert_string_booleans_to_actual( $data ) {
		if ( is_array( $data ) ) {
			foreach ( $data as $key => $value ) {
				if ( is_array( $value ) || is_object( $value ) ) {
					// Recursively convert nested arrays and objects.
					$data[ $key ] = $this->convert_string_booleans_to_actual( $value );
				} elseif ( 'true' === $value ) {
					$data[ $key ] = true;
				} elseif ( 'false' === $value ) {
					$data[ $key ] = false;
				}
			}
		} elseif ( is_object( $data ) ) {
			foreach ( get_object_vars( $data ) as $key => $value ) {
				if ( is_array( $value ) || is_object( $value ) ) {
					// Recursively convert nested arrays and objects.
					$data->$key = $this->convert_string_booleans_to_actual( $value );
				} elseif ( 'true' === $value ) {
					$data->$key = true;
				} elseif ( 'false' === $value ) {
					$data->$key = false;
				}
			}
		}

		return $data;
	}

	/**
	 * Handle post generation via AJAX.
	 *
	 * This method receives form data, forwards it to the API_POST_MAKER endpoint,
	 * and returns the response.
	 *
	 * @param array $data The data for post generation.
	 *
	 * @return array The response from the API.
	 */
	public function generate_post( $data ) {
		// Check user authentication.
		if ( ! is_user_logged_in() ) {
			return array(
				'success' => false,
				'data'    => array( 'message' => 'Unauthorized access.' ),
				'status'  => 403,
			);
		}

		// Verify nonce for security.
		if ( ! isset( $data['nonce'] ) || ! wp_verify_nonce( sanitize_key( $data['nonce'] ), 'postomatic_admin_nonce' ) ) {
			return array(
				'success' => false,
				'data'    => array( 'message' => 'Security verification failed.' ),
				'status'  => 403,
			);
		}

		// Get the API key.
		$api_key = Utils::get_api_service_key();
		if ( empty( $api_key ) ) {
			return array(
				'success' => false,
				'data'    => array( 'message' => 'API key not found' ),
				'status'  => 403,
			);
		}

		// Remove action and nonce from the payload.
		if ( isset( $data['action'] ) ) {
			unset( $data['action'] );
		}
		if ( isset( $data['nonce'] ) ) {
			unset( $data['nonce'] );
		}

		// Convert string boolean values to actual booleans recursively.
		$data = $this->convert_string_booleans_to_actual( $data );

		// Convert back to JSON for the API request.
		$json_data = wp_json_encode( $data );
		// Make the API request to the post maker endpoint.
		$api_url  = Constants::API_POST_MAKER;
		$response = wp_remote_post(
			$api_url,
			array(
				'headers'     => array(
					'Api-Key'      => $api_key,
					'Content-Type' => 'application/json',
					'Accept'       => 'application/json',
				),
				'body'        => $json_data,
				'timeout'     => 60, // Increase timeout for potentially long-running request.
				'data_format' => 'json',
			)
		);

		// Check for errors.
		if ( is_wp_error( $response ) ) {
			return array(
				'success' => false,
				'data'    => array( 'message' => $response->get_error_message() ),
				'status'  => 500,
			);
		}

		// Get the response status code.
		$status_code = wp_remote_retrieve_response_code( $response );

		// Check for 401 Unauthorized error.
		if ( 401 === $status_code ) {
			// Call Activator::activate() to refresh the API key.
			Activator::activate();
		}

		// Get the response body.
		$body          = wp_remote_retrieve_body( $response );
		$response_data = json_decode( $body, true );

		// Return the response with the same status code.
		if ( $status_code >= 400 ) {
			return array(
				'success' => false,
				'data'    => $response_data,
				'status'  => $status_code,
			);
		}

		return array(
			'success' => true,
			'data'    => $response_data,
			'status'  => $status_code,
		);
	}
}
