<?php

namespace Limb_Chatbot\Includes\Repositories;

use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\Chat_Meta;
use Limb_Chatbot\Includes\Data_Objects\Chatbot;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Services\Collection;
use Limb_Chatbot\Includes\Services\Data_Object_Collection;

/**
 * Repository class for managing Chat records.
 *
 * @since 1.0.0
 */
class Chat_Repository {

	/**
	 * Creates a new Chat record for a given chatbot and user.
	 *
	 * @param  array  $data  Chat attributes to store.
	 * @param  Chatbot  $chatbot  The related chatbot object.
	 *
	 * @return Chat|null The created Chat object or null on failure.
	 * @throws \Exception
	 * @since 1.0.0
	 *
	 */
	public function create( array $data, Chatbot $chatbot ): ?Chat {
		$data['chatbot_uuid'] = $chatbot->uuid;
		$data['chatbot_id']   = $chatbot->id;

		return Chat::create( $data );
	}

	/**
	 * Retrieve a collection of chats based on given parameters.
	 *
	 * Supports pagination, ordering, and prioritized search.
	 * Search priority: User name/email → Messages content → Chat name
	 *
	 * @since 1.0.0
	 *
	 * @param array $params {
	 *     Optional. Parameters to filter chats.
	 *
	 *     @type int    $per_page       Number of items per page.
	 *     @type int    $page           Page number.
	 *     @type string $orderby        Field to order by.
	 *     @type string $order          Order direction (ASC|DESC).
	 *     @type string $search         Search term.
	 *     @type array  $include        Relationships to include.
	 * }
	 * @return Collection Chat collection object.
	 */
	public function get_items( $params ) {
		$params = $this->exclude_preview_chats_in_transcripts( $params );

		// Sanitize and validate input parameters
		$per_page = isset( $params['per_page'] ) ? intval( $params['per_page'] ) : 10;
		$page     = isset( $params['page'] ) ? absint( $params['page'] ) : 1;

		// Allow -1 to disable pagination, otherwise ensure positive value.
		if ( $per_page !== -1 ) {
			$per_page = absint( $per_page ) ?: 10;
		}
		$order_by = isset( $params['orderby'] ) ? sanitize_key( $params['orderby'] ) : 'updated_at';
		$order    = isset( $params['order'] ) ? strtoupper( sanitize_key( $params['order'] ) ) : 'DESC';

		// Validate order direction
		if ( ! in_array( $order, [ 'ASC', 'DESC' ], true ) ) {
			$order = 'DESC';
		}

		// Validate orderby field
		$allowed_orderby = [ 'id', 'name', 'created_at', 'updated_at' ];
		if ( ! in_array( $order_by, $allowed_orderby, true ) ) {
			$order_by = 'created_at';
		}

		// Handle search with priority
		if ( ! empty( $params['search'] ) ) {
			$search_term = sanitize_text_field( $params['search'] );
			unset( $params['search'] );

			if ( ! empty( $search_term ) ) {
				// Use a single optimized query with priority ordering
				$items = $this->search_chats_with_priority( $search_term, $params, $per_page, $page, $order_by, $order );
			} else {
				$items = new Data_Object_Collection( [] );
			}
		} else {
			// Regular query without search
			if ( ! empty( $params['start_date'] ) ) {
				$params['created_at>='] = sanitize_text_field( $params['start_date'] );
				unset( $params['start_date'] );
			}
			if ( ! empty( $params['end_date'] ) ) {
				$params['created_at<='] = sanitize_text_field( $params['end_date'] );
				unset( $params['end_date'] );
			}
			$items = Chat::where( $params, $per_page, $page, $order_by, $order );
		}

		if ( $items->is_empty() ) {
			return $items;
		}
		if ( ! empty( $params['include'] ) && is_array( $params['include'] ) ) {
			if ( in_array( 'messages', $params['include'] ) ) {
				$items = $items->map( function ( Chat $item ) {
					$messages                   = Message::where( [ 'chat_uuid' => $item->uuid ], 1, 1, 'id', 'desc' );
					$messages                   = $messages->with( 'agent' );
					$item->included['messages'] = $messages;

					return $item;
				} );
				$params['include'] = array_values( array_diff( $params['include'], [ 'messages' ] ) );
			}
			if ( ! empty( $params['include'] ) ) {
				$items = $items->with( $params['include'] );
			}
		}

		return $items;
	}

