<?php

namespace Limb_Chatbot\Includes\Services\Actions;

use Exception;
use Limb_Chatbot\Includes\Data_Objects\Action_Plan;
use Limb_Chatbot\Includes\Data_Objects\Dataset;
use Limb_Chatbot\Includes\Data_Objects\Dataset_Entry;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Data_Objects\Parameter;
use Limb_Chatbot\Includes\Data_Objects\Vector_Index;
use Limb_Chatbot\Includes\Factories\Recommendation_Search_Processor_Factory;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Services\User_Manager;
use Limb_Chatbot\Includes\Services\User_Roles;
use Limb_Chatbot\Includes\Utilities\Chatbot_Utility;
use Limb_Chatbot\Includes\Utilities\Copilot_Utility;
use WP_Post;
use WP_REST_Request;
use WP_REST_Response;

class Search_Results_Provider {

	const DEFAULT_SIMILARITY_SCORE = 33;
	private const ROLE_EVERYONE = 'everyone';
	private Chatbot_Utility $chatbot_utility;
	private Recommendation_Search_Processor_Factory $search_processor_factory;

	public function __construct( Chatbot_Utility $chatbot_utility ) {
		$this->chatbot_utility          = $chatbot_utility;
		$this->search_processor_factory = new Recommendation_Search_Processor_Factory();
	}

	public function get_from_last_message( Action_Plan $action_plan, Parameter $parameter ) {
		// Get the last message from chat
		$last_message_obj = $this->chatbot_utility->chat ? $this->chatbot_utility->chat->get_last_user_message() : null;
		if ( ! $last_message_obj ) {
			return [];
		}
		// Extract text from message
		$last_message_text = $last_message_obj->extract_text();
		if ( empty( $last_message_text ) ) {
			return [];
		}
		// Check if this is for the data collect step
		$first_step = $action_plan->get_first_step();
		if ( ! $first_step || ! $first_step->is_data_collect_step() ) {
			return [];
		}
		// Verify the parameter matches the collect parameter
		$collect_parameter = $first_step->get_data()['parameter'] ?? null;
		if ( ! $collect_parameter || $collect_parameter !== $parameter ) {
			return [];
		}
		// Refine the search query using AI
		try {
			$ai_model = $this->chatbot_utility->get_ai_model();
			$config   = $this->chatbot_utility->get_config();
			$copilot  = new Copilot_Utility( $ai_model, $config );
			// Refine the search query via AI
			try {
				$refined_search_query = $copilot->refine_search_query( $last_message_text );
				$refined_text         = $refined_search_query instanceof Message ? $refined_search_query->extract_text() : 'none';
			} catch ( Exception $e ) {
				$refined_text = 'none';
			}
			// Extract the refined query text
			// If AI returns "none", it means no search intent was detected
			if ( 'none' === trim( strtolower( $refined_text ) ) ) {
				$refined_text = '';
			}
			// Create a message object for vector search
			$search_query_message = Message::make( [
				'content'   => [
					[
						'type' => 'text',
						'text' => [
							'value' => $refined_text,
						],
					],
				],
				'role'      => Message::ROLE_USER,
				'chat_uuid' => $this->chatbot_utility->chat->get_uuid(),
			] );

			// Perform vector search with the refined query
			return $this->get_results( $parameter, $search_query_message );
		} catch ( Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return [];
		}
	}

	public function get_results( ?Parameter $parameter, $search_query, $format = true, $entry = false ) {
		$search_query_text = $search_query->extract_text();
		$cpt_name          = Helper::underscore_to_hyphen( $parameter->get_config_value( 'cpt' ) );
		$vector_indexes    = Vector_Index::where( [
			'name' => "lbaiccpt{$cpt_name}index"
		] );
		if ( $vector_indexes->is_empty() ) {
			// Return structure with search_query even when no index found
			return [
				'search_query' => $search_query_text,
				'results'      => [],
			];
		}

		$vector_index = null;
		foreach ( $vector_indexes->get() as $index ) {
			if ( $index instanceof Vector_Index && $index->is_actionable() ) {
				$vector_index = $index;
				break;
			}
		}

		$processor = $this->search_processor_factory->make( $search_query_text );

		if ( ! empty( $vector_index ) ) {
			$similarity_score = $parameter->get_config_value( 'similarity_score' ) ?? self::DEFAULT_SIMILARITY_SCORE;
			$items = $processor->search(
				$this->chatbot_utility,
				$search_query,
				$vector_index,
				$similarity_score
			);

			if ( ! empty( $items ) ) {
				$results = [];
				foreach ( $items as $item ) {
					if ( ! $item instanceof Dataset_Entry ) {
						continue;
					}
					if ( ! $this->dataset_entry_is_accessible_for_current_user( $item ) ) {
						continue;
					}
					if ( $entry ) {
						$res = $item;
					} else {
						$res = $this->format_item( $item, $format );
					}
					if ( $res ) {
						$results[] = $res;
					}
				}

				return array(
					'search_query' => $search_query_text,
					'results'      => $results,
				);
			}
		}

		// Return structure with search_query even when no results found
		return [
			'search_query' => $search_query_text,
			'results'      => [],
		];
	}

