<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Includes\Data_Objects\Action;
use Limb_Chatbot\Includes\Data_Objects\Chat;
use Limb_Chatbot\Includes\Data_Objects\Chatbot_User;
use Limb_Chatbot\Includes\Data_Objects\Lead;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Services\Channel_Events\Channel_Event;
use Limb_Chatbot\Includes\Services\Channel_Events\Channel_Event_Types;

/**
 * Enriches admin notification Channel_Event rows with domain dependencies.
 *
 * Enrichment requirements:
 * - include chatbot_user (name + email) when available
 * - include chat + last chat messages when chat_uuid is available
 * - include lead object for admin.new_lead
 * - include action object for admin.new_form_entry
 */
class Admin_Notifications_Enricher {

	/**
	 * Latest message is resolved per chat (latest user message only).
	 */
	public function __construct() {}

	/**
	 * @param  Channel_Event[]  $events
	 *
	 * @return array<int,array<string,mixed>>
	 */
	public function enrich( array $events ): array {
		if ( empty( $events ) ) {
			return [];
		}

		$chat_uuids    = [];
		$user_uuids    = [];
		$lead_ids      = [];
		$action_ids    = [];
		$needs_lead_to_user = false;

		foreach ( $events as $event ) {
			if ( ! $event instanceof Channel_Event ) {
				continue;
			}

			$chat_uuid = (string) ( $event->chat_uuid ?? '' );
			if ( $chat_uuid !== '' ) {
				$chat_uuids[] = $chat_uuid;
			}

			$chatbot_user_uuid = (string) ( $event->chatbot_user_uuid ?? '' );
			if ( $chatbot_user_uuid !== '' ) {
				$user_uuids[] = $chatbot_user_uuid;
			}

			if ( $event->event_type === Channel_Event_Types::ADMIN_NEW_LEAD ) {
				$lead_id = $event->payload['lead_id'] ?? null;
				if ( is_numeric( $lead_id ) && (int) $lead_id > 0 ) {
					$lead_ids[] = (int) $lead_id;
					$needs_lead_to_user = true;
				}
			}

			if ( $event->event_type === Channel_Event_Types::ADMIN_NEW_FORM_ENTRY ) {
				$action_id = $event->payload['action_id'] ?? null;
				if ( is_numeric( $action_id ) && (int) $action_id > 0 ) {
					$action_ids[] = (int) $action_id;
				}
			}
		}

		$chat_uuids = array_values( array_unique( array_filter( $chat_uuids ) ) );
		$user_uuids = array_values( array_unique( array_filter( $user_uuids ) ) );
		$lead_ids   = array_values( array_unique( array_filter( $lead_ids ) ) );
		$action_ids = array_values( array_unique( array_filter( $action_ids ) ) );

		$chat_map     = $this->load_chats( $chat_uuids );
		$user_map     = $this->load_chatbot_users( $user_uuids );
		$lead_map     = $this->load_leads( $lead_ids );
		$action_map   = $this->load_actions( $action_ids );

		// If lead events didn't carry chatbot_user_uuid in the event row,
		// derive the uuid from the lead.
		if ( $needs_lead_to_user && ! empty( $lead_map ) ) {
			foreach ( $lead_map as $lead ) {
				if ( $lead instanceof Lead ) {
					$user_uuid = (string) $lead->get_chatbot_user_uuid();
					if ( $user_uuid !== '' && ! isset( $user_map[ $user_uuid ] ) ) {
						$maybe = $this->load_chatbot_users( [ $user_uuid ] );
						if ( ! empty( $maybe ) ) {
							$user_map = array_replace( $user_map, $maybe );
						}
					}
				}
			}
		}

		$last_user_message_map = $this->load_last_user_messages_for_chats( array_values( $chat_map ) );

		$result = [];
		foreach ( $events as $event ) {
			if ( ! $event instanceof Channel_Event ) {
				continue;
			}

			$row = $event->to_api_array();

			$chat_uuid = (string) ( $event->chat_uuid ?? '' );
			if ( $chat_uuid !== '' && isset( $chat_map[ $chat_uuid ] ) ) {
				$row['chat'] = $this->serialize_chat( $chat_map[ $chat_uuid ] );
				$row['last_message'] = isset( $last_user_message_map[ $chat_uuid ] )
					? $this->serialize_message( $last_user_message_map[ $chat_uuid ] )
					: null;
			} else {
				$row['chat'] = null;
				$row['last_message'] = null;
			}

			$chatbot_user_uuid = (string) ( $event->chatbot_user_uuid ?? '' );
			if ( $chatbot_user_uuid !== '' && isset( $user_map[ $chatbot_user_uuid ] ) ) {
				$current_user = $user_map[ $chatbot_user_uuid ];
				$row['chatbot_user'] = $this->serialize_chatbot_user( $current_user );
				if ( $event->event_type === Channel_Event_Types::ADMIN_NEW_CHATBOT_USER ) {
					$row['visitor_context'] = $this->build_visitor_context( $current_user );
				}
			} else {
				// Fallback for lead events when event row doesn't contain chatbot_user_uuid.
				if ( $event->event_type === Channel_Event_Types::ADMIN_NEW_LEAD ) {
					$lead_id = $event->payload['lead_id'] ?? null;
					if ( is_numeric( $lead_id ) && isset( $lead_map[ (int) $lead_id ] ) ) {
						$lead = $lead_map[ (int) $lead_id ];
						$derived_uuid = $lead instanceof Lead ? (string) $lead->get_chatbot_user_uuid() : '';
						if ( $derived_uuid !== '' && isset( $user_map[ $derived_uuid ] ) ) {
							$row['chatbot_user'] = $this->serialize_chatbot_user( $user_map[ $derived_uuid ] );
						} else {
							$row['chatbot_user'] = null;
						}
						$row['lead'] = $lead instanceof Lead ? $this->serialize_lead( $lead ) : null;
					}
				} else {
					$row['chatbot_user'] = null;
				}
			}

			// Attach lead/action objects as requested.
			if ( $event->event_type === Channel_Event_Types::ADMIN_NEW_LEAD ) {
				$lead_id = $event->payload['lead_id'] ?? null;
				$row['lead'] = ( is_numeric( $lead_id ) && isset( $lead_map[ (int) $lead_id ] ) )
					? $this->serialize_lead( $lead_map[ (int) $lead_id ] )
					: null;
			}

			if ( $event->event_type === Channel_Event_Types::ADMIN_NEW_FORM_ENTRY ) {
				$action_id = $event->payload['action_id'] ?? null;
				$row['action'] = ( is_numeric( $action_id ) && isset( $action_map[ (int) $action_id ] ) )
					? $action_map[ (int) $action_id ]->toArray()
					: null;
			}

			$result[] = $row;
		}

		return $result;
	}

