<?php

namespace Limb_Chatbot\Includes\Data_Objects;

use Exception;
use Limb_Chatbot\Includes\Services\Chat_Language_Service;
use Limb_Chatbot\Includes\Services\Live_Agent_Service;
use Limb_Chatbot\Includes\Services\Parsers\Action_Plan_Parser;

/**
 * Class Chat
 *
 * Represents a chat session associated with a chatbot user.
 *
 * @package Limb_Chatbot\Includes\Data_Objects
 * @since 1.0.0
 */
class Chat extends WPDB_Data_Object {

	/**
	 * Database table name.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	const TABLE_NAME = 'lbaic_chats';

	/**
	 * Column name representing UUID.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	const UUID_COLUMN_NAME = 'uuid';

	/**
	 * Role constant: regular user.
	 *
	 * @since 1.0.0
	 */
	const ROLE_USER = 'user';

	/**
	 * Role constant: admin user.
	 *
	 * @since 1.0.0
	 */
	const ROLE_ADMIN = 'admin';

	/**
	 * Role constant: moderator user.
	 *
	 * @since 1.0.0
	 */
	const ROLE_MODER = 'moderator';

	/**
	 * Status constant: active.
	 *
	 * @since 1.0.0
	 */
	const STATUS_ACTIVE = 1;

	/**
	 * Status constant: inactive.
	 *
	 * @since 1.0.0
	 */
	const STATUS_INACTIVE = 2;

	/**
	 * Status constant: archived.
	 *
	 * @since 1.0.0
	 */
	const STATUS_ARCHIVED = 3;

	/**
	 * Type constant: AI-driven chat.
	 *
	 * @since 1.0.0
	 */
	const TYPE_AI = 1;

	/**
	 * Type constant: live (human) chat.
	 *
	 * @since 1.0.0
	 */
	const TYPE_LIVE = 2;

	/**
	 * Type constant: moderated chat.
	 *
	 * @since 1.0.0
	 */
	const TYPE_MODERATE = 3;

	/**
	 * Fillable fields for the Chat object.
	 *
	 * @var array
	 * @since 1.0.0
	 */
	const FILLABLE
		= [
			'id',
			'name',
			'uuid',
			'chatbot_id',
			'type',
			'status',
			'chatbot_uuid',
			'created_at',
			'updated_at',
		];
	const KEY_CURRENT_ACTION = 'actual_action_plan';

	/**
	 * The type of the chat.
	 *
	 * @var int|null
	 * @since 1.0.0
	 */
	public ?int $type;

	/**
	 * The status of the chat.
	 *
	 * @var int|null
	 * @since 1.0.0
	 */
	public ?int $status;

	/**
	 * Chat ID (primary key).
	 * @json_excluded
	 *
	 * @var int|null
	 * @since 1.0.0
	 */
	public ?int $id;

	/**
	 * Name of the chat.
	 *
	 * @var string|null
	 * @since 1.0.0
	 */
	public ?string $name = null;

	/**
	 * Unique UUID of the chat.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $uuid;

	/**
	 * Associated Chatbot instance.
	 * @json_excluded
	 *
	 * @var Chatbot|null
	 * @since 1.0.0
	 */
	public ?Chatbot $chatbot;

	/**
	 * Chatbot ID foreign key.
	 * @json_excluded
	 *
	 * @var int|null
	 * @since 1.0.0
	 */
	public ?int $chatbot_id = null;

	/**
	 * Chatbot UUID foreign key.
	 *
	 * @var string|null
	 * @since 1.0.0
	 */
	public ?string $chatbot_uuid = null;

	/**
	 * Timestamp when the chat was created.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $created_at;

	/**
	 * Timestamp when the chat was last updated.
	 *
	 * @var string
	 * @since 1.0.0
	 */
	public string $updated_at;

	/**
	 * Meta properties related to this chat.
	 *
	 * @var array
	 * @since 1.0.0
	 */
	protected array $meta_properties = ['user', 'messages', '_links', 'conversation_state', 'messages_count', 'metas', 'is_live_agent_active', 'message' ];

	/**
	 * Array of included related objects.
	 * @json_excluded
	 *
	 * @var array
	 * @since 1.0.0
	 */
	public array $included = [];

	/**
	 * Chat constructor.
	 *
	 * @param mixed|null $instance Optional initial data.
	 * @since 1.0.0
	 */
	public function __construct( $instance = null ) {
		parent::__construct( $instance );
		$this->define_chatbot();
	}

