<?php

namespace Limb_Chatbot\Includes\Services;

use Exception;
use Limb_Chatbot\Includes\Data_Objects\Chatbot_User;
use Limb_Chatbot\Includes\Data_Objects\Setting;
use Limb_Chatbot\Includes\Repositories\Chatbot_User_Repository;
use Limb_Chatbot\Includes\Traits\SingletonTrait;

/**
 * Class User_Manager
 *
 * Manages the current chatbot user session and identification.
 *
 * @since 1.0.0
 */
class User_Manager {

	use SingletonTrait;

	/**
	 * Identification by device UUID.
	 *
	 * @since 1.0.0
	 */
	const IDENTIFICATION_METHOD_DEVICE_UUID = 1;

	/**
	 * Identification by IP address.
	 *
	 * @since 1.0.0
	 */
	const IDENTIFICATION_METHOD_IP = 2;

	/**
	 * Identification by both device UUID and IP address.
	 *
	 * @since 1.0.0
	 */
	const IDENTIFICATION_METHOD_BOTH = 3;

	/**
	 * Setting key for identification method.
	 *
	 * @since 1.0.0
	 */
	const IDENTIFICATION_METHOD_SETTING_KEY = 'guest_user_identification_method';

	/**
	 * Currently identified chatbot user.
	 *
	 * @var Chatbot_User|null
	 * @since 1.0.0
	 */
	public ?Chatbot_User $current_user = null;

	/**
	 * Gets the current chatbot user.
	 *
	 * @return Chatbot_User|null
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function get_current_user(): ?Chatbot_User {
		if ( empty( $this->current_user ) ) {
			$this->define_current_user();
		}

		return $this->current_user;
	}

	/**
	 * Sets the current chatbot user.
	 *
	 * @param  Chatbot_User|null  $current_user
	 *
	 * @return void
	 * @since 1.0.0
	 */
	public function set_current_user( ?Chatbot_User $current_user ): void {
		$this->current_user = $current_user;
		Session_Manager::instance()->set_cookie( 'chatbot_user_uuid', $current_user->get_uuid() );
	}

	/**
	 * Gets the client IP address, respecting Cloudflare and other reverse proxies.
	 *
	 * When behind Cloudflare, REMOTE_ADDR is the edge IP; the real client IP is in
	 * CF-Connecting-IP. Also checks X-Forwarded-For and X-Real-IP for other proxies.
	 *
	 * @return string Valid IPv4 or IPv6 address, or '0.0.0.0' if none could be determined.
	 * @since 1.0.0
	 */
	private function get_client_ip(): string {
		// X-Forwarded-For can be "client, proxy1, proxy2" — use first (original client).
		$forwarded = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null;
		$xff_first = null;
		if ( $forwarded !== null && $forwarded !== '' ) {
			$first = trim( (string) preg_replace( '/,.*/', '', $forwarded ) );
			if ( $first !== '' ) {
				$xff_first = $first;
			}
		}

		// Prefer Cloudflare (trusted when behind CF), then X-Real-IP, then X-Forwarded-For, then REMOTE_ADDR.
		$candidates = array_filter( [
			$_SERVER['HTTP_CF_CONNECTING_IP'] ?? null,
			$_SERVER['HTTP_X_REAL_IP'] ?? null,
			$xff_first,
			$_SERVER['REMOTE_ADDR'] ?? null,
		] );

		foreach ( array_unique( $candidates ) as $value ) {
			if ( $value === null || $value === '' ) {
				continue;
			}
			$cleaned = sanitize_text_field( $value );
			if ( $cleaned !== '' && filter_var( $cleaned, FILTER_VALIDATE_IP ) ) {
				return $this->normalize_ip_to_ipv4( $cleaned );
			}
		}

		return '0.0.0.0';
	}

