<?php
/**
 * Session management for login sessions.
 *
 * @package ZA_Creative_Login_Shield
 */

defined( 'ABSPATH' ) || exit;

/**
 * Manages authenticated user sessions with device tracking and timeout enforcement.
 */
class ZACLS_Session {

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->register_hooks();
	}

	/**
	 * Register WordPress hooks.
	 */
	private function register_hooks() {
		add_action( 'wp_login', array( $this, 'create_session' ), 10, 2 );
		add_action( 'wp_logout', array( $this, 'destroy_session' ) );
		add_action( 'wp', array( $this, 'check_session_timeout' ) );
		add_action( 'admin_init', array( $this, 'check_session_timeout' ) );
		add_filter( 'zacls_session_valid', array( $this, 'is_session_valid' ), 10, 2 );
	}

	/**
	 * Create a new session record on login.
	 *
	 * @param string  $user_login Username.
	 * @param WP_User $user       User object.
	 */
	public function create_session( $user_login, $user ) {
		self::create_for_user( $user );
	}

	/**
	 * Create a new session record for a user and set the session cookie.
	 *
	 * Callable statically so that 2FA and WebAuthn login flows (which bypass
	 * the normal wp_login hook) can create a tracked session.
	 *
	 * @param WP_User|int $user User object or ID.
	 */
	public static function create_for_user( $user ) {
		global $wpdb;

		if ( is_numeric( $user ) ) {
			$user = get_userdata( (int) $user );
		}
		if ( ! $user instanceof WP_User ) {
			return;
		}

		$session_id = bin2hex( random_bytes( 32 ) );
		$device     = new ZACLS_Device();
		$device_id  = $device->get_device_id();
		$ip         = ZACLS_Main::get_ip();
		$timeout    = (int) get_option( 'zacls_session_timeout', 1440 );

		$wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prefix . 'zacls_sessions',
			array(
				'session_id'    => $session_id,
				'user_id'       => $user->ID,
				'ip'            => $ip,
				'device_id'     => $device_id,
				'user_agent'    => isset( $_SERVER['HTTP_USER_AGENT'] )
					? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) )
					: '',
				'login_time'    => current_time( 'mysql' ),
				'last_activity' => current_time( 'mysql' ),
				'expires_at'    => gmdate( 'Y-m-d H:i:s', time() + $timeout * 60 ),
				'is_active'     => 1,
			),
			array( '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%d' )
		);

		if ( ! headers_sent() ) {
			self::set_session_cookie( $session_id );
		}
	}

	/**
	 * Set the session cookie.
	 *
	 * @param string $session_id The session ID.
	 */
	private static function set_session_cookie( $session_id ) {
		if ( headers_sent() ) {
			return;
		}
		$timeout = (int) get_option( 'zacls_session_timeout', 1440 );
		setcookie(
			'zacls_session',
			$session_id,
			time() + $timeout * 60,
			COOKIEPATH,
			COOKIE_DOMAIN,
			is_ssl(),
			true
		);
	}

	/**
	 * Destroy session on logout.
	 */
	public function destroy_session() {
		global $wpdb;

		$session_id = self::get_session_id_from_cookie();
		if ( ! empty( $session_id ) ) {
			$wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
				$wpdb->prefix . 'zacls_sessions',
				array( 'is_active' => 0 ),
				array( 'session_id' => $session_id ),
				array( '%d' ),
				array( '%s' )
			);
		}

		if ( ! headers_sent() ) {
			setcookie( 'zacls_session', '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
		}
	}

	/**
	 * Check if the current session has timed out.
	 */
	public function check_session_timeout() {
		if ( ! is_user_logged_in() ) {
			return;
		}

		$session_id = self::get_session_id_from_cookie();
		if ( empty( $session_id ) ) {
			return;
		}

		if ( ! $this->is_session_valid( get_current_user_id(), $session_id ) ) {
			wp_logout();
			wp_safe_redirect( wp_login_url() );
			exit;
		}

		$this->update_activity( $session_id );
	}

	/**
	 * Check if a session is valid.
	 *
	 * @param int    $user_id    User ID.
	 * @param string $session_id Session ID.
	 * @return bool
	 */
	public function is_session_valid( $user_id, $session_id ) {
		global $wpdb;

		$session = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT * FROM {$wpdb->prefix}zacls_sessions
				WHERE session_id = %s AND user_id = %d AND is_active = 1
				LIMIT 1",
				$session_id,
				$user_id
			)
		);

		if ( ! $session ) {
			return false;
		}

		$timeout_minutes = (int) get_option( 'zacls_session_timeout', 1440 );
		$inactive_limit  = (int) get_option( 'zacls_session_inactive_limit', 30 );
		$inactive_secs   = time() - strtotime( $session->last_activity );

		if ( $inactive_secs > $inactive_limit * 60 ) {
			return false;
		}

		if ( strtotime( $session->expires_at ) < time() ) {
			return false;
		}

		return true;
	}

	/**
	 * Update last activity timestamp.
	 *
	 * @param string $session_id Session ID.
	 */
	private function update_activity( $session_id ) {
		global $wpdb;

		$wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prefix . 'zacls_sessions',
			array( 'last_activity' => current_time( 'mysql' ) ),
			array( 'session_id' => $session_id ),
			array( '%s' ),
			array( '%s' )
		);
	}

	/**
	 * Get session ID from cookie.
	 *
	 * @return string
	 */
	public static function get_session_id_from_cookie() {
		if ( isset( $_COOKIE['zacls_session'] ) ) {
			return sanitize_text_field( wp_unslash( $_COOKIE['zacls_session'] ) );
		}
		return '';
	}

	/**
	 * Get active sessions for a user.
	 *
	 * @param int $user_id User ID.
	 * @return array
	 */
	public static function get_user_sessions( $user_id ) {
		global $wpdb;

		return $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT * FROM {$wpdb->prefix}zacls_sessions
				WHERE user_id = %d AND is_active = 1
				ORDER BY last_activity DESC",
				$user_id
			),
			ARRAY_A
		);
	}

	/**
	 * Terminate a specific session.
	 *
	 * @param string $session_id Session ID.
	 * @return bool
	 */
	public static function terminate_session( $session_id ) {
		global $wpdb;

		$result = $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prefix . 'zacls_sessions',
			array( 'is_active' => 0 ),
			array( 'session_id' => $session_id ),
			array( '%d' ),
			array( '%s' )
		);

		return false !== $result;
	}

	/**
	 * Terminate all sessions for a user except current.
	 *
	 * @param int $user_id User ID.
	 */
	public static function terminate_other_sessions( $user_id ) {
		global $wpdb;

		$wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prefix . 'zacls_sessions',
			array( 'is_active' => 0 ),
			array(
				'user_id'   => $user_id,
				'is_active' => 1,
			),
			array( '%d' ),
			array( '%d', '%d' )
		);
	}

	/**
	 * Cleanup expired sessions.
	 */
	public static function cleanup_expired() {
		global $wpdb;

		$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"UPDATE {$wpdb->prefix}zacls_sessions SET is_active = 0 WHERE expires_at < %s",
				current_time( 'mysql' )
			)
		);
	}
}