	/**
	 * Exclude preview chats only for transcript tab requests.
	 *
	 * @param array $params Query params.
	 *
	 * @return array
	 */
	private function exclude_preview_chats_in_transcripts( array $params ): array {
		if ( ! (bool) ( $params['_exclude_preview_chats'] ?? false ) ) {
			return $params;
		}

		$preview_metas = Chat_Meta::where( [ 'meta_key' => 'preview' ], -1, -1 );
		if ( $preview_metas->is_empty() ) {
			return $params;
		}
		$preview_chat_ids = array_map( 'absint', $preview_metas->pluck( 'chat_id' ) );
		if ( empty( $preview_chat_ids ) ) {
			return $params;
		}

		$params['id NOT IN'] = $preview_chat_ids;

		return $params;
	}

	/**
	 * Updates chat records matching the given conditions.
	 *
	 * @since 1.0.0
	 *
	 * @param array $where Where clause conditions.
	 * @param array $data Data to update.
	 * @return Chat Success status.
	 */
	public function update( $where, $data ) {
		return Chat::update( $where, $data );
	}

	public function get_item( string $uuid, ?array $include ) {
		$chat = Chat::find_by_uuid($uuid);
		if ( ! empty( $include ) ) {
			if ( in_array( 'messages', $include ) ) {
				$messages                   = Message::where( [ 'chat_uuid' => $uuid ], 100, 1, 'id', 'desc' );
				$messages                   = $messages->with( 'agent' );
				$chat->included['messages'] = $messages;
			}
			if ( in_array( 'metas', $include ) ) {
				$chat = $chat->with( 'metas' );
			}
			if ( in_array( 'messages_count', $include ) ) {
				$chat = $chat->with( 'messages_count' );
			}
			if ( in_array( 'user', $include ) ) {
				$chat = $chat->with( 'user' );
			}
		}

		return $chat;
	}

	/**
	 * Returns a human-readable chat title based on current timestamp.
	 *
	 * @since 1.0.0
	 *
	 * @return string Chat title.
	 */
	protected function get_title(): string {
		// translators: %s is the formatted date/time the chat was created.
		return sprintf( __( 'Chat &ndash; %s', 'limb-chatbot' ), ( new \DateTime( 'now' ) )->format( _x( 'M d, Y @ h:i A', 'Chat creation date parsed by DateTime::format', 'limb-chatbot' ) ) );
	}

	/**
	 * Retrieve a chat by its UUID.
	 *
	 * @since 1.0.0
	 *
	 * @param string $uuid Chat UUID.
	 * @return Chat|null The found chat or null if not found.
	 */
	public function get_by_uuid( string $uuid ): ?Chat {
		return Chat::find_by_uuid( $uuid );
	}

	/**
	 * Search chats with priority using a simple and reliable approach.
	 * Priority: User name/email → Messages content → Chat name
	 *
	 * @since 1.0.0
	 *
	 * @param string $search_term The search term.
	 * @param array $params Additional query parameters.
	 * @param int $per_page Items per page.
	 * @param int $page Page number.
	 * @param string $order_by Order by field.
	 * @param string $order Order direction.
	 * @return Data_Object_Collection Collection of chat objects.
	 */
	private function search_chats_with_priority( string $search_term, array $params, int $per_page, int $page, string $order_by, string $order ): Data_Object_Collection {
		// Validate inputs
		$search_term = sanitize_text_field( $search_term );
		$per_page = absint( $per_page );
		$page = absint( $page );
		$order_by = sanitize_key( $order_by );
		$order = strtoupper( sanitize_key( $order ) );

		if ( empty( $search_term ) || $per_page <= 0 || $page <= 0 ) {
			return new Data_Object_Collection( [] );
		}

		// Get matching chat IDs with priority
		$matching_chat_ids = $this->get_search_matching_chat_ids( $search_term );

		if ( empty( $matching_chat_ids ) ) {
			return new Data_Object_Collection( [] );
		}

		// Add the matching chat IDs to the params
		$params['id'] = array_map( 'absint', $matching_chat_ids );

		// Handle date filters with proper sanitization
		if ( ! empty( $params['start_date'] ) ) {
			$params['created_at>='] = sanitize_text_field( $params['start_date'] );
			unset( $params['start_date'] );
		}
		if ( ! empty( $params['end_date'] ) ) {
			$params['created_at<='] = sanitize_text_field( $params['end_date'] );
			unset( $params['end_date'] );
		}

		// Get the chats using the existing Chat::where method
		$items = Chat::where( $params, $per_page, $page, $order_by, $order );

		return $items;
	}