	/**
	 * Defines the associated Chatbot object based on chatbot_id or chatbot_uuid.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function define_chatbot(): void {
		$chatbot = ! is_null( $this->chatbot_id ) ? Chatbot::find( $this->chatbot_id ) : ( ! is_null( $this->chatbot_uuid ) ? Chatbot::find_by_uuid( $this->chatbot_uuid ) : Chatbot::make() );
		if ( $chatbot instanceof Chatbot ) {
			$this->set_chatbot( $chatbot );
		}
	}

	/**
	 * Sets the Chatbot object.
	 *
	 * @param Chatbot $chatbot Chatbot instance.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_chatbot( Chatbot $chatbot ): void {
		$this->chatbot = $chatbot;
	}

	/**
	 * Gets the associated Chatbot object.
	 *
	 * @return Chatbot|null Chatbot instance or null if not set.
	 * @since 1.0.0
	 */
	public function get_chatbot(): ?Chatbot {
		return $this->chatbot;
	}

	/**
	 * Finds a Chat by UUID.
	 *
	 * @param string $uuid UUID to search.
	 * @return Chat|null Found Chat instance or null if not found.
	 * @since 1.0.0
	 */
	public static function find_by_uuid( $uuid ): ?Chat {
		return self::where( [ 'uuid' => $uuid ] )->first();
	}

	/**
	 * Get chat creation timestamp.
	 *
	 * @return string
	 * @since 1.0.0
	 */
	public function get_created_at(): string {
		return $this->created_at;
	}

	/**
	 * Set chat creation timestamp.
	 *
	 * @param string $created_at Timestamp string.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_created_at( $created_at ): void {
		$this->created_at = $created_at;
	}

	/**
	 * Get chatbot ID.
	 *
	 * @return int|null
	 * @since 1.0.0
	 */
	public function get_chatbot_id(): ?int {
		return $this->chatbot_id;
	}

	/**
	 * Set chatbot ID.
	 *
	 * @param int|null $chatbot_id Chatbot ID.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_chatbot_id( $chatbot_id ): void {
		$this->chatbot_id = $chatbot_id;
	}

	/**
	 * Set chatbot UUID.
	 *
	 * @param string|null $chatbot_uuid Chatbot UUID.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_chatbot_uuid( $chatbot_uuid ): void {
		$this->chatbot_uuid = $chatbot_uuid;
	}

	/**
	 * Get chatbot UUID.
	 *
	 * @return string|null
	 * @since 1.0.0
	 */
	public function get_chatbot_uuid(): string {
		return $this->chatbot_uuid;
	}

	/**
	 * Get chat update timestamp.
	 *
	 * @return string
	 * @since 1.0.0
	 */
	public function get_updated_at(): string {
		return $this->updated_at;
	}

	/**
	 * Set chat update timestamp.
	 *
	 * @param string $updated_at Timestamp string.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_updated_at( $updated_at ): void {
		$this->updated_at = $updated_at;
	}

	/**
	 * Get a meta value for the chat by key.
	 *
	 * @param string $key Meta key.
	 * @return mixed|null Meta value or null if not found.
	 * @since 1.0.0
	 */
	public function get_meta( $key ) {
		$meta = Chat_Meta::where( [ 'chat_id' => $this->id, 'meta_key' => $key ] );

		return ! $meta->is_empty() ? $meta->first()->get_meta_value() : null;
	}

	/**
	 * Get chat UUID.
	 *
	 * @return string
	 * @since 1.0.0
	 */
	public function get_uuid(): string {
		return $this->uuid;
	}

	/**
	 * Set chat UUID.
	 *
	 * @param string $uuid UUID string.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_uuid( $uuid ): void {
		$this->uuid = $uuid;
	}

	/**
	 * Get thread ID meta value.
	 *
	 * @return mixed|null Thread ID or null if not set.
	 * @since 1.0.0
	 */
	public function get_thread_id() {
		return $this->get_meta( '_thread_id' );
	}

	/**
	 * Get chat name.
	 *
	 * @return string|null Chat name or null if not set.
	 * @since 1.0.0
	 */
	public function get_name(): ?string {
		return $this->name;
	}

	/**
	 * Set chat name.
	 *
	 * @param string|null $name Chat name.
	 * @return void
	 * @since 1.0.0
	 */
	public function set_name( $name ) {
		$this->name = $name;
	}

	/**
	 * Update a meta key value for the chat.
	 *
	 * @param string $key Meta key.
	 * @param mixed $value Meta value.
	 * @return void
	 * @since 1.0.0
	 */
	public function update_meta( $key, $value ): void {
        Chat_Meta::update( [ 'chat_id' => $this->id, 'meta_key' => $key ], [ 'meta_value' => $value ] );
	}

	/**
	 * Get last message in the chat.
	 *
	 * @return Message|null Last message object or null if no messages.
	 * @since 1.0.0
	 */
	public function get_last_message(): ?Message {
		$messages = Message::where( [ 'chat_uuid' => $this->uuid ], 1, 1, 'id', 'DESC' );

		return $messages->is_empty() ? null : $messages->first();
	}