	/**
	 * Normalizes an IP to IPv4 dotted-quad format (e.g. 0.0.0.0, 127.0.0.1).
	 *
	 * Converts ::1 to 127.0.0.1, IPv4-mapped IPv6 (::ffff:x.x.x.x) to x.x.x.x,
	 * leaves IPv4 unchanged, and returns 0.0.0.0 for other IPv6 addresses.
	 *
	 * @param  string  $ip  Valid IPv4 or IPv6 address.
	 * @return string IPv4 dotted-quad address.
	 * @since 1.0.0
	 */
	private function normalize_ip_to_ipv4( string $ip ): string {
		if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
			return $ip;
		}
		if ( $ip === '::1' ) {
			return '127.0.0.1';
		}
		if ( substr( $ip, 0, 7 ) === '::ffff:' ) {
			$mapped = substr( $ip, 7 );
			if ( filter_var( $mapped, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
				return $mapped;
			}
		}

		return '0.0.0.0';
	}

	/**
	 * Defines the current chatbot user based on identification method.
	 *
	 * @return void
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function define_current_user() {
		$ip_address            = $this->get_client_ip();
		$identification_method = Setting::find( self::IDENTIFICATION_METHOD_SETTING_KEY )->get_value() ?? self::IDENTIFICATION_METHOD_DEVICE_UUID;
		$device_uuid           = Session_Manager::instance()->get_device_uuid();
		if ( empty( $device_uuid ) ) {
			$device_uuid = Helper::get_uuid();
			Session_Manager::instance()->set_cookie( 'device_uuid', $device_uuid );
		}

		// Collect device information
		$device_info = $this->collect_device_info();

		if ( $wp_user_id = get_current_user_id() ) {
			$current_user = Chatbot_User::where( [
				'wp_user_id'  => $wp_user_id,
				'device_uuid' => $device_uuid
			] )->first();
			if ( ! $current_user ) {
				$current_user = ( new Chatbot_User_Repository() )->create( $wp_user_id,
					$ip_address,
					$device_uuid,
					Chatbot_User::TYPE_WP_USER );
				// Store device information in meta for new user
				$this->store_device_info( $current_user, $device_info );
			}
		} else {
			$where = [];
			if ( $identification_method === self::IDENTIFICATION_METHOD_DEVICE_UUID ) {
				$where['device_uuid'] = $device_uuid;
			} elseif ( $identification_method === self::IDENTIFICATION_METHOD_IP ) {
				$where['ip'] = $ip_address;
			} elseif ( $identification_method === self::IDENTIFICATION_METHOD_BOTH ) {
				$where['device_uuid'] = $device_uuid;
				$where['ip']          = $ip_address;
			}
			$where['wp_user_id'] = null;
			$current_user        = Chatbot_User::where( $where )->first();
			if ( ! $current_user ) {
				$current_user = ( new Chatbot_User_Repository() )->create( null,
					$ip_address,
					$device_uuid,
					Chatbot_User::TYPE_GUEST );
				// Store device information in meta for new user
				$this->store_device_info( $current_user, $device_info );
			}
		}
		$this->set_current_user( $current_user );
	}

	/**
	 * Collects device information from server variables.
	 *
	 * @return array Device information array.
	 * @since 1.0.0
	 */
	private function collect_device_info(): array {
		$user_agent = sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ?? '' );

		// Parse user agent to extract browser and device info
		$device_info = array(
			'user_agent'      => $user_agent,
			'browser'         => $this->detect_browser( $user_agent ),
			'browser_version' => $this->detect_browser_version( $user_agent ),
			'os'              => $this->detect_os( $user_agent ),
			'os_version'      => $this->detect_os_version( $user_agent ),
			'device_type'     => $this->detect_device_type( $user_agent ),
			'is_mobile'       => $this->is_mobile_device( $user_agent ),
			'is_tablet'       => $this->is_tablet_device( $user_agent ),
			'is_desktop'      => ! $this->is_mobile_device( $user_agent ) && ! $this->is_tablet_device( $user_agent ),
			'screen_width'    => isset( $_SERVER['HTTP_X_SCREEN_WIDTH'] ) ? absint( $_SERVER['HTTP_X_SCREEN_WIDTH'] ) : null,
			'screen_height'   => isset( $_SERVER['HTTP_X_SCREEN_HEIGHT'] ) ? absint( $_SERVER['HTTP_X_SCREEN_HEIGHT'] ) : null,
			'language'        => sanitize_text_field( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '' ),
			'timezone'        => isset( $_SERVER['HTTP_X_TIMEZONE'] ) ? sanitize_text_field( $_SERVER['HTTP_X_TIMEZONE'] ) : null,
			'platform'        => $this->detect_platform( $user_agent ),
		);