	// -------------------------------------------------------------------------
	// Batch loaders
	// -------------------------------------------------------------------------

	/**
	 * @param  string[]  $uuids
	 *
	 * @return array<string,Chat>
	 */
	private function load_chats( array $uuids ): array {
		if ( empty( $uuids ) ) {
			return [];
		}

		try {
			$chats = Chat::where( [ 'uuid' => $uuids ] )->get();
		} catch ( \Throwable $e ) {
			Helper::log( $e, __METHOD__ );

			return [];
		}

		$map = [];
		foreach ( $chats as $chat ) {
			if ( $chat instanceof Chat && ! empty( $chat->uuid ) ) {
				$map[ $chat->uuid ] = $chat;
			}
		}

		return $map;
	}

	/**
	 * @param  string[]  $uuids
	 *
	 * @return array<string,Chatbot_User>
	 */
	private function load_chatbot_users( array $uuids ): array {
		if ( empty( $uuids ) ) {
			return [];
		}

		try {
			$users = Chatbot_User::where( [ 'uuid' => $uuids ] )->get();
		} catch ( \Throwable $e ) {
			Helper::log( $e, __METHOD__ );

			return [];
		}

		$map = [];
		foreach ( $users as $user ) {
			if ( $user instanceof Chatbot_User && ! empty( $user->uuid ) ) {
				$map[ $user->uuid ] = $user;
			}
		}

		return $map;
	}