	/**
	 * Enforce dataset access roles at actionable result formatting time.
	 *
	 * This mirrors the effective informational behavior and also protects against
	 * stale vectors that may still carry outdated access_roles from earlier indexing.
	 */
	private function dataset_entry_is_accessible_for_current_user( Dataset_Entry $dataset_entry ): bool {
		$dataset = $dataset_entry->dataset();
		if ( ! $dataset instanceof Dataset ) {
			return false;
		}

		$allowed_roles = $this->normalize_dataset_roles( $dataset->get_meta_value( 'user_roles' ) );
		if ( empty( $allowed_roles ) || in_array( self::ROLE_EVERYONE, $allowed_roles, true ) ) {
			return true;
		}

		$current_user_roles = $this->resolve_current_chatbot_user_roles();
		if ( empty( $current_user_roles ) ) {
			return false;
		}

		return ! empty( array_intersect( $allowed_roles, $current_user_roles ) );
	}

	/**
	 * @param  mixed  $roles
	 *
	 * @return string[]
	 */
	private function normalize_dataset_roles( $roles ): array {
		if ( null === $roles || false === $roles || '' === $roles ) {
			return [];
		}

		if ( is_string( $roles ) ) {
			$decoded = json_decode( $roles, true );
			if ( is_array( $decoded ) ) {
				$roles = $decoded;
			} else {
				$roles = str_contains( $roles, ',' ) ? array_map( 'trim', explode( ',', $roles ) ) : [ $roles ];
			}
		}

		if ( ! is_array( $roles ) ) {
			return [];
		}

		return User_Roles::normalize_roles( $roles );
	}

	/**
	 * @return string[]
	 */
	private function resolve_current_chatbot_user_roles(): array {
		$chatbot_user = User_Manager::instance()->get_current_user();
		if ( ! $chatbot_user ) {
			return [ User_Roles::ROLE_VISITORS ];
		}

		return User_Roles::resolve_current_chatbot_user_roles( $chatbot_user );
	}

	/**
	 * Converts a dataset entry into its REST representation.
	 */
	private function format_item( Dataset_Entry $dataset_entry, $format = true ) {
		$dataset = $dataset_entry->dataset();
		if (!$dataset instanceof Dataset){
			return [];
		}

		if ( ! $format ) {
			return apply_filters( 'lbaic_search_results_' . $dataset->get_source_sub_type() . '_source_object', $dataset );
		}
		$source = Helper::resolve_source_object( $dataset->get_source_type(), $dataset->get_source() );
		if ( ! $source instanceof WP_Post ) {
			return [];
		}

		$result       = $this->get_post_rest_representation( $source );
		$thumbnail_id = get_post_thumbnail_id( $source->ID );

		// Get all available image sizes
		$result['thumbnail_urls'] = [];
		if ( $thumbnail_id ) {
			// Get all registered image sizes
			$image_sizes = get_intermediate_image_sizes();
			// Add full size as well
			$image_sizes[] = 'full';

			foreach ( $image_sizes as $size ) {
				$image_src = wp_get_attachment_image_src( $thumbnail_id, $size );
				if ( $image_src ) {
					$result['thumbnail_urls'][ $size ] = [
						'url'    => $image_src[0],
						'width'  => $image_src[1],
						'height' => $image_src[2],
					];
				}
			}
		}

		return apply_filters( 'lbaic_vector_search_format_search_result_item', $result, $source );
	}

	/**
	 * Returns the REST API representation for a post.
	 * Supports:
	 *   - WooCommerce products (full WC v3 schema)
	 *   - Custom post types (with or without show_in_rest)
	 *   - Standard posts
	 */
	/**
	 * Returns the REST API representation for any WP_Post.
	 * Products use WooCommerce's own controller so the data matches /wc/v3/products/{id}.
	 */
	private function get_post_rest_representation( WP_Post $post ): array {
		try {
			return $this->get_fallback_post_representation( $post );
		} catch ( \Throwable $e ) {
			Helper::log( $e, __METHOD__ );

			return [];
		}
	}

	/**
	 * Fallback REST representation for CPTs not exposed via REST.
	 */
	private function get_fallback_post_representation( WP_Post $post ): array {
		try {
			if ( ! class_exists( \WP_REST_Posts_Controller::class ) ) {
				return [];
			}

			$controller = new \WP_REST_Posts_Controller( $post->post_type );
			$response   = $controller->prepare_item_for_response( $post, new WP_REST_Request() );

			return $response instanceof WP_REST_Response ? $response->get_data() : (array) $response;
		} catch ( \Throwable $e ) {
			Helper::log( $e, __METHOD__ );

			return [];
		}
	}
}