		return $device_info;
	}

	/**
	 * Detects browser name from user agent.
	 *
	 * @param  string  $user_agent  User agent string.
	 *
	 * @return string|null Browser name.
	 * @since 1.0.0
	 */
	private function detect_browser( string $user_agent ): ?string {
		if ( empty( $user_agent ) ) {
			return null;
		}

		if ( strpos( $user_agent, 'Chrome' ) !== false && strpos( $user_agent, 'Edg' ) === false && strpos( $user_agent, 'OPR' ) === false ) {
			return 'Chrome';
		} elseif ( strpos( $user_agent, 'Firefox' ) !== false ) {
			return 'Firefox';
		} elseif ( strpos( $user_agent, 'Safari' ) !== false && strpos( $user_agent, 'Chrome' ) === false ) {
			return 'Safari';
		} elseif ( strpos( $user_agent, 'Edg' ) !== false ) {
			return 'Edge';
		} elseif ( strpos( $user_agent, 'OPR' ) !== false ) {
			return 'Opera';
		} elseif ( strpos( $user_agent, 'MSIE' ) !== false || strpos( $user_agent, 'Trident' ) !== false ) {
			return 'Internet Explorer';
		}

		return 'Other';
	}

	/**
	 * Detects browser version from user agent.
	 *
	 * @param  string  $user_agent  User agent string.
	 *
	 * @return string|null Browser version.
	 * @since 1.0.0
	 */
	private function detect_browser_version( string $user_agent ): ?string {
		if ( empty( $user_agent ) ) {
			return null;
		}

		// Chrome/Edge version
		if ( preg_match( '/Chrome\/([\d.]+)/', $user_agent, $matches ) ) {
			return $matches[1];
		}
		// Firefox version
		if ( preg_match( '/Firefox\/([\d.]+)/', $user_agent, $matches ) ) {
			return $matches[1];
		}
		// Safari version
		if ( preg_match( '/Version\/([\d.]+).*Safari/', $user_agent, $matches ) ) {
			return $matches[1];
		}
		// Edge version
		if ( preg_match( '/Edg\/([\d.]+)/', $user_agent, $matches ) ) {
			return $matches[1];
		}
		// Opera version
		if ( preg_match( '/OPR\/([\d.]+)/', $user_agent, $matches ) ) {
			return $matches[1];
		}

		return null;
	}

	/**
	 * Detects operating system from user agent.
	 *
	 * @param  string  $user_agent  User agent string.
	 *
	 * @return string|null OS name.
	 * @since 1.0.0
	 */
	private function detect_os( string $user_agent ): ?string {
		if ( empty( $user_agent ) ) {
			return null;
		}

		if ( strpos( $user_agent, 'Windows' ) !== false ) {
			return 'Windows';
		} elseif ( strpos( $user_agent, 'Mac OS X' ) !== false || strpos( $user_agent, 'Macintosh' ) !== false ) {
			return 'macOS';
		} elseif ( strpos( $user_agent, 'Linux' ) !== false ) {
			return 'Linux';
		} elseif ( strpos( $user_agent, 'Android' ) !== false ) {
			return 'Android';
		} elseif ( strpos( $user_agent, 'iOS' ) !== false || strpos( $user_agent, 'iPhone' ) !== false || strpos( $user_agent, 'iPad' ) !== false ) {
			return 'iOS';
		}

		return 'Unknown';
	}

	/**
	 * Detects operating system version from user agent.
	 *
	 * @param  string  $user_agent  User agent string.
	 *
	 * @return string|null OS version.
	 * @since 1.0.0
	 */
	private function detect_os_version( string $user_agent ): ?string {
		if ( empty( $user_agent ) ) {
			return null;
		}

		// Windows version
		if ( preg_match( '/Windows NT ([\d.]+)/', $user_agent, $matches ) ) {
			return $matches[1];
		}
		// macOS version
		if ( preg_match( '/Mac OS X ([\d_]+)/', $user_agent, $matches ) ) {
			return str_replace( '_', '.', $matches[1] );
		}
		// Android version
		if ( preg_match( '/Android ([\d.]+)/', $user_agent, $matches ) ) {
			return $matches[1];
		}
		// iOS version
		if ( preg_match( '/OS ([\d_]+)/', $user_agent, $matches ) ) {
			return str_replace( '_', '.', $matches[1] );
		}

		return null;
	}

	/**
	 * Detects device type from user agent.
	 *
	 * @param  string  $user_agent  User agent string.
	 *
	 * @return string Device type (mobile, tablet, desktop).
	 * @since 1.0.0
	 */
	private function detect_device_type( string $user_agent ): string {
		if ( $this->is_mobile_device( $user_agent ) ) {
			return 'mobile';
		} elseif ( $this->is_tablet_device( $user_agent ) ) {
			return 'tablet';
		}

		return 'desktop';
	}

	/**
	 * Checks if device is mobile.
	 *
	 * @param  string  $user_agent  User agent string.
	 *
	 * @return bool True if mobile device.
	 * @since 1.0.0
	 */
	private function is_mobile_device( string $user_agent ): bool {
		if ( empty( $user_agent ) ) {
			return false;
		}

		return (bool) preg_match( '/Mobile|Android|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i', $user_agent );
	}

	/**
	 * Checks if device is tablet.
	 *
	 * @param  string  $user_agent  User agent string.
	 *
	 * @return bool True if tablet device.
	 * @since 1.0.0
	 */
	private function is_tablet_device( string $user_agent ): bool {
		if ( empty( $user_agent ) ) {
			return false;
		}

		return (bool) preg_match( '/iPad|Android(?!.*Mobile)|Tablet/i', $user_agent );
	}

	/**
	 * Detects platform (mobile/desktop).
	 *
	 * @param  string  $user_agent  User agent string.
	 *
	 * @return string Platform type.
	 * @since 1.0.0
	 */
	private function detect_platform( string $user_agent ): string {
		if ( $this->is_mobile_device( $user_agent ) || $this->is_tablet_device( $user_agent ) ) {
			return 'mobile';
		}

		return 'desktop';
	}

	/**
	 * Stores device information in user meta.
	 *
	 * @param  Chatbot_User  $user         Chatbot user instance.
	 * @param  array         $device_info  Device information array.
	 *
	 * @return void
	 * @since 1.0.0
	 */
	private function store_device_info( Chatbot_User $user, array $device_info ): void {
		// Store complete device info as JSON in meta
		$user->update_meta( 'device_info', wp_json_encode( $device_info ) );
	}

	/**
	 * Get chatbot users that correspond to WordPress admin / super admin users.
	 *
	 * Finds WP users with administrator or super_admin role, then returns Chatbot_User
	 * records linked to those wp_user_ids.
	 *
	 * @return Chatbot_User[] Array of Chatbot_User instances (may be empty).
	 * @since 1.0.15
	 */
	public function get_admins(): array {
		$admin_ids = get_users( [
			'role__in' => [ 'administrator', 'super_admin' ],
			'fields'   => 'ID',
		] );

		$admin_ids = array_map( 'absint', (array) $admin_ids );
		$admin_ids = array_filter( array_unique( $admin_ids ) );

		if ( empty( $admin_ids ) ) {
			return [];
		}

		$collection = Chatbot_User::where( [ 'wp_user_id' => $admin_ids ], - 1, - 1, 'created_at', 'DESC' );
		$items      = $collection->get() ?? [];

		// Return only one Chatbot_User per wp_user_id (e.g. same admin on multiple devices).
		$seen_wp_user_ids = [];

		return array_values( array_filter( $items, function ( Chatbot_User $user ) use ( &$seen_wp_user_ids ) {
			$wp_id = $user->get_wp_user_id();
			if ( $wp_id === null ) {
				return true;
			}
			if ( isset( $seen_wp_user_ids[ $wp_id ] ) ) {
				return false;
			}
			$seen_wp_user_ids[ $wp_id ] = true;

			return true;
		} ) );
	}
}