	/**
	 * Get the count of messages in the chat.
	 *
	 * @return int|null Number of messages or null if not available.
	 * @since 1.0.0
	 */
	public function get_messages_count(): ?int {
		return Message::count( [ 'chat_uuid' => $this->uuid ] );
	}

	/**
	 * Get chat type.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function get_type(): ?int {
		return $this->type;
	}

	/**
	 * Set chat type.
	 *
	 * @param  int|null  $type The chat type.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function set_type( ?int $type ): void {
		$this->type = $type;
	}

	/**
	 * Get chat status.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function get_status(): ?int {
		return $this->status;
	}

	/**
	 * Set chat status.
	 *
	 * @param  int|null  $status The chat status.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function set_status( ?int $status ): void {
		$this->status = $status;
	}

	/**
	 * Check whether the user is participant of current chat.
	 *
	 * @param  Chatbot_User  $user The chatbot user.
	 *
	 * @return bool
	 * @since 1.0.0
	 */
	public function has_participant( Chatbot_User $user ) {
		return ! ( Chat_Participant::where( [
			'chat_id' => $this->id,
			'user_id' => $user->get_id(),
		] ) )->is_empty();
	}

	/**
	 * Add a new participant to this chat.
	 *
	 * @param  Chatbot_User|null  $chatbot_user
	 * @param  string  $role
	 *
	 * @return Chat_Participant
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function add_participant( ?Chatbot_User $chatbot_user, $role = 'user' ) {
		return Chat_Participant::create( [
			'chat_id'   => $this->id,
			'user_id'   => $chatbot_user->get_id(),
			'role'      => $role,
			'joined_at' => current_time( 'mysql', true )
		] );
	}

	public function get_user_participant(): ?Chatbot_User {
		$participant = Chat_Participant::where( [
			'chat_id' => $this->id,
			'role'    => Chat_Participant::ROLE_USER,
		] )->first();
		if ( $participant instanceof Chat_Participant ) {
			return $participant->user();
		}

		return null;
	}

	/**
	 * Retrieves the current conversation state for this chat.
	 *
	 * This method looks up the `Conversation_State` record associated with
	 * the chat's UUID. If no matching state exists, it returns `null`.
	 *
	 * @return Conversation_State|null The conversation state instance, or null if none found.
	 */
	public function conversation_state() {
		return Conversation_State::where( [ 'chat_uuid' => $this->uuid ] )->first() ?? null;
	}

	/**
	 * The messages of this chat.
	 *
	 * @return array
	 * @since 1.0.0
	 */
	public function messages() {
		return Message::where( [ 'chat_uuid' => $this->uuid ], 1, 1, 'id', 'desc' )->get();
	}

	/**
	 * The chatbot user connected with this chat
	 * @param $user_fields
	 *
	 * @return Chatbot_User|null
	 * @since 1.0.0
	 */
	public function user( $user_fields = [ 'email', 'name' ] ): ?Chatbot_User {
		$participant = Chat_Participant::where( [ 'chat_id' => $this->id, 'role' => self::ROLE_USER ] )->first();
		if ( ! empty( $participant ) && $participant instanceof Chat_Participant ) {
			return Chatbot_User::find( $participant->user_id )->with( $user_fields );
		}

		return null;
	}

	/**
	 * Include the messages count
	 *
	 * @return int|null
	 * @since 1.0.0
	 */
	public function messages_count(): ?int {
		return $this->get_messages_count();
	}

	/**
	 * Include metas of this chat
	 *
	 * @return array
	 * @since 1.0.0
	 */
	public function metas() {
		return Chat_Meta::where( [ 'chat_id' => $this->id ] )->get();
	}


	public function current_action_plan(): ?Action_Plan {
		$chat_meta = $this->get_meta( self::KEY_CURRENT_ACTION );
		if ( ! empty( $chat_meta ) ) {
			return ( new Action_Plan_Parser() )->parse( $chat_meta );
		}

		return null;
	}

	public function is_live_agent_active(){
		return (bool) $this->get_meta( Live_Agent_Service::META_LIVE_AGENT_ACTIVE );
	}

	/**
	 * Get last message in the chat.
	 *
	 * @return Message|null Last message object or null if no messages.
	 * @since 1.0.0
	 */
	public function get_last_user_message(): ?Message {
		$messages = Message::where( [ 'chat_uuid' => $this->uuid, 'role' => self::ROLE_USER ], 1, 1, 'id', 'DESC' );

		return $messages->is_empty() ? null : $messages->first();
	}

	public function has_language() {
		return ! empty( $this->get_meta( Chat_Language_Service::META_KEY ) );
	}

	public function inject_message( Message $response ) {
		$this->included['message'] = $response;
		return $this;
	}

	public function has_action() {
		return ! empty( $this->get_meta( self::KEY_CURRENT_ACTION ) );
	}
}