<?php

namespace Gigneo;

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

use WP_Error;

/**
 * Class Messages
 *
 * Plugin Messages class for Gigneo.
 *
 * @since 0.0.1
 */
class Messages {


	/**
	 * The single instance of the class.
	 *
	 * @var Messages|null
	 * @since 0.0.1
	 */
	private static $instance = null;

	/**
	 * Constructor for the class.
	 *
	 * @since 0.0.1
	 */
	public function __construct() {
	}

	/**
	 * Ensures only one instance of the class is loaded or can be loaded.
	 *
	 * @return Messages
	 * @since 0.0.1
	 */
	public static function instance() {
		if ( is_null( self::$instance ) ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Retrieve conversation messages between two profiles.
	 *
	 * @since 1.0.0
	 *
	 * @param int    $sender_profile_id   Sender profile ID.
	 * @param int    $receiver_profile_id Receiver profile ID.
	 * @param string $type                Message type.
	 * @param int    $limit               Number of messages to fetch.
	 * @param int    $offset              Query offset.
	 * @param bool   $attachments         Whether to include attachments.
	 * @param string $status              Message status filter.
	 * @param int    $post_id             Related post ID.
	 *
	 * @return array
	 */
	public static function get_messages( $sender_profile_id, $receiver_profile_id, $type = 'chat', $limit = 30, $offset = 0, $attachments = false, $status = 'all', $post_id = '' ) {
		global $wpdb;
		$table_messages    = $wpdb->prefix . 'gigneo_messages';
		$table_attachments = $wpdb->prefix . 'gigneo_attachments';

		// Validate input parameters
		if ( empty( $sender_profile_id ) || empty( $receiver_profile_id ) ) {
			return array(
				'profile_id' => (int) $sender_profile_id,
				'recipient'  => (int) $receiver_profile_id,
				'total'      => 0,
				'offset'     => (int) $offset,
				'limit'      => (int) $limit,
				'pinned'     => array(),
				'messages'   => array(),
			);
		}

		// Build conditions for SQL query using standard placeholder array
		$base_where = '( (sender_id = %d AND receiver_id = %d) OR (sender_id = %d AND receiver_id = %d) )';
		$base_args  = array(
			$sender_profile_id,
			$receiver_profile_id,
			$receiver_profile_id,
			$sender_profile_id,
		);

		if ( 'read' === $status ) {
			$base_where .= ' AND status = %s';
			$base_args[] = 'read';
		} elseif ( 'unread' === $status ) {
			$base_where .= ' AND status = %s';
			$base_args[] = 'unread';
		}

		$base_where .= ' AND type = %s';
		$base_args[] = $type;

		if ( ! empty( $post_id ) ) {
			$base_where .= ' AND post_id = %d';
			$base_args[] = $post_id;
		}

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
		// Retrieve total message count
		$total_query    = "SELECT COUNT(*) FROM %i WHERE $base_where";
		$total_messages = $wpdb->get_var(
			$wpdb->prepare( $total_query, array_merge( array( $table_messages ), $base_args ) )
		);

		// Retrieve pinned messages for the conversation
		$pinned_query    = "SELECT * FROM %i WHERE pinned = 1 AND $base_where ORDER BY sent_at ASC";
		$pinned_messages = $wpdb->get_results(
			$wpdb->prepare( $pinned_query, array_merge( array( $table_messages ), $base_args ) )
		);

		// Retrieve paginated messages
		$messages_query = "SELECT * FROM %i WHERE $base_where ORDER BY sent_at DESC LIMIT %d OFFSET %d";
		$messages       = $wpdb->get_results(
			$wpdb->prepare( $messages_query, array_merge( array( $table_messages ), $base_args, array( $limit, $offset ) ) )
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

		// Reverse messages for chronological display (oldest to newest)
		$messages = array_reverse( $messages );

		// Fetch attachments if requested
		$attachments_results = array();
		if ( $attachments && ! empty( $messages ) ) {
			$messages_with_attachments = array_filter(
				$messages,
				function ( $msg ) {
					return ! empty( $msg->attachments );
				}
			);
			// If any messages have attachments
			if ( ! empty( $messages_with_attachments ) ) {
				// After fetching $messages
				$message_ids = wp_list_pluck( $messages, 'id' );

				if ( ! empty( $message_ids ) ) {
					// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
					$placeholders     = implode( ',', array_fill( 0, count( $message_ids ), '%d' ) );
					$attachments_data = $wpdb->get_results(
						$wpdb->prepare(
							"SELECT * FROM %i WHERE post_id IN ($placeholders) AND post_type = %s",
							array_merge( array( $table_attachments ), array_map( 'absint', $message_ids ), array( $type ) )
						)
					);
					// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber

					foreach ( $attachments_data as $attachment ) {
						$attachments_results[ $attachment->post_id ][] = $attachment;
					}
				}
			}
		}

		// Enrich messages with attachments and reply data
		foreach ( $messages as $message ) {
			// Attachments
			$message->attachments = $attachments_results[ $message->id ] ?? array();

			// Replied message (if any)
			if ( ! empty( $message->reply ) ) {
				// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				$replied_message = $wpdb->get_row(
					$wpdb->prepare(
						'SELECT * FROM %i WHERE id = %d',
						$table_messages,
						$message->reply
					)
				);
				// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				if ( $replied_message ) {
					// Attachments for replied message
					// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
					$replied_attachments = $wpdb->get_results(
						$wpdb->prepare(
							'SELECT * FROM %i WHERE post_id = %d',
							$table_attachments,
							$replied_message->id
						)
					);
					// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
					$message->reply = array(
						'id'             => $replied_message->id,
						'content'        => $replied_message->filter_message,
						'sender_id'      => $replied_message->sender_id,
						'formatted_time' => $replied_message->sent_at,
						'attachments'    => $replied_attachments,
					);
				}
			}
		}

		return array(
			'profile_id' => $sender_profile_id,
			'recipient'  => $receiver_profile_id,
			'total'      => $total_messages,
			'offset'     => $offset,
			'limit'      => $limit,
			'pinned'     => $pinned_messages,
			'messages'   => $messages,
		);
	}

	/**
	 * Mark unread messages as read for a conversation.
	 *
	 * @since 1.0.0
	 *
	 * @param int    $profile_id        Receiver profile ID.
	 * @param int    $sender_profile_id Sender profile ID.
	 * @param string $type              Message type.
	 * @param int    $post_id           Related post ID.
	 *
	 * @return void
	 */
	public static function mark_as_read( $profile_id, $sender_profile_id, $type = 'chat', $post_id = '' ) {

		if ( empty( $profile_id ) || empty( $sender_profile_id ) ) {
			return;
		}

		global $wpdb;
		$table_messages = $wpdb->prefix . 'gigneo_messages';

		// Build conditions for SQL query
		$conditions        = array(
			'sender_id'   => $sender_profile_id,
			'receiver_id' => $profile_id,
			'status'      => 'unread',
		);
		$condition_formats = array( '%d', '%d', '%s' );

		// Add type condition
		$conditions['type']  = $type;
		$condition_formats[] = '%s';

		// Add post_id condition if provided
		if ( ! empty( $post_id ) ) {
			$conditions['post_id'] = $post_id;
			$condition_formats[]   = '%s';
		}

		// Update messages to mark as read
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->update(
			$table_messages,
			array(
				'status'      => 'read',
				'read_at'     => current_time( 'mysql' ),
				'read_at_gmt' => current_time( 'mysql', 1 ),
			),
			$conditions,
			array( '%s', '%s', '%s' ),
			$condition_formats
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
	}

	/**
	 * Soft delete a message after ownership verification.
	 *
	 * @since 1.0.0
	 *
	 * @param int $message_id        Message ID.
	 * @param int $sender_profile_id Sender profile ID.
	 *
	 * @return bool|WP_Error
	 */
	public static function delete_message( $message_id, $sender_profile_id ) {
		global $wpdb;

		$table_messages = $wpdb->prefix . 'gigneo_messages';

		if ( empty( $message_id ) || empty( $sender_profile_id ) ) {
			return new WP_Error( 'missing_required_option', esc_html__( 'Something_went_wrong.', 'gigneo' ) );
		}

		$message_id        = absint( $message_id );
		$sender_profile_id = absint( $sender_profile_id );

		// Fetch original message for ownership check
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$original = $wpdb->get_row(
			$wpdb->prepare(
				'SELECT * FROM %i WHERE id = %d',
				$table_messages,
				$message_id
			)
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

		if ( ! $original ) {
			return new WP_Error( 'not_found', esc_html__( 'Message not found.', 'gigneo' ) );
		}

		if ( (int) $original->sender_id !== (int) $sender_profile_id ) {
			return new WP_Error( 'forbidden', esc_html__( 'You are not allowed to delete this message.', 'gigneo' ) );
		}

		// Soft delete message
		$message      = esc_html__( 'This message has been deleted.', 'gigneo' );
		$message_html = '<p>' . esc_html( $message ) . '</p>';
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$updated = $wpdb->update(
			$table_messages, // Table name
			array(
				'status'         => 'deleted',
				'filter_message' => $message_html,
			), // Data to update
			array( 'id' => $message_id ),
			array( '%s', '%s' ),
			array( '%d' )
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

		if ( false === $updated ) {
			return new WP_Error( 'db_error', esc_html__( 'Failed to delete message.', 'gigneo' ) );
		}

		return true;
	}

	/**
	 * Send a message with optional attachments.
	 *
	 * @since 1.0.0
	 *
	 * @param int    $sender_profile_id   Sender profile ID.
	 * @param int    $receiver_profile_id Receiver profile ID.
	 * @param string $message             Message content.
	 * @param int    $reply_id            Replied message ID.
	 * @param array  $files               Uploaded files array.
	 * @param string $type                Message type.
	 * @param int    $post_id             Related post ID.
	 *
	 * @return int|WP_Error
	 */
	public static function send_message( $sender_profile_id, $receiver_profile_id, $message, $reply_id = 0, $files = array(), $type = '', $post_id = '' ) {
		global $wpdb;

		$table_messages    = $wpdb->prefix . 'gigneo_messages';
		$table_attachments = $wpdb->prefix . 'gigneo_attachments';

		if ( empty( $sender_profile_id ) || empty( $receiver_profile_id ) ) {
			return new WP_Error( 'missing_required_option', esc_html__( 'Something_went_wrong.', 'gigneo' ) );
		}
		// Sanitize message
		$allowed_tags = gigneo_allowed_html_tags();

		$message = wp_kses( wp_unslash( $message ), $allowed_tags );

		// Load blocked keywords
		$block_keywords_raw = gigneo_option( '_gigneo_ts_block_keywords', '' );
		$block_keywords     = array_filter( array_map( 'trim', preg_split( '/[\n,]+/', $block_keywords_raw ) ) );

		// Filter text content only
		$filtered_message = preg_replace_callback(
			'/>([^<]+)</',
			static function ( $matches ) use ( $block_keywords ) {
				$text = $matches[1];
				foreach ( $block_keywords as $word ) {
					$pattern     = '/\b' . preg_quote( $word, '/' ) . '\b/i';
					$replacement = str_repeat( '*', strlen( $word ) );
					$text        = preg_replace( $pattern, $replacement, $text );
				}

				return '>' . $text . '<';
			},
			'>' . $message . '<'
		);

		$filtered_message = substr( $filtered_message, 1, -1 );

		$sender_id   = absint( $sender_profile_id );
		$receiver_id = absint( $receiver_profile_id );
		$reply_id    = absint( $reply_id );
		$has_files   = ! empty( $files ) && ! empty( $files['name'][0] );

		if ( empty( $message ) && ! $has_files ) {
			return new WP_Error( 'empty_message', esc_html__( 'Message cannot be empty.', 'gigneo' ) );
		}

		$insert_data    = array(
			'sender_id'      => $sender_id,
			'receiver_id'    => $receiver_id,
			'reply'          => $reply_id,
			'message'        => $message,
			'filter_message' => $filtered_message,
			'status'         => 'unread',
			'sent_at'        => current_time( 'mysql' ),
			'sent_at_gmt'    => current_time( 'mysql', 1 ),
			'type'           => $type,
		);
		$insert_formats = array( '%d', '%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s' );

		if ( ! empty( $post_id ) ) {
			$insert_data['post_id'] = $post_id;
			$insert_formats[]       = '%s';
		}

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$inserted = $wpdb->insert( $table_messages, $insert_data, $insert_formats );
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

		if ( ! $inserted ) {
			return new WP_Error( 'db_insert_error', esc_html__( 'Something went wrong.', 'gigneo' ) );
		}

		$message_id = $wpdb->insert_id;

		$attachment_ids = array();
		if ( $has_files && 'chat' === $type ) {

			$enabled_media      = gigneo_option( '_gigneo_ts_enable_media', array() );
			$chat_upload_dir    = gigneo_upload_dir_path( '/' . gmdate( 'Y' ) . '/' . gmdate( 'm' ) . '/chat/messages/' . $sender_id . '/' . $receiver_id . '/' );
			$max_files          = gigneo_option( '_gigneo_ts_chat_max_files', 3 );
			$image_extensions   = in_array( 'images', (array) $enabled_media, true ) ? gigneo_option( '_gigneo_ts_image_extensions', array() ) : array();
			$video_extensions   = in_array( 'videos', (array) $enabled_media, true ) ? gigneo_option( '_gigneo_ts_video_extensions', array() ) : array();
			$audio_extensions   = in_array( 'audios', (array) $enabled_media, true ) ? gigneo_option( '_gigneo_ts_audio_extensions', array() ) : array();
			$doc_extensions     = in_array( 'docs', (array) $enabled_media, true ) ? gigneo_option( '_gigneo_ts_document_extensions', array() ) : array();
			$image_max_size     = gigneo_option( '_gigneo_ts_image_max_size', 1 ) * 1024 * 1024;
			$video_max_size     = gigneo_option( '_gigneo_ts_video_max_size', 1 ) * 1024 * 1024;
			$audio_max_size     = gigneo_option( '_gigneo_ts_audio_max_size', 1 ) * 1024 * 1024;
			$doc_max_size       = gigneo_option( '_gigneo_ts_document_max_size', 1 ) * 1024 * 1024;
			$allowed_extensions = array_merge( $image_extensions, $video_extensions, $audio_extensions, $doc_extensions );

			// Create upload directory
			if ( empty( $chat_upload_dir ) || ( ! file_exists( $chat_upload_dir ) && ! wp_mkdir_p( $chat_upload_dir ) ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Unable to create upload directory.', 'gigneo' ) ), 403 );
			}

			// Validate file count
			$total_files = count( $files['name'] );
			if ( ! isset( $max_files ) || $total_files > $max_files ) {
				wp_send_json_error(
					array(
						'message' => sprintf(
							// translators: 1. Maximum number of files allowed.
							esc_html__( 'You can only upload up to %s files.', 'gigneo' ),
							esc_html( $max_files ?? 1 )
						),
					),
					403
				);
			}

			$files_collection = array();

			for ( $i = 0; $i < $total_files; $i++ ) {

				$error = isset( $files['error'][ $i ] )
					? absint( $files['error'][ $i ] )
					: UPLOAD_ERR_NO_FILE;

				$size = isset( $files['size'][ $i ] )
					? absint( $files['size'][ $i ] )
					: 0;

				$name = isset( $files['name'][ $i ] )
					? sanitize_file_name( wp_unslash( $files['name'][ $i ] ) )
					: '';

				$tmp_name = isset( $files['tmp_name'][ $i ] )
					? sanitize_text_field( $files['tmp_name'][ $i ] )
					: '';

				$file_ext = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );

				// Check upload errors.
				if ( UPLOAD_ERR_OK !== $error ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
								/* translators: %s: File name. */
								esc_html__( 'Error uploading file: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				// Validate uploaded file.
				if ( empty( $tmp_name ) || ! is_uploaded_file( $tmp_name ) ) {
					wp_send_json_error(
						array(
							'message' => esc_html__( 'Invalid uploaded file.', 'gigneo' ),
						),
						403
					);
				}

				// Validate file extension.
				if ( ! isset( $allowed_extensions ) || ! in_array( $file_ext, $allowed_extensions, true ) ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
								/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				$filetype = wp_check_filetype_and_ext( $tmp_name, $name );

				if ( empty( $filetype['ext'] ) || empty( $filetype['type'] ) || $filetype['ext'] !== $file_ext ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
								/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				$is_valid = false;

				// Validate file size.
				if ( 'chat' === $type ) {

					$size_rules = array(
						array( $image_extensions ?? array(), absint( $image_max_size ?? 0 ) ),
						array( $video_extensions ?? array(), absint( $video_max_size ?? 0 ) ),
						array( $audio_extensions ?? array(), absint( $audio_max_size ?? 0 ) ),
						array( $doc_extensions ?? array(), absint( $doc_max_size ?? 0 ) ),
					);

					foreach ( $size_rules as $rule ) {

						list($extensions, $max_size) = $rule;

						if (
							! empty( $extensions ) &&
							in_array( $file_ext, $extensions, true ) &&
							! empty( $max_size ) &&
							$size <= $max_size
						) {
							$is_valid = true;
							break;
						}
					}

					if ( ! $is_valid ) {
						wp_send_json_error(
							array(
								'message' => sprintf(
									/* translators: %s: File name. */
									esc_html__( '%s exceeds the maximum file size for its type.', 'gigneo' ),
									esc_html( $name )
								),
							),
							403
						);
					}
				} elseif ( empty( $max_file_size ) || $size > $max_file_size ) {

					$max_size_mb = ! empty( $max_file_size )
						? ( $max_file_size / ( 1024 * 1024 ) )
						: 1;

					wp_send_json_error(
						array(
							'message' => sprintf(
								/* translators: 1: File name, 2: Maximum file size in MB. */
								esc_html__( '%1$s exceeds the maximum file size of %2$s MB.', 'gigneo' ),
								esc_html( $name ),
								esc_html( $max_size_mb )
							),
						),
						403
					);
				}

				$files_collection[] = array(
					'name'     => $name,
					'tmp_name' => $tmp_name,
					'size'     => $size,
					'ext'      => sanitize_key( $file_ext ),
				);
			}

			// Process files
			foreach ( $files_collection as $file ) {
				$name     = $file['name'];
				$tmp_name = $file['tmp_name'];
				$size     = $file['size'];
				$file_ext = $file['ext'];

				$random_number = wp_rand( 1000, 9999 );
				$filename      = pathinfo( sanitize_file_name( $name ), PATHINFO_FILENAME ) . '-' . $random_number . '.' . $file_ext;
				$filename      = wp_unique_filename( $chat_upload_dir, $filename );
				$filepath      = $chat_upload_dir . $filename;

				if ( ! is_uploaded_file( $tmp_name ) || ! gigneo_move_uploaded_file( $tmp_name, $filepath ) ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
								// translators: 1. File name.
								esc_html__( 'Upload Error: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				$file_path = str_replace( gigneo_upload_dir_path(), '', $filepath );
				$file_url  = str_replace( gigneo_upload_dir_path(), gigneo_upload_dir_url(), $filepath );

				// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				$insert_file = $wpdb->insert(
					$table_attachments,
					array(
						'post_id'         => $message_id,
						'profile_id'      => $sender_id,
						'name'            => $filename,
						'mime_type'       => $file_ext,
						'size'            => $size,
						'guid'            => $file_url,
						'path'            => $file_path,
						'uploaded_at'     => current_time( 'mysql' ),
						'uploaded_at_gmt' => current_time( 'mysql', 1 ),
						'post_type'       => $type,
					),
					array( '%d', '%d', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s' )
				);
				// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

				if ( $insert_file ) {
					$attachment_ids[] = $wpdb->insert_id;
				} else {
					wp_send_json_error(
						array(
							'message' => esc_html__( 'Failed to store file metadata.', 'gigneo' ) . ' (Error: ' . $wpdb->last_error . ')',
						),
						403
					);
				}
			}

			// Update message with attachment IDs
			if ( ! empty( $attachment_ids ) ) {
				// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				$wpdb->update(
					$table_messages,
					array( 'attachments' => maybe_serialize( $attachment_ids ) ),
					array( 'id' => $message_id ),
					array( '%s' ),
					array( '%d' )
				);
				// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			}
		}

		return $message_id;
	}

	/**
	 * Edit an existing message and optionally replace attachments.
	 *
	 * @since 1.0.0
	 *
	 * @param int    $message_id        Message ID.
	 * @param int    $sender_profile_id Sender profile ID.
	 * @param string $new_message       Updated message content.
	 * @param array  $files             Uploaded files array.
	 * @param string $type              Message type.
	 * @param int    $post_id           Related post ID.
	 *
	 * @return int|WP_Error
	 */
	public static function edit_message( $message_id, $sender_profile_id, $new_message, $files = array(), $type = '', $post_id = '' ) {
		global $wpdb;

		$table_messages    = $wpdb->prefix . 'gigneo_messages';
		$table_attachments = $wpdb->prefix . 'gigneo_attachments';

		if ( empty( $sender_profile_id ) ) {
			return new WP_Error( 'missing_required_option', esc_html__( 'Something_went_wrong.', 'gigneo' ) );
		}

		// Fetch the original message
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$original = $wpdb->get_row(
			$wpdb->prepare(
				'SELECT * FROM %i WHERE id = %d',
				$table_messages,
				$message_id
			)
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

		if ( ! $original ) {
			return new WP_Error( 'not_found', esc_html__( 'Message not found.', 'gigneo' ) );
		}

		// Verify ownership
		if ( (int) $original->sender_id !== (int) $sender_profile_id ) {
			return new WP_Error( 'forbidden', esc_html__( 'You are not allowed to edit this message.', 'gigneo' ) );
		}
		$sender_id   = (int) $original->sender_id;
		$receiver_id = (int) $original->receiver_id;

		$allowed_tags = gigneo_allowed_html_tags();

		$new_message = wp_kses( wp_unslash( $new_message ), $allowed_tags );

		// Load blocked keywords
		$block_keywords_raw = gigneo_option( '_gigneo_ts_block_keywords', '' );
		$block_keywords     = array_filter( array_map( 'trim', preg_split( '/[\n,]+/', $block_keywords_raw ) ) );

		$filtered_message = preg_replace_callback(
			'/>([^<]+)</',
			static function ( $matches ) use ( $block_keywords ) {
				$text = $matches[1];
				foreach ( $block_keywords as $word ) {
					$pattern     = '/\b' . preg_quote( $word, '/' ) . '\b/i';
					$replacement = str_repeat( '*', strlen( $word ) );
					$text        = preg_replace( $pattern, $replacement, $text );
				}

				return '>' . $text . '<';
			},
			'>' . $new_message . '<'
		);
		$filtered_message = substr( $filtered_message, 1, -1 );

		$has_files = ! empty( $files ) && ! empty( $files['name'][0] );

		if ( empty( $new_message ) && ! $has_files ) {
			return new WP_Error( 'empty_message', esc_html__( 'Message cannot be empty.', 'gigneo' ) );
		}

		$update_data = array(
			'filter_message' => $filtered_message,
			'status'         => 'edited',
		);

		if ( ! empty( $post_id ) ) {
			$update_data['post_id'] = sanitize_text_field( $post_id );
		}

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$updated = $wpdb->update(
			$table_messages,
			$update_data,
			array( 'id' => $message_id ),
			array( '%s', '%s', '%s', '%s' ),
			array( '%d' )
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

		if ( false === $updated ) {
			return new WP_Error( 'db_update_error', esc_html__( 'Something went wrong.', 'gigneo' ) );
		}

		if ( $has_files && 'chat' === $type ) {
			// Define upload directory based on type
			$enabled_media      = gigneo_option( '_gigneo_ts_enable_media', array() );
			$chat_upload_dir    = gigneo_upload_dir_path( '/' . gmdate( 'Y' ) . '/' . gmdate( 'm' ) . '/chat/messages/' . $sender_id . '/' . $receiver_id . '/' );
			$max_files          = gigneo_option( '_gigneo_ts_chat_max_files', 3 );
			$image_extensions   = in_array( 'images', (array) $enabled_media, true ) ? gigneo_option( '_gigneo_ts_image_extensions', array() ) : array();
			$video_extensions   = in_array( 'videos', (array) $enabled_media, true ) ? gigneo_option( '_gigneo_ts_video_extensions', array() ) : array();
			$audio_extensions   = in_array( 'audios', (array) $enabled_media, true ) ? gigneo_option( '_gigneo_ts_audio_extensions', array() ) : array();
			$doc_extensions     = in_array( 'docs', (array) $enabled_media, true ) ? gigneo_option( '_gigneo_ts_document_extensions', array() ) : array();
			$image_max_size     = gigneo_option( '_gigneo_ts_image_max_size', 1 ) * 1024 * 1024;
			$video_max_size     = gigneo_option( '_gigneo_ts_video_max_size', 1 ) * 1024 * 1024;
			$audio_max_size     = gigneo_option( '_gigneo_ts_audio_max_size', 1 ) * 1024 * 1024;
			$doc_max_size       = gigneo_option( '_gigneo_ts_document_max_size', 1 ) * 1024 * 1024;
			$allowed_extensions = array_merge( $image_extensions, $video_extensions, $audio_extensions, $doc_extensions );

			// Create upload directory
			if ( empty( $chat_upload_dir ) || ( ! file_exists( $chat_upload_dir ) && ! wp_mkdir_p( $chat_upload_dir ) ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Unable to create upload directory.', 'gigneo' ) ), 403 );
			}

			// Validate file count
			$total_files = count( $files['name'] );
			if ( ! isset( $max_files ) || $total_files > $max_files ) {
				wp_send_json_error(
					array(
						'message' => sprintf(
							// translators: 1. Maximum number of files allowed.
							esc_html__( 'You can only upload up to %s files.', 'gigneo' ),
							esc_html( $max_files ?? 1 )
						),
					),
					403
				);
			}

			$files_collection = array();
			for ( $i = 0; $i < $total_files; $i++ ) {

				$error = isset( $files['error'][ $i ] )
					? absint( $files['error'][ $i ] )
					: UPLOAD_ERR_NO_FILE;

				$size = isset( $files['size'][ $i ] )
					? absint( $files['size'][ $i ] )
					: 0;

				$name = isset( $files['name'][ $i ] )
					? sanitize_file_name( wp_unslash( $files['name'][ $i ] ) )
					: '';

				$tmp_name = isset( $files['tmp_name'][ $i ] )
					? sanitize_text_field( $files['tmp_name'][ $i ] )
					: '';

				$file_ext = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );

				// Check upload errors.
				if ( UPLOAD_ERR_OK !== $error ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
								/* translators: %s: File name. */
								esc_html__( 'Error uploading file: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				// Validate uploaded file.
				if ( empty( $tmp_name ) || ! is_uploaded_file( $tmp_name ) ) {
					wp_send_json_error(
						array(
							'message' => esc_html__( 'Invalid uploaded file.', 'gigneo' ),
						),
						403
					);
				}

				// Validate file extension.
				if ( ! isset( $allowed_extensions ) || ! in_array( $file_ext, $allowed_extensions, true ) ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
								/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				$filetype = wp_check_filetype_and_ext( $tmp_name, $name );

				if ( empty( $filetype['ext'] ) || empty( $filetype['type'] ) || $filetype['ext'] !== $file_ext ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
								/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				// Validate file size.
				$is_valid = false;

				if ( 'chat' === $type ) {

					$rules = array(
						array( $image_extensions ?? array(), absint( $image_max_size ?? 0 ) ),
						array( $video_extensions ?? array(), absint( $video_max_size ?? 0 ) ),
						array( $audio_extensions ?? array(), absint( $audio_max_size ?? 0 ) ),
						array( $doc_extensions ?? array(), absint( $doc_max_size ?? 0 ) ),
					);

					foreach ( $rules as $rule ) {

						list($extensions, $max_size) = $rule;

						if (
							! empty( $extensions ) &&
							in_array( $file_ext, $extensions, true ) &&
							! empty( $max_size ) &&
							$size <= $max_size
						) {
							$is_valid = true;
							break;
						}
					}

					if ( ! $is_valid ) {
						wp_send_json_error(
							array(
								'message' => sprintf(
									/* translators: %s: File name. */
									esc_html__( '%s exceeds the maximum file size for its type.', 'gigneo' ),
									esc_html( $name )
								),
							),
							403
						);
					}
				} elseif ( empty( $max_file_size ) || $size > $max_file_size ) {

					$max_size_mb = ! empty( $max_file_size )
						? ( $max_file_size / ( 1024 * 1024 ) )
						: 1;

					wp_send_json_error(
						array(
							'message' => sprintf(
								/* translators: 1: File name, 2: Maximum file size in MB. */
								esc_html__( '%1$s exceeds the maximum file size of %2$s MB.', 'gigneo' ),
								esc_html( $name ),
								esc_html( $max_size_mb )
							),
						),
						403
					);
				}

				$files_collection[] = array(
					'name'     => $name,
					'tmp_name' => $tmp_name,
					'size'     => $size,
					'ext'      => sanitize_key( $file_ext ),
				);
			}

			// Process files
			foreach ( $files_collection as $file ) {
				$name     = $file['name'];
				$tmp_name = $file['tmp_name'];
				$size     = $file['size'];
				$file_ext = $file['ext'];

				$random_number = wp_rand( 1000, 9999 );
				$filename      = pathinfo( sanitize_file_name( $name ), PATHINFO_FILENAME ) . '-' . $random_number . '.' . $file_ext;
				$filename      = wp_unique_filename( $chat_upload_dir, $filename );
				$filepath      = $chat_upload_dir . $filename;

				if ( ! is_uploaded_file( $tmp_name ) || ! gigneo_move_uploaded_file( $tmp_name, $filepath ) ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
								// translators: 1. File name.
								esc_html__( 'Upload Error: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				$file_path = str_replace( gigneo_upload_dir_path(), '', $filepath );
				$file_url  = str_replace( gigneo_upload_dir_path(), gigneo_upload_dir_url(), $filepath );

				// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				$insert_file = $wpdb->insert(
					$table_attachments,
					array(
						'post_id'         => $message_id,
						'profile_id'      => $sender_id,
						'name'            => $filename,
						'mime_type'       => $file_ext,
						'size'            => $size,
						'guid'            => $file_url,
						'path'            => $file_path,
						'uploaded_at'     => current_time( 'mysql' ),
						'uploaded_at_gmt' => current_time( 'mysql', 1 ),
						'post_type'       => $type,
					),
					array( '%d', '%d', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s' )
				);
				// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

				if ( $insert_file ) {
					$attachment_ids[] = $wpdb->insert_id;
				} else {
					wp_send_json_error(
						array(
							'message' => esc_html__( 'Failed to store file metadata.', 'gigneo' ),
						),
						403
					);
				}
			}

			// Update message with attachment IDs
			if ( ! empty( $attachment_ids ) ) {
				// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				$wpdb->update(
					$table_messages,
					array( 'attachments' => maybe_serialize( $attachment_ids ) ),
					array( 'id' => $message_id ),
					array( '%s' ),
					array( '%d' )
				);
				// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			}
		}

		return $message_id;
	}

	/**
	 * Get unread message count for a profile and type.
	 *
	 * @since 1.0.0
	 *
	 * @param int    $profile_id Profile ID.
	 * @param string $type       Message type.
	 * @param int    $order_id   Related order/post ID.
	 *
	 * @return int
	 */
	public static function unread_messages_count( $profile_id, $type, $order_id = '' ) {
		global $wpdb;
		$table_messages = $wpdb->prefix . 'gigneo_messages';

		$query = 'SELECT COUNT(*) FROM %i WHERE receiver_id = %d AND status = %s AND type = %s';
		$args  = array(
			$table_messages,
			absint( $profile_id ),
			'unread',
			sanitize_text_field( $type ),
		);

		if ( ! empty( $order_id ) ) {
			$query .= ' AND post_id = %d';
			$args[] = absint( $order_id );
		}

		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
		return (int) $wpdb->get_var( $wpdb->prepare( $query, $args ) );
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
	}
}