<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\Chatbot;
use Limb_Chatbot\Includes\Data_Objects\Config;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Data_Objects\Message_Meta;
use Limb_Chatbot\Includes\Database_Strategies\WPDB;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Repositories\Message_Repository;
use Limb_Chatbot\Includes\Repositories\Widget_Item_Repository;
use Limb_Chatbot\Includes\Widgets\Items\Message_Widget_Item;


/**
 * Service for managing chat messages.
 *
 * @since 1.0.0
 */
class Message_Service {

	/**
	 * The chat instance.
	 *
	 * @var Chat
	 * @since 1.0.0
	 */
	public Chat $chat;

	/**
	 * Message repository instance.
	 *
	 * @var Message_Repository|null
	 * @since 1.0.0
	 */
	public ?Message_Repository $repository;

	/**
	 * Widget item repository instance.
	 *
	 * @var Widget_Item_Repository
	 * @since 1.0.0
	 */
	protected Widget_Item_Repository $widget_item_repository;

	/**
	 * Constructor.
	 *
	 * @param Chat $chat Chat instance.
	 * @param Message_Repository|null $repository Optional message repository.
	 * @since 1.0.0
	 */
	public function __construct( Chat $chat, ?Message_Repository $repository = null ) {
		$this->chat                   = $chat;
		$this->repository             = $repository;
		$this->widget_item_repository = new Widget_Item_Repository();
	}

	/**
	 * Creates a new message.
	 *
	 * @param array $data Message data.
	 * @param bool $preview Whether the message is a preview.
	 * @return Message|null Created message or null.
	 * @throws Exception|\Exception Throws if usage limits exceeded or preparation fails.
	 * @since 1.0.0
	 */
	public function create( array $data, bool $preview = false ): ?Message {
		$wpdb = new WPDB();
		$wpdb->start_transaction();
		try {
			$wpdb->lock_row( $this->chat );
			// Sanitize content before preparing it (safety check in case content didn't go through REST API)
			if ( ! empty( $data['content'] ) && is_array( $data['content'] ) ) {
				$messages_controller = new \Limb_Chatbot\Includes\Api\V1\Controllers\Chat_Messages_Controller();
				$data['content'] = $messages_controller->sanitize_content( $data['content'] );
			}
			$data['content']   = $this->prepare_content( $data['content'], $this->chat->get_chatbot() );
			$data['chat_uuid'] = $this->chat->get_uuid();
			$data['user_id']   = User_Manager::instance()->current_user->get_id();
			$message           = $this->repository->make( $data );
			$chatbot           = $this->chat->get_chatbot();
			if ( $preview && User_Manager::instance()->current_user->wp_can( 'manage_options' ) ) {
				$chatbot->set_preview( $preview );
			}
			// Check usage limits only if not in an ongoing action
			// (action parameter validation is handled in Action_Service::check_ongoing_action)
			if ( ! $this->chat->current_action_plan() || ! $this->chat->is_live_agent_active() ) {
				$usage_service = $chatbot->get_parameter( 'limits' ) instanceof Collection ? new Usage_Service( $chatbot->get_parameter( 'limits' ) ) : null;
				if ( $usage_service && $limit = $usage_service->limits_passed( $message ) ) {
					throw new Exception( Error_Codes::CHATBOT_LIMIT_EXCEED,
						$usage_service->get_error_message( $limit ) );
				}
				$this->save_possible_assistant_messages();
			}
			$message->save();
//			if ( $message->is_first_user_message() || ! $this->chat->has_language() ) {
//				Chat_Language_Service::detect_language( $chatbot, $this->chat, $message );
//			}
			$this->chat->update_meta( 'admin_read', false );
			$this->set_chat_name( $message );
			$this->maybe_log_to_live_agent( $message );
			$wpdb->commit_transaction();
			$this->chat->touch();

			return $message;
		} catch ( \Exception $e ) {
			$wpdb->rollback_transaction();
			throw $this->format_error( $e );
		}
	}

