<?php

namespace Limb_Chatbot\Includes\Integrations\Slack\Services;

use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\Chatbot_User_Meta;
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\Integrations\Slack\Utilities\Messages_Utility;
use Limb_Chatbot\Includes\Services\Collection;
use Limb_Chatbot\Includes\Services\Data_Object_Collection;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Services\Live_Agent_Service;
use Limb_Chatbot\Includes\Utilities\Slack_Messages_Utility;

/**
 * Slack Message Sync Service
 *
 * Handles polling-based message synchronization from Slack channels.
 * Fetches new messages using conversations.history API.
 *
 * @package Limb_Chatbot\Includes\Integrations\Slack\Services
 * @since 1.0.0
 */
class Slack_Message_Sync_Service {

	/**
	 * Sync messages from Slack channel to database.
	 *
	 * @param  Chat  $chat  Chat object.
	 * @param  Config  $config
	 *
	 * @return Collection Number of new messages synced.
	 * @since 1.0.0
	 */
	public function sync_messages( Chat $chat, Config $config ): Collection {
		$collection = new Data_Object_Collection();
		try {
			// Get Slack channel ID
			$channel_id = $chat->get_meta( Slack_Live_Agent_Service::META_LIVE_AGENT_CHANNEL_ID );
			if ( empty( $channel_id ) ) {
				return $collection;
			}

			// Get last synced timestamp
			$last_sync_ts = $this->get_last_sync_timestamp( $chat );

			// Fetch messages from Slack
			$slack_messages = $this->fetch_slack_messages( $config, $channel_id, $last_sync_ts );
			usort( $slack_messages, function ( $a, $b ) {
				return $a['ts'] <=> $b['ts'];
			} );
			$last_message = null;
			// Save new messages to database
			foreach ( $slack_messages as $slack_message ) {
				if ( $last_message = $this->save_slack_message( $chat, $slack_message ) ) {
					$collection->push_item( $last_message );
				}
			}
			if ( ! empty( $last_message ) ) {
				$last_sync_ts = $last_message->get_meta( 'slack_ts' );
				if ( $last_sync_ts instanceof Message_Meta && $last_sync_ts->get_meta_value() ) {
					$chat->update_meta( 'slack_ts', $last_sync_ts->get_meta_value() );
				}
			}

			return $collection;
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return $collection;
		}
	}

	/**
	 * Get last synced message timestamp.
	 *
	 * @param  Chat  $chat  Chat object.
	 *
	 * @return string|null Last Slack timestamp or null.
	 * @since 1.0.0
	 */
	private function get_last_sync_timestamp( Chat $chat ): ?string {
		return $chat->get_meta( 'slack_ts' );
	}

	/**
	 * Fetch messages from Slack channel.
	 *
	 * @param  Config  $config  Slack config.
	 * @param  string  $channel_id  Channel ID.
	 * @param  string|null  $oldest  Oldest timestamp to fetch from.
	 *
	 * @return array Array of Slack messages.
	 * @since 1.0.0
	 */
	private function fetch_slack_messages( Config $config, string $channel_id, ?string $oldest = null ): array {
		try {
			$global_utility = new Slack_Messages_Utility( $config );
			$utility        = new Messages_Utility( $global_utility );

			$messages = $utility->get_history( $channel_id, $oldest );

			// Filter out bot messages and only get user messages
			return array_filter( $messages, function ( $msg ) {
				return ! isset( $msg['bot_id'] ) && ! isset( $msg['subtype'] );
			} );
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return [];
		}
	}