	/**
	 * Get chat IDs that match the search term with priority.
	 * Priority: User name/email → Messages content → Chat name
	 *
	 * @since 1.0.0
	 *
	 * @param string $search_term The search term.
	 * @return array Array of chat IDs ordered by priority.
	 */
	public function get_search_matching_chat_ids( string $search_term ): array {
		// Sanitize and validate input
		$search_term = sanitize_text_field( $search_term );
		if ( empty( $search_term ) ) {
			return [];
		}

		global $wpdb;

		$search_term = '%' . $wpdb->esc_like( $search_term ) . '%';
		$matching_ids = [];

		// Priority 1: Search by user name/email
		$user_matches = $wpdb->get_results( $wpdb->prepare( "
			SELECT DISTINCT c.id
			FROM {$wpdb->prefix}lbaic_chats c
			INNER JOIN {$wpdb->prefix}lbaic_chat_participants cp ON c.id = cp.chat_id
			INNER JOIN {$wpdb->prefix}lbaic_users u ON cp.user_id = u.id
			LEFT JOIN {$wpdb->prefix}users wp_users ON u.wp_user_id = wp_users.ID
			LEFT JOIN {$wpdb->prefix}lbaic_user_metas um_name ON u.id = um_name.chatbot_user_id AND um_name.meta_key = %s
			LEFT JOIN {$wpdb->prefix}lbaic_user_metas um_email ON u.id = um_email.chatbot_user_id AND um_email.meta_key = %s
			WHERE cp.role = %s 
			AND (
				um_name.meta_value LIKE %s 
				OR um_email.meta_value LIKE %s 
				OR wp_users.display_name LIKE %s 
				OR wp_users.user_email LIKE %s
			)
		", 'name', 'email', 'user', $search_term, $search_term, $search_term, $search_term ), ARRAY_A );

		if ( $user_matches ) {
			foreach ( $user_matches as $match ) {
				$matching_ids[ absint( $match['id'] ) ] = 1; // Priority 1
			}
		}

		// Priority 2: Search by messages content
		$message_matches = $wpdb->get_results( $wpdb->prepare( "
			SELECT DISTINCT c.id
			FROM {$wpdb->prefix}lbaic_chats c
			INNER JOIN {$wpdb->prefix}lbaic_chat_messages m ON c.uuid = m.chat_uuid
			WHERE m.content LIKE %s
		", $search_term ), ARRAY_A );

		if ( $message_matches ) {
			foreach ( $message_matches as $match ) {
				$chat_id = absint( $match['id'] );
				if ( ! isset( $matching_ids[ $chat_id ] ) ) {
					$matching_ids[ $chat_id ] = 2; // Priority 2
				}
			}
		}

		// Priority 3: Search by chat name
		$chat_name_matches = $wpdb->get_results( $wpdb->prepare( "
			SELECT DISTINCT c.id
			FROM {$wpdb->prefix}lbaic_chats c
			WHERE c.name LIKE %s
		", $search_term ), ARRAY_A );

		if ( $chat_name_matches ) {
			foreach ( $chat_name_matches as $match ) {
				$chat_id = absint( $match['id'] );
				if ( ! isset( $matching_ids[ $chat_id ] ) ) {
					$matching_ids[ $chat_id ] = 3; // Priority 3
				}
			}
		}

		// Sort by priority (1 = highest priority) and return just the IDs
		asort( $matching_ids );
		return array_keys( $matching_ids );
	}
}