	/**
	 * Saves assistant messages if none exist.
	 *
	 * @return void
	 * @throws \Exception
	 * @since 1.0.0
	 */
	protected function save_possible_assistant_messages(): void {
		if ( ! $this->chat->get_messages_count() ) {
			if ( $widgets = $this->chat->get_chatbot()->get_parameter( 'widgets' ) ) {
				if ( $widgets instanceof Widget_Collection && $message_widget_items = $this->widget_item_repository->get_message_items( $widgets ) ) {
					$content = [];
					foreach ( $message_widget_items->get() as $message_item ) {
						if ( ! $message_item instanceof Message_Widget_Item || ! $message_item->is_published() ) {
							continue;
						}
						$new_chat = false;
						foreach ( $message_item->locations as $location ) {
							if ( array_key_exists( 0, $location )
							     && is_array( $location[0] )
							     && isset( $location[0]['param'] )
							     && isset( $location[0]['value'] ) ) {
								if ( $location[0]['param'] === 'screen' && $location[0]['value'] === 'new_chat' ) {
									$new_chat = true;
									break;
								}
							}
						}
						if ( $new_chat ) {
							$content = array_merge( $content, $message_item->get_content() );
						}
					}
					if ( ! empty( $content ) ) {
						$new_assistant_message = new Message();
						$new_assistant_message->set_role( Message::ROLE_ASSISTANT );
						$new_assistant_message->set_content( $content );
						$new_assistant_message->set_chat_uuid( $this->chat->get_uuid() );
						$new_assistant_message->save();
					}
				}
			}
		}
	}

	/**
	 * Retrieves message items according to parameters.
	 *
	 * @param mixed $params Query parameters.
	 * @return Collection|null Collection of messages or null.
	 * @since 1.0.0
	 */
	public function get_items( $params ): ?Collection {
		return $this->repository->get_items( $this->chat, $params );
	}

	/**
	 * Prepares message content, uploading attachments if needed.
	 *
	 * @param mixed $content The content to prepare.
	 * @param Chatbot $chatbot Chatbot instance.
	 * @return string JSON encoded prepared content.
	 * @throws Exception Throws if content preparation fails.
	 * @since 1.0.0
	 */
	public function prepare_content( $content, Chatbot $chatbot ): string {
		// Media attachment storage scheme is enabled, then we should upload the image into the media
		$content = wp_json_encode( $content );
		if ( empty( $content ) ) {
			throw new Exception( Error_Codes::USER_MESSAGE_PREPARATION_FAILED, __( 'Array message failed', 'limb-chatbot' ) );
		}

		return $content;
	}

	/**
	 * Sets the chat name based on the first user text message.
	 *
	 * @param  Message|null  $message  The message to extract name from.
	 *
	 * @return void
	 * @throws \Exception
	 * @since 1.0.0
	 */
	protected function set_chat_name( ?Message $message ): void {
		if ( empty( $message ) || $this->chat->get_name() || ! $message->role_is_user() ) {
			return;
		}
		$content = Helper::maybe_json_decode( $message->get_content() );
		foreach ( $content as $item ) {
			if ( $item['type'] === 'text' ) {
				$this->chat->set_name( $item['text']['value'] );
				$this->chat->save();
				break;
			}
		}
	}

	/**
	 * Deletes messages and associated metas by criteria.
	 *
	 * @param mixed $where Conditions to find messages to delete.
	 * @return bool True if delete succeeded, false otherwise.
	 * @since 1.0.0
	 */
	public function delete( $where ): bool {
		$messages = Message::where( $where )->get();
		$delete   = $this->repository->delete( $where );
		if ( $delete ) {
			foreach ( $messages as $message ) {
				$message_id = $message->get_id();
				if ( $message_id ) {
					Message_Meta::delete( [ 'message_id' => $message_id ] );
				}
			}
		}

		return $delete;
	}

	private function format_error( \Exception $e ): \Exception {
		$error = Helper::get_wp_error( $e );
		if ( $code = $e->getCode() ) {
			if ( in_array( $code, [ Error_Codes::CHATBOT_LIMIT_EXCEED, Error_Codes::USER_MESSAGE_LIMIT_EXCEED ] ) ) {
				return $e;
			}
		}

		return new Exception( Error_Codes::USER_MESSAGE_FAILED, $error->get_error_message() );
	}

	/**
	 * Log message to live agent integration if active.
	 *
	 * @param Message $message Message to log.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	private function maybe_log_to_live_agent( Message $message ): void {
		try {
			$live_agent_service = new Live_Agent_Service();
			if ( ! $live_agent_service->is_live_agent_active( $this->chat ) ) {
				return;
			}

			$config_id = $this->chat->get_meta( Live_Agent_Service::META_LIVE_AGENT_CONFIG_ID );
			if ( ! $config_id ) {
				return;
			}

			$config = Config::find( $config_id );
			if ( ! $config ) {
				return;
			}

			// Route to appropriate integration
			$live_agent_service->send_message( $this->chat, $message, $config );
		} catch ( \Exception $e ) {
			// Silently fail - don't break the conversation
			error_log( sprintf( 'Failed to log message to live agent: %s', $e->getMessage() ) );
		}
	}
}