	/**
	 * Save Slack message to database.
	 *
	 * @param  Chat  $chat  Chat object.
	 * @param  array  $slack_message  Slack message data.
	 *
	 * @return null|Message True if saved successfully.
	 * @since 1.0.0
	 */
	private function save_slack_message( Chat $chat, array $slack_message ): ?Message {
		$slack_ts = $slack_message['ts'] ?? '';
		$text     = $slack_message['text'] ?? '';
		$user_id  = $slack_message['user'] ?? '';

		if ( empty( $slack_ts ) || empty( $text ) ) {
			return null;
		}

		// Check for closure commands (polling mode detection)
		if ( $this->is_closure_command( $text ) ) {
			return $this->handle_closure_command( $chat, $user_id, $slack_ts );
		}

		// Check if message already exists
		$existing = Message_Meta::where( [
			'meta_key'   => 'slack_ts',
			'meta_value' => $slack_ts
		] );
		if ( ! $existing->is_empty() ) {
			foreach ( $existing->get() as $item ) {
				$message = $item->message();
				if ( $message instanceof Message && $message->get_chat_uuid() === $chat->get_uuid() ) {
					return null;
				}
			}
		}

		try {
			$message = Message::make( [
				'chat_uuid' => $chat->get_uuid(),
				'role'      => Message::ROLE_ASSISTANT,
				'content'   => [
					[
						'type' => 'text',
						'text' => [ 'value' => $text ]
					]
				]
			] );
			$message->save();
			// Store Slack metadata
			$message->update_meta( 'slack_user_id', $user_id );
			$message->update_meta( 'slack_ts', $slack_ts );
			$message->update_meta( 'source', 'slack_agent' );
			$chatbot_user_meta = Chatbot_User_Meta::where( [
				'meta_key'   => 'slack_user_id',
				'meta_value' => $user_id
			] );
			if ( ! $chatbot_user_meta->is_empty() ) {
				$chatbot_user_meta->each( function ( Chatbot_User_Meta $meta ) use ( $message ) {
					$message->update_meta( 'agent_id', $meta->get_chatbot_user_id() );
				} );
			}

			return $message;
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ );

			return null;
		}
	}

	/**
	 * Check if message is a closure command.
	 *
	 * Detects special commands that agents can type to close sessions.
	 *
	 * @param  string  $text  Message text.
	 *
	 * @return bool True if message is a closure command.
	 * @since 1.0.0
	 */
	private function is_closure_command( string $text ): bool {
		$text             = trim( strtolower( $text ) );
		$closure_commands = [ '#close', '#end', '#disconnect', '#done' ];

		return in_array( $text, $closure_commands, true );
	}

	/**
	 * Handle closure command from agent (polling mode).
	 *
	 * @param  Chat  $chat  Chat object.
	 * @param  string  $user_id  Slack user ID who sent command.
	 * @param  string  $slack_ts  Slack timestamp of command.
	 *
	 * @return Message|null
	 * @since 1.0.0
	 */
	private function handle_closure_command( Chat $chat, string $user_id, string $slack_ts ): ?Message {
		try {
			// Check if this command was already processed
			$processed_commands = $chat->get_meta( 'live_agent_processed_commands' ) ?: [];
			if ( in_array( $slack_ts, $processed_commands, true ) ) {
				return null; // Already processed
			}

			$live_agent_service = new Live_Agent_Service();

			// Check if session is active
			if ( ! $live_agent_service->is_live_agent_active( $chat ) ) {
				return null; // Already closed
			}

			// Mark command as processed
			$processed_commands[] = $slack_ts;
			$chat->update_meta( 'live_agent_processed_commands', $processed_commands );

			// Store who closed the session
			$chat->update_meta( 'live_agent_closed_by_slack_id', $user_id );
			$chat->update_meta( 'live_agent_closed_at', current_time( 'mysql' ) );
			$chat->update_meta( 'live_agent_closed_via', 'polling' );

			// Disconnect and send message to user
			$message = $live_agent_service->disconnect_live_agent( $chat );

			$message->set_content(
				array_merge( $message->get_content(), array(
					array('type' => Message::CONTENT_TYPE_LIVE_AGENT_DISCONNECTION)
				) )
			);
			$message->update_meta( 'source', 'slack_agent' );
			$message->update_meta( 'slack_user_id', $user_id );

			return $message->save();
		} catch ( \Exception $e ) {
			Helper::log( $e, __METHOD__ . ' - Failed to handle closure command in polling mode' );

			return null;
		}
	}
}