	/**
	 * @param  int[]  $lead_ids
	 *
	 * @return array<int,Lead>
	 */
	private function load_leads( array $lead_ids ): array {
		if ( empty( $lead_ids ) ) {
			return [];
		}

		try {
			$leads = Lead::where( [ 'id' => $lead_ids ] )->get();
		} catch ( \Throwable $e ) {
			Helper::log( $e, __METHOD__ );

			return [];
		}

		$map = [];
		foreach ( $leads as $lead ) {
			if ( $lead instanceof Lead ) {
				$map[ (int) $lead->get_id() ] = $lead;
			}
		}

		return $map;
	}

	/**
	 * @param  int[]  $action_ids
	 *
	 * @return array<int,Action>
	 */
	private function load_actions( array $action_ids ): array {
		if ( empty( $action_ids ) ) {
			return [];
		}

		try {
			$actions = Action::where( [ 'id' => $action_ids ] )->get();
		} catch ( \Throwable $e ) {
			Helper::log( $e, __METHOD__ );

			return [];
		}

		$map = [];
		foreach ( $actions as $action ) {
			if ( $action instanceof Action && $action->get_id() ) {
				$map[ (int) $action->get_id() ] = $action;
			}
		}

		return $map;
	}

	/**
	 * Load each chat's latest USER message.
	 *
	 * @param  Chat[]  $chats
	 *
	 * @return array<string,Message|null> keyed by chat_uuid
	 */
	private function load_last_user_messages_for_chats( array $chats ): array {
		if ( empty( $chats ) ) {
			return [];
		}

		$map = [];
		foreach ( $chats as $chat ) {
			if ( ! $chat instanceof Chat || empty( $chat->uuid ) ) {
				continue;
			}

			try {
				$messages = Message::where(
					[ 'chat_uuid' => $chat->uuid, 'role' => Message::ROLE_USER ],
					1,
					1,
					'id',
					'DESC'
				);

				$map[ $chat->uuid ] = $messages->is_empty() ? null : $messages->first();
			} catch ( \Throwable $e ) {
				Helper::log( $e, __METHOD__ );
				$map[ $chat->uuid ] = null;
			}
		}

		return $map;
	}

	// -------------------------------------------------------------------------
	// Serializers
	// -------------------------------------------------------------------------

	private function serialize_chat( Chat $chat ): array {
		return [
			'id'           => $chat->id,
			'uuid'         => $chat->uuid,
			'name'         => $chat->name,
			'type'         => $chat->type,
			'status'       => $chat->status,
			'chatbot_id'   => $chat->chatbot_id,
			'chatbot_uuid' => $chat->chatbot_uuid,
			'created_at'   => $chat->created_at,
			'updated_at'   => $chat->updated_at,
		];
	}

	private function serialize_chatbot_user( Chatbot_User $user ): array {
		return [
			'uuid'       => $user->uuid,
			'type'       => $user->type,
			'wp_user_id' => $user->wp_user_id,
			'name'       => $user->name(),
			'email'      => $user->email(),
		];
	}

	/**
	 * Bundle extra visitor metadata for "new visitor" notifications.
	 *
	 * @return array<string,mixed>
	 */
	private function build_visitor_context( Chatbot_User $user ): array {
		return [
			'geolocation'       => $this->resolve_user_geolocation( $user ),
			'device_info'       => $user->device_info(),
			'last_visited_page' => $user->last_visited_page(),
		];
	}

	/**
	 * Resolve geolocation unless user IP is localhost/loopback.
	 *
	 * @return array<string,mixed>|null
	 */
	private function resolve_user_geolocation( Chatbot_User $user ): ?array {
		$ip = trim( (string) $user->get_ip() );
		if ( $ip === '' || $ip === '127.0.0.1' || $ip === '::1' || $ip === '0.0.0.0' || $ip === 'localhost' ) {
			return null;
		}

		$geolocation = $user->geolocation();

		return is_array( $geolocation ) ? $geolocation : null;
	}

	/**
	 * @return array<string,mixed>
	 */
	private function serialize_message( Message $message ): array {
		return [
			'uuid'       => $message->uuid,
			'chat_uuid'  => $message->chat_uuid,
			'role'       => $message->role,
			'content'    => $message->content,
			'created_at' => $message->created_at,
		];
	}

	/**
	 * @return array<string,mixed>
	 */
	private function serialize_lead( Lead $lead ): array {
		return $lead->toArray();
	}
}

