<?php
/**
 * Guard Dog Sessions Management Page
 *
 * Provides a comprehensive view of all active user sessions across the site,
 * with filtering, bulk actions, and the ability to terminate sessions remotely.
 *
 * @package GuardDog
 * @since 1.9.1
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly
}

class Guard_Dog_Sessions_Page {

    /**
     * Query arg used for the inner Sessions tabs.
     */
    private const TAB_QUERY_ARG = 'sessions_tab';

    /**
     * Session manager instance.
     * @var Guard_Dog_Session_Manager
     */
    private $session_manager;

    /**
     * Current filter parameters.
     * @var array
     */
    private $filters = array();

    /**
     * Current active tab
     * @var string
     */
    private $current_tab;

    /**
     * Available tabs configuration
     * @var array
     */
    private $tabs;

    /**
     * Constructor.
     *
     * @param Guard_Dog_Session_Manager $session_manager The session manager instance.
     */
    public function __construct( $session_manager ) {
        $this->session_manager = $session_manager;
        $this->parse_filters();
        $this->initialize_tabs();
        $this->determine_current_tab();
    }

    /**
     * Initialize tabs configuration
     */
    private function initialize_tabs(): void {
        $this->tabs = [
            'active_sessions' => [
                'label' => __( 'Active Sessions', 'guard-dog' ),
                'icon' => 'dashicons-laptop',
            ],
            'settings' => [
                'label' => __( 'Settings', 'guard-dog' ),
                'icon' => 'dashicons-admin-generic',
            ],
        ];
    }

    /**
     * Determine the current active tab
     */
    private function determine_current_tab(): void {
        $this->current_tab = 'active_sessions';

        $tab_param = $this->get_requested_tab();
        if ( $tab_param && array_key_exists( $tab_param, $this->tabs ) ) {
            $this->current_tab = $tab_param;
        }
    }

    /**
     * Get the requested Sessions tab from the current request.
     *
     * Supports the new dedicated query arg and the legacy nonce-protected tab
     * links so older URLs continue to resolve.
     */
    private function get_requested_tab(): string {
        if ( isset( $_GET[ self::TAB_QUERY_ARG ] ) ) {
            return sanitize_text_field( wp_unslash( $_GET[ self::TAB_QUERY_ARG ] ) );
        }

        if ( isset( $_GET['tab'] ) && isset( $_GET['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'guard_dog_sessions_tab' ) ) {
            return sanitize_text_field( wp_unslash( $_GET['tab'] ) );
        }

        return '';
    }

    /**
     * Determine whether Sessions is being rendered inside Users & Sessions.
     */
    private function is_embedded_in_users_sessions(): bool {
        return isset( $_GET['page'] ) && 'guard-dog-users-sessions' === sanitize_text_field( wp_unslash( $_GET['page'] ) );
    }

    /**
     * Build a Sessions tab URL for the current page context.
     */
    private function get_tab_url( string $tab_id ): string {
        $args = [ self::TAB_QUERY_ARG => $tab_id ];

        if ( $this->is_embedded_in_users_sessions() ) {
            $args['users_sessions_tab'] = 'sessions';
        }

        return add_query_arg(
            $args,
            remove_query_arg( [ 'tab', '_wpnonce' ] )
        );
    }

    /**
     * Get the active admin page slug for links and forms.
     */
    private function get_current_page_slug(): string {
        return isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : 'guard-dog-sessions';
    }

    /**
     * Parse filter parameters from request.
     */
    private function parse_filters() {
        $this->filters = array(
            'user_id'    => isset( $_GET['user_id'] ) ? absint( $_GET['user_id'] ) : 0,
            'ip_address' => isset( $_GET['ip_address'] ) ? sanitize_text_field( wp_unslash( $_GET['ip_address'] ) ) : '',
            'status'     => isset( $_GET['status'] ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : 'all',
        );
    }

    /**
     * Render the Sessions page.
     */
    public function render() {
        // Handle actions
        $this->handle_actions();

        ?>
        <div class="wrap guard-dog-admin">
            <h1 class="guard-dog-page-title">
                <span class="dashicons dashicons-laptop"></span>
                <?php esc_html_e( 'Session Management', 'guard-dog' ); ?>
            </h1>

            <p class="guard-dog-page-subtitle">
                <?php esc_html_e( 'Monitor and manage active user sessions, configure session limits, and protect against suspicious activity.', 'guard-dog' ); ?>
            </p>

            <?php $this->render_notices(); ?>

            <div class="guard-dog-admin-grid">
                <div class="guard-dog-main-content">
                    <!-- Integrated Tabbed Interface -->
                    <div class="guard-dog-tabbed-interface">
                        <nav class="guard-dog-tab-nav">
                            <?php foreach ( $this->tabs as $tab_id => $tab_config ) : ?>
                                <a href="<?php echo esc_url( $this->get_tab_url( $tab_id ) ); ?>"
                                   class="guard-dog-tab-link<?php echo esc_attr( $this->current_tab === $tab_id ? ' active' : '' ); ?>"
                                   data-tab="<?php echo esc_attr( $tab_id ); ?>"
                                   data-storage-key="guard_dog_sessions_active_tab"
                                   data-query-param="<?php echo esc_attr( self::TAB_QUERY_ARG ); ?>">
                                    <?php if ( isset( $tab_config['icon'] ) ) : ?>
                                        <span class="dashicons <?php echo esc_attr( $tab_config['icon'] ); ?>"></span>
                                    <?php endif; ?>
                                    <?php echo esc_html( $tab_config['label'] ); ?>
                                </a>
                            <?php endforeach; ?>
                        </nav>

                        <div class="guard-dog-tab-content">
                            <?php $this->render_all_tab_panels(); ?>
                        </div>
                    </div>
                </div>

                <div class="guard-dog-sidebar">
                    <?php $this->render_help_sidebar(); ?>
                </div>
            </div>
        </div>

        <?php
    }

    /**
     * Render the page content without the outer page wrapper
     *
     * Used by the consolidated Users & Sessions page to embed
     * Session Management content within a tab panel. Handles
     * actions and renders the internal tabbed interface
     * (active sessions, settings) with inline styles.
     *
     * @return void
     */
    public function render_page_content(): void {
        $this->handle_actions();

        $this->render_notices();
        ?>
        <div class="guard-dog-tabbed-interface">
            <nav class="guard-dog-tab-nav">
                <?php foreach ( $this->tabs as $tab_id => $tab_config ) : ?>
                    <a href="<?php echo esc_url( $this->get_tab_url( $tab_id ) ); ?>"
                       class="guard-dog-tab-link<?php echo esc_attr( $this->current_tab === $tab_id ? ' active' : '' ); ?>"
                       data-tab="<?php echo esc_attr( $tab_id ); ?>"
                       data-storage-key="guard_dog_sessions_active_tab"
                       data-query-param="<?php echo esc_attr( self::TAB_QUERY_ARG ); ?>">
                        <?php if ( isset( $tab_config['icon'] ) ) : ?>
                            <span class="dashicons <?php echo esc_attr( $tab_config['icon'] ); ?>"></span>
                        <?php endif; ?>
                        <?php echo esc_html( $tab_config['label'] ); ?>
                    </a>
                <?php endforeach; ?>
            </nav>

            <div class="guard-dog-tab-content">
                <?php $this->render_all_tab_panels(); ?>
            </div>
        </div>

        <?php
    }

    /**
     * Render all tab panels
     */
    private function render_all_tab_panels(): void {
        foreach ( $this->tabs as $tab_id => $tab_config ) {
            $is_active = $this->current_tab === $tab_id;
            $active_class = $is_active ? ' active' : '';
            ?>
            <div id="<?php echo esc_attr( $tab_id ); ?>" class="guard-dog-tab-panel<?php echo esc_attr( $active_class ); ?>">
                <?php
                switch ( $tab_id ) {
                    case 'active_sessions':
                        $this->render_active_sessions_tab();
                        break;
                    case 'settings':
                        $this->render_settings_tab();
                        break;
                }
                ?>
            </div>
            <?php
        }
    }

    /**
     * Render the Active Sessions tab
     */
    private function render_active_sessions_tab(): void {
        ?>
        <h3>
            <span class="dashicons dashicons-laptop"></span>
            <?php esc_html_e( 'Active Sessions', 'guard-dog' ); ?>
        </h3>

        <p><?php esc_html_e( 'View and manage all active user sessions. Suspicious sessions are highlighted and can be terminated to protect user accounts.', 'guard-dog' ); ?></p>

        <?php $this->render_filters(); ?>
        <?php $this->render_sessions_table(); ?>
        <?php
    }

    /**
     * Render the Settings tab
     */
    private function render_settings_tab(): void {
        ?>
        <h3>
            <span class="dashicons dashicons-admin-generic"></span>
            <?php esc_html_e( 'Session Settings', 'guard-dog' ); ?>
        </h3>

        <p><?php esc_html_e( 'Configure session tracking, timeout settings, and suspicious activity detection.', 'guard-dog' ); ?></p>

        <?php $this->render_settings_form(); ?>
        <?php
    }

    /**
     * Render the help sidebar
     */
    private function render_help_sidebar(): void {
        ?>
        <div class="guard-dog-card">
            <h3>
                <span class="dashicons dashicons-info"></span>
                <?php esc_html_e( 'Session Management Help', 'guard-dog' ); ?>
            </h3>

            <div class="guard-dog-help-section">
                <h4><?php esc_html_e( 'Session Tracking', 'guard-dog' ); ?></h4>
                <p><?php esc_html_e( 'Sessions are tracked to monitor user activity and detect suspicious behavior like simultaneous logins from different locations.', 'guard-dog' ); ?></p>
            </div>

            <div class="guard-dog-help-section">
                <h4><?php esc_html_e( 'Suspicious Activity', 'guard-dog' ); ?></h4>
                <p><?php esc_html_e( 'Sessions are flagged when IP addresses change unexpectedly or when concurrent sessions are detected from different countries.', 'guard-dog' ); ?></p>
            </div>

            <div class="guard-dog-help-section">
                <h4><?php esc_html_e( 'IP Reputation', 'guard-dog' ); ?></h4>
                <p><?php esc_html_e( 'Guard Dog can automatically block logins and terminate sessions from IP addresses with known malicious activity.', 'guard-dog' ); ?></p>
            </div>

            <div class="guard-dog-help-section">
                <h4><?php esc_html_e( 'Reverse Proxy Setup', 'guard-dog' ); ?></h4>
                <p><?php esc_html_e( 'If your site is behind a CDN, load balancer, or managed host (Cloudflare, Kinsta, WP Engine, AWS, etc.), you may need to configure the IP detection method so Guard Dog sees real visitor IPs instead of proxy IPs.', 'guard-dog' ); ?></p>
                <p><strong><?php esc_html_e( 'Warning:', 'guard-dog' ); ?></strong> <?php esc_html_e( 'Misconfiguring these settings can cause Guard Dog to detect incorrect IP addresses, which affects all IP-based features including login limiting, access control, IP reputation blocking, and activity logging. If unsure, leave the default "Auto" setting.', 'guard-dog' ); ?></p>
                <ul class="guard-dog-tips-list">
                    <li><?php esc_html_e( 'Cloudflare users: select "Cloudflare" method', 'guard-dog' ); ?></li>
                    <li><?php esc_html_e( 'Kinsta / WP Engine: select "X-Forwarded-For"', 'guard-dog' ); ?></li>
                    <li><?php esc_html_e( 'Add your proxy IPs to the trusted list', 'guard-dog' ); ?></li>
                    <li><?php esc_html_e( 'Always verify using the "Detected IP" row', 'guard-dog' ); ?></li>
                </ul>
            </div>

            <div class="guard-dog-help-section">
                <h4><?php esc_html_e( 'Tips & Best Practices', 'guard-dog' ); ?></h4>
                <ul class="guard-dog-tips-list">
                    <li><?php esc_html_e( 'Limit concurrent sessions to prevent account sharing', 'guard-dog' ); ?></li>
                    <li><?php esc_html_e( 'Enable IP shift detection for high-security sites', 'guard-dog' ); ?></li>
                    <li><?php esc_html_e( 'Set reasonable timeout values (30-60 minutes)', 'guard-dog' ); ?></li>
                    <li><?php esc_html_e( 'Review suspicious sessions regularly', 'guard-dog' ); ?></li>
                    <li><?php esc_html_e( 'Use IP reputation blocking to prevent attacks', 'guard-dog' ); ?></li>
                </ul>
            </div>
        </div>
        <?php
    }

    /**
     * Handle page actions.
     *
     * Note: Session termination is now handled via AJAX for better UX.
     * This method is kept for potential future non-AJAX actions.
     */
    private function handle_actions() {
        // Session termination is now handled via AJAX in guard-dog-session-manager.js
        // This prevents page reloads and allows rapid termination of multiple sessions.
    }

    /**
     * Render admin notices.
     */
    private function render_notices() {
        settings_errors( 'guard_dog_sessions' );
    }

    /**
     * Render filter controls.
     */
    private function render_filters() {
        $current_page = $this->get_current_page_slug();
        $reset_args    = [ 'page' => $current_page ];

        if ( $this->is_embedded_in_users_sessions() ) {
            $reset_args['users_sessions_tab'] = 'sessions';
        }

        $reset_args[ self::TAB_QUERY_ARG ] = $this->current_tab;
        $current_url = add_query_arg( $reset_args, admin_url( 'admin.php' ) );
        ?>
        <form method="get" class="guard-dog-sessions-filters">
            <input type="hidden" name="page" value="<?php echo esc_attr( $current_page ); ?>">
            <?php if ( $this->is_embedded_in_users_sessions() ) : ?>
                <input type="hidden" name="users_sessions_tab" value="sessions">
            <?php endif; ?>
            <input type="hidden" name="<?php echo esc_attr( self::TAB_QUERY_ARG ); ?>" value="<?php echo esc_attr( $this->current_tab ); ?>">

            <div class="guard-dog-filter-group">
                <label for="filter-user"><?php esc_html_e( 'User', 'guard-dog' ); ?></label>
                <?php
                wp_dropdown_users( array(
                    'name'             => 'user_id',
                    'id'               => 'filter-user',
                    'show_option_all'  => __( 'All Users', 'guard-dog' ),
                    'selected'         => $this->filters['user_id'],
                    'class'            => 'regular-text',
                ) );
                ?>
            </div>

            <div class="guard-dog-filter-group">
                <label for="filter-ip"><?php esc_html_e( 'IP Address', 'guard-dog' ); ?></label>
                <input type="text"
                       id="filter-ip"
                       name="ip_address"
                       value="<?php echo esc_attr( $this->filters['ip_address'] ); ?>"
                       placeholder="<?php esc_attr_e( 'e.g., 192.168.1.1', 'guard-dog' ); ?>"
                       class="regular-text">
            </div>

            <div class="guard-dog-filter-group">
                <label for="filter-status"><?php esc_html_e( 'Status', 'guard-dog' ); ?></label>
                <select id="filter-status" name="status" class="regular-text">
                    <option value="all" <?php selected( $this->filters['status'], 'all' ); ?>><?php esc_html_e( 'All Sessions', 'guard-dog' ); ?></option>
                    <option value="active" <?php selected( $this->filters['status'], 'active' ); ?>><?php esc_html_e( 'Active Only', 'guard-dog' ); ?></option>
                    <option value="suspicious" <?php selected( $this->filters['status'], 'suspicious' ); ?>><?php esc_html_e( 'Suspicious Only', 'guard-dog' ); ?></option>
                </select>
            </div>

            <div class="guard-dog-filter-group">
                <button type="submit" class="button"><?php esc_html_e( 'Filter', 'guard-dog' ); ?></button>
                <a href="<?php echo esc_url( $current_url ); ?>" class="button"><?php esc_html_e( 'Reset', 'guard-dog' ); ?></a>
            </div>
        </form>
        <?php
    }

    /**
     * Render the sessions table.
     */
    private function render_sessions_table() {
        global $wpdb;

        $sessions = $this->get_filtered_sessions();

        if ( empty( $sessions ) ) {
            echo '<p>' . esc_html__( 'No sessions found matching your criteria.', 'guard-dog' ) . '</p>';
            return;
        }

        // Get current user's session token for comparison
        $current_token_hash = null;
        $current_user_id = get_current_user_id();
        $cookie = wp_parse_auth_cookie( '', 'logged_in' );
        if ( $cookie && ! empty( $cookie['token'] ) ) {
            $current_token_hash = hash( 'sha256', $cookie['token'] );
        }

        ?>
        <table class="wp-list-table widefat fixed striped">
            <thead>
                <tr>
                    <th scope="col" style="width: 15%;"><?php esc_html_e( 'User', 'guard-dog' ); ?></th>
                    <th scope="col" style="width: 20%;"><?php esc_html_e( 'Device / Browser', 'guard-dog' ); ?></th>
                    <th scope="col" style="width: 15%;"><?php esc_html_e( 'Location', 'guard-dog' ); ?></th>
                    <th scope="col" style="width: 12%;"><?php esc_html_e( 'IP Address', 'guard-dog' ); ?></th>
                    <th scope="col" style="width: 12%;"><?php esc_html_e( 'Last Activity', 'guard-dog' ); ?></th>
                    <th scope="col" style="width: 10%;"><?php esc_html_e( 'Status', 'guard-dog' ); ?></th>
                    <th scope="col" style="width: 16%;"><?php esc_html_e( 'Actions', 'guard-dog' ); ?></th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ( $sessions as $session ) :
                    $user = get_userdata( $session->user_id );
                    $is_current = ( $current_token_hash && $session->session_token_hash === $current_token_hash );
                    $device_info = $this->parse_user_agent( $session->user_agent );
                    $location = $this->format_location( $session->geo_country, $session->geo_city );

                ?>
                    <tr class="<?php echo $session->is_compromised ? 'guard-dog-compromised-row' : ''; ?>">
                        <td>
                            <?php if ( $user ) : ?>
                                <strong><?php echo esc_html( $user->display_name ); ?></strong>
                                <div class="guard-dog-session-meta"><?php echo esc_html( $user->user_login ); ?></div>
                            <?php else : ?>
                                <em><?php esc_html_e( 'Unknown User', 'guard-dog' ); ?></em>
                            <?php endif; ?>
                        </td>
                        <td>
                            <?php echo esc_html( $device_info ); ?>
                            <?php if ( $is_current ) : ?>
                                <span class="guard-dog-current-badge"><?php esc_html_e( 'Current', 'guard-dog' ); ?></span>
                            <?php endif; ?>
                            <div class="guard-dog-session-meta">
                                <?php
                                echo esc_html(
                                    sprintf(
                                        /* translators: %s: time ago */
                                        __( 'Started %s ago', 'guard-dog' ),
                                        human_time_diff( strtotime( $session->created_at ), current_time( 'timestamp', true ) )
                                    )
                                );
                                ?>
                            </div>
                        </td>
                        <td><?php echo esc_html( $location ); ?></td>
                        <td><code><?php echo esc_html( $session->ip_address ); ?></code></td>
                        <td>
                            <?php
                            echo esc_html(
                                human_time_diff( strtotime( $session->last_activity ), current_time( 'timestamp', true ) )
                                . ' ' . __( 'ago', 'guard-dog' )
                            );
                            ?>
                        </td>
                        <td>
                            <?php if ( $session->is_compromised ) : ?>
                                <span class="guard-dog-status-warning" title="<?php echo esc_attr( $session->compromised_reason ); ?>">
                                    ⚠ <?php esc_html_e( 'Suspicious', 'guard-dog' ); ?>
                                </span>
                            <?php else : ?>
                                <span class="guard-dog-status-ok">● <?php esc_html_e( 'Active', 'guard-dog' ); ?></span>
                            <?php endif; ?>
                        </td>
                        <td>
                            <?php if ( ! $is_current ) : ?>
                                <button type="button"
                                        class="button button-small guard-dog-terminate-session"
                                        data-session-id="<?php echo esc_attr( $session->session_id ); ?>">
                                    <?php esc_html_e( 'Terminate', 'guard-dog' ); ?>
                                </button>
                            <?php else : ?>
                                <span class="description"><?php esc_html_e( 'This session', 'guard-dog' ); ?></span>
                            <?php endif; ?>
                        </td>
                    </tr>
                <?php endforeach; ?>
            </tbody>
        </table>
        <?php
    }

    /**
     * Get filtered sessions from database.
     *
     * @return array Array of session objects.
     */
    private function get_filtered_sessions() {
        global $wpdb;

        $table_name = Guard_Dog()->db->get_sessions_table_name();
        $escaped_table = esc_sql( $table_name );
        $now = current_time( 'mysql', true );

        // Build query based on filters
        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        // Direct database query is necessary for session retrieval with filtering
        // Table name is validated by Guard_Dog_DB
        // Caching is not appropriate for real-time session data

        if ( $this->filters['user_id'] && $this->filters['ip_address'] && 'suspicious' === $this->filters['status'] ) {
            $sessions = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT * FROM $escaped_table WHERE user_id = %d AND ip_address = %s AND is_compromised = 1 AND expires_at > %s ORDER BY last_activity DESC LIMIT 100",
                    $this->filters['user_id'],
                    $this->filters['ip_address'],
                    $now
                )
            );
        } elseif ( $this->filters['user_id'] && $this->filters['ip_address'] ) {
            $sessions = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT * FROM $escaped_table WHERE user_id = %d AND ip_address = %s AND expires_at > %s ORDER BY last_activity DESC LIMIT 100",
                    $this->filters['user_id'],
                    $this->filters['ip_address'],
                    $now
                )
            );
        } elseif ( $this->filters['user_id'] && 'suspicious' === $this->filters['status'] ) {
            $sessions = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT * FROM $escaped_table WHERE user_id = %d AND is_compromised = 1 AND expires_at > %s ORDER BY last_activity DESC LIMIT 100",
                    $this->filters['user_id'],
                    $now
                )
            );
        } elseif ( $this->filters['user_id'] ) {
            $sessions = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT * FROM $escaped_table WHERE user_id = %d AND expires_at > %s ORDER BY last_activity DESC LIMIT 100",
                    $this->filters['user_id'],
                    $now
                )
            );
        } elseif ( $this->filters['ip_address'] && 'suspicious' === $this->filters['status'] ) {
            $sessions = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT * FROM $escaped_table WHERE ip_address = %s AND is_compromised = 1 AND expires_at > %s ORDER BY last_activity DESC LIMIT 100",
                    $this->filters['ip_address'],
                    $now
                )
            );
        } elseif ( $this->filters['ip_address'] ) {
            $sessions = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT * FROM $escaped_table WHERE ip_address = %s AND expires_at > %s ORDER BY last_activity DESC LIMIT 100",
                    $this->filters['ip_address'],
                    $now
                )
            );
        } elseif ( 'suspicious' === $this->filters['status'] ) {
            $sessions = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT * FROM $escaped_table WHERE is_compromised = 1 AND expires_at > %s ORDER BY last_activity DESC LIMIT 100",
                    $now
                )
            );
        } else {
            $sessions = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT * FROM $escaped_table WHERE expires_at > %s ORDER BY last_activity DESC LIMIT 100",
                    $now
                )
            );
        }
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared

        if ( empty( $sessions ) ) {
            return array();
        }

        // Filter out orphaned Guard Dog sessions whose WordPress session token
        // no longer exists.  This prevents stale records from appearing when a
        // WP auth cookie expired independently of Guard Dog's session timeout.
        return $this->filter_orphaned_sessions( $sessions );
    }

    /**
     * Filter out sessions whose token hash no longer exists in WordPress session_tokens.
     *
     * Groups sessions by user_id to minimise user meta lookups, then validates
     * each Guard Dog record against the authoritative WP session token store.
     *
     * This method is READ-ONLY — it filters results for display without deleting
     * database records. Deleting during page render is a destructive side-effect
     * that can remove valid session records if the object cache returns stale
     * session_tokens data (e.g. after a cache flush or during high concurrency).
     * Orphaned records are cleaned up safely by cleanup_orphaned_sessions() during
     * login and by the hourly cron job.
     *
     * @since 1.9.34
     * @param array $sessions Array of session objects from the database.
     * @return array Filtered array containing only sessions with a valid WP token.
     */
    private function filter_orphaned_sessions( $sessions ) {
        // Build lookup of WP session tokens keyed by user_id (one DB read per user).
        $wp_tokens_by_user = array();
        $valid_sessions = array();

        foreach ( $sessions as $session ) {
            $uid = (int) $session->user_id;

            if ( ! isset( $wp_tokens_by_user[ $uid ] ) ) {
                $wp_tokens_by_user[ $uid ] = $this->get_authoritative_wp_session_tokens( $uid );
            }

            if ( isset( $wp_tokens_by_user[ $uid ][ $session->session_token_hash ] ) ) {
                $valid_sessions[] = $session;
            }
            // Orphaned records are intentionally left in the database. They will be
            // cleaned up by cleanup_orphaned_sessions() on the user's next login or
            // by the hourly cleanup_expired_sessions() cron once they pass retention.
        }

        return $valid_sessions;
    }

    /**
     * Fetch WordPress session_tokens directly from usermeta.
     *
     * This bypasses user-meta object cache so sessions-page filtering does not
     * hide valid sessions when stale cached metadata is returned.
     *
     * @since 1.9.35
     * @param int $user_id User ID.
     * @return array Session token map keyed by token hash.
     */
    private function get_authoritative_wp_session_tokens( $user_id ) {
        global $wpdb;

        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
        $raw_tokens = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT meta_value FROM {$wpdb->usermeta} WHERE user_id = %d AND meta_key = %s ORDER BY umeta_id DESC LIMIT 1",
                $user_id,
                'session_tokens'
            )
        );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery

        if ( null === $raw_tokens ) {
            return array();
        }

        $tokens = maybe_unserialize( $raw_tokens );
        return is_array( $tokens ) ? $tokens : array();
    }

    /**
     * Render settings form.
     */
    private function render_settings_form() {
        ?>
        <form method="post" action="options.php">
            <?php settings_fields( 'guard_dog_sessions_settings' ); ?>

            <table class="form-table">
                <tr>
                    <th scope="row">
                        <label for="guard_dog_sessions_enabled"><?php esc_html_e( 'Enable Session Tracking', 'guard-dog' ); ?></label>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox"
                                   id="guard_dog_sessions_enabled"
                                   name="guard_dog_sessions_enabled"
                                   value="1"
                                   <?php checked( get_option( 'guard_dog_sessions_enabled', true ) ); ?>>
                            <?php esc_html_e( 'Track user sessions in the database', 'guard-dog' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'When enabled, Guard Dog will track all user sessions for security monitoring.', 'guard-dog' ); ?>
                        </p>
                    </td>
                </tr>
                <tr>
                    <th scope="row">
                        <label for="guard_dog_sessions_max_per_user"><?php esc_html_e( 'Max Sessions Per User', 'guard-dog' ); ?></label>
                    </th>
                    <td>
                        <input type="number"
                               id="guard_dog_sessions_max_per_user"
                               name="guard_dog_sessions_max_per_user"
                               value="<?php echo esc_attr( get_option( 'guard_dog_sessions_max_per_user', 5 ) ); ?>"
                               min="1"
                               max="100"
                               class="small-text">
                        <p class="description">
                            <?php esc_html_e( 'Maximum concurrent sessions allowed per user. Oldest sessions are terminated when this limit is reached.', 'guard-dog' ); ?>
                        </p>
                    </td>
                </tr>
                <tr>
                    <th scope="row">
                        <label for="guard_dog_sessions_timeout_minutes"><?php esc_html_e( 'Session Timeout', 'guard-dog' ); ?></label>
                    </th>
                    <td>
                        <input type="number"
                               id="guard_dog_sessions_timeout_minutes"
                               name="guard_dog_sessions_timeout_minutes"
                               value="<?php echo esc_attr( get_option( 'guard_dog_sessions_timeout_minutes', 60 ) ); ?>"
                               min="5"
                               max="10080"
                               class="small-text">
                        <?php esc_html_e( 'minutes', 'guard-dog' ); ?>
                        <p class="description">
                            <?php esc_html_e( 'Sessions expire after this many minutes of inactivity.', 'guard-dog' ); ?>
                        </p>
                    </td>
                </tr>
                <tr>
                    <th scope="row"><?php esc_html_e( 'Suspicious Activity Detection', 'guard-dog' ); ?></th>
                    <td>
                        <fieldset>
                            <label>
                                <input type="checkbox"
                                       name="guard_dog_sessions_detect_ip_shift"
                                       value="1"
                                       <?php checked( get_option( 'guard_dog_sessions_detect_ip_shift', true ) ); ?>>
                                <?php esc_html_e( 'Detect IP address shifts (flag when session IP changes to a different country)', 'guard-dog' ); ?>
                            </label>
                            <br>
                            <label>
                                <input type="checkbox"
                                       name="guard_dog_sessions_detect_geo_anomaly"
                                       value="1"
                                       <?php checked( get_option( 'guard_dog_sessions_detect_geo_anomaly', true ) ); ?>>
                                <?php esc_html_e( 'Detect geographic anomalies (flag concurrent sessions from different countries)', 'guard-dog' ); ?>
                            </label>
                        </fieldset>
                    </td>
                </tr>
                <tr>
                    <th scope="row">
                        <label for="guard_dog_sessions_notify_user"><?php esc_html_e( 'User Notifications', 'guard-dog' ); ?></label>
                    </th>
                    <td>
                        <label>
                            <input type="checkbox"
                                   id="guard_dog_sessions_notify_user"
                                   name="guard_dog_sessions_notify_user"
                                   value="1"
                                   <?php checked( get_option( 'guard_dog_sessions_notify_user', false ) ); ?>>
                            <?php esc_html_e( 'Send email to users when suspicious activity is detected on their account', 'guard-dog' ); ?>
                        </label>
                    </td>
                </tr>
            </table>

            <h3 style="margin-top: 30px;"><?php esc_html_e( 'Reverse Proxy / Load Balancer', 'guard-dog' ); ?></h3>
            <p class="description" style="margin-bottom: 15px;">
                <?php esc_html_e( 'Configure how Guard Dog detects the real visitor IP address. If your site is behind a reverse proxy, CDN, or load balancer (Cloudflare, Kinsta, AWS ELB, etc.), the default settings may detect the proxy IP instead of the visitor\'s real IP, causing false IP shift alerts.', 'guard-dog' ); ?>
            </p>
            <div class="notice notice-warning inline" style="margin: 0 0 15px 0; padding: 10px 15px;">
                <p>
                    <strong><?php esc_html_e( 'Important:', 'guard-dog' ); ?></strong>
                    <?php esc_html_e( 'These settings affect how Guard Dog identifies visitors across all features, including login attempt limiting, IP blocking, access control, and activity logging. Incorrect configuration can cause Guard Dog to see wrong IP addresses. If you are not sure what to change, leave the default "Auto" setting and only add your proxy\'s IP to the trusted list.', 'guard-dog' ); ?>
                </p>
            </div>

            <table class="form-table">
                <tr>
                    <th scope="row">
                        <label for="guard_dog_ip_detection_method"><?php esc_html_e( 'IP Detection Method', 'guard-dog' ); ?></label>
                    </th>
                    <td>
                        <?php $current_method = get_option( 'guard_dog_ip_detection_method', 'auto' ); ?>
                        <select id="guard_dog_ip_detection_method" name="guard_dog_ip_detection_method">
                            <option value="auto" <?php selected( $current_method, 'auto' ); ?>><?php esc_html_e( 'Auto (check all headers)', 'guard-dog' ); ?></option>
                            <option value="cloudflare" <?php selected( $current_method, 'cloudflare' ); ?>><?php esc_html_e( 'Cloudflare (CF-Connecting-IP)', 'guard-dog' ); ?></option>
                            <option value="x_forwarded_for" <?php selected( $current_method, 'x_forwarded_for' ); ?>><?php esc_html_e( 'X-Forwarded-For', 'guard-dog' ); ?></option>
                            <option value="x_real_ip" <?php selected( $current_method, 'x_real_ip' ); ?>><?php esc_html_e( 'X-Real-IP', 'guard-dog' ); ?></option>
                            <option value="remote_addr" <?php selected( $current_method, 'remote_addr' ); ?>><?php esc_html_e( 'REMOTE_ADDR only (no proxy)', 'guard-dog' ); ?></option>
                        </select>
                        <p class="description">
                            <?php esc_html_e( 'Choose how Guard Dog determines the visitor\'s real IP address. Forwarded headers are only used when the direct connection comes from a trusted proxy below. Most sites behind Cloudflare should select "Cloudflare". Sites behind other load balancers typically use "X-Forwarded-For".', 'guard-dog' ); ?>
                        </p>
                    </td>
                </tr>
                <tr>
                    <th scope="row">
                        <label for="guard_dog_trusted_proxy_ips"><?php esc_html_e( 'Trusted Proxy IPs', 'guard-dog' ); ?></label>
                    </th>
                    <td>
                        <textarea id="guard_dog_trusted_proxy_ips"
                                  name="guard_dog_trusted_proxy_ips"
                                  rows="5"
                                  class="large-text code"
                                  placeholder="34.145.254.243&#10;10.0.0.0/8&#10;172.16.0.0/12"
                        ><?php echo esc_textarea( get_option( 'guard_dog_trusted_proxy_ips', '' ) ); ?></textarea>
                        <p class="description">
                            <?php esc_html_e( 'One IP address or CIDR range per line. Forwarded headers (X-Forwarded-For, CF-Connecting-IP, etc.) are only trusted when the direct connection comes from one of these IPs. IP shift detection will also ignore shifts between these proxy IPs. Leave empty to ignore forwarded headers and use REMOTE_ADDR.', 'guard-dog' ); ?>
                        </p>
                    </td>
                </tr>
                <tr>
                    <th scope="row"><?php esc_html_e( 'Detected IP', 'guard-dog' ); ?></th>
                    <td>
                        <?php
                        // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated
                        // 1. Context: Displaying REMOTE_ADDR for admin diagnostic purposes
                        // 2. Security: Output escaped with esc_html, read-only display
                        // 3. Sanitization: sanitize_text_field + wp_unslash applied
                        // 4. Justification: Admin-only settings page behind authentication
                        $detected_ip = Guard_Dog_Utils::get_user_ip();
                        $raw_remote  = isset( $_SERVER['REMOTE_ADDR'] )
                            ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) )
                            : __( 'N/A', 'guard-dog' );
                        // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotValidated
                        ?>
                        <code><?php echo esc_html( $detected_ip ? $detected_ip : __( 'Unknown', 'guard-dog' ) ); ?></code>
                        <span class="description">
                            &mdash; <?php esc_html_e( 'REMOTE_ADDR:', 'guard-dog' ); ?>
                            <code><?php echo esc_html( $raw_remote ); ?></code>
                        </span>
                        <p class="description">
                            <?php esc_html_e( 'Use this to verify your proxy settings are correct. "Detected IP" should show the real visitor IP, not the proxy IP. If both values are the same proxy IP, adjust your detection method or trusted proxy list.', 'guard-dog' ); ?>
                        </p>
                    </td>
                </tr>
            </table>

            <h3 style="margin-top: 30px;"><?php esc_html_e( 'IP Reputation Protection', 'guard-dog' ); ?></h3>
            <p class="description" style="margin-bottom: 15px;">
                <?php esc_html_e( 'Block or terminate sessions from IP addresses with known malicious activity. Uses a risk score from DNS blocklists (Spamhaus, CBL), Tor exit-node checks, and optional geo (ip-api.com or ipinfo.io).', 'guard-dog' ); ?>
            </p>

            <table class="form-table">
                <tr>
                    <th scope="row"><?php esc_html_e( 'Login Protection', 'guard-dog' ); ?></th>
                    <td>
                        <fieldset>
                            <label>
                                <input type="checkbox"
                                       id="guard_dog_ip_reputation_login_enabled"
                                       name="guard_dog_ip_reputation_login_enabled"
                                       value="1"
                                       <?php checked( get_option( 'guard_dog_ip_reputation_login_enabled', true ) ); ?>>
                                <?php esc_html_e( 'Block login attempts from high-risk IPs', 'guard-dog' ); ?>
                            </label>
                            <br><br>
                            <label>
                                <?php esc_html_e( 'Block login when abuse confidence is', 'guard-dog' ); ?>
                                <input type="number"
                                       id="guard_dog_ip_reputation_login_threshold"
                                       name="guard_dog_ip_reputation_login_threshold"
                                       value="<?php echo esc_attr( get_option( 'guard_dog_ip_reputation_login_threshold', 80 ) ); ?>"
                                       min="0"
                                       max="100"
                                       class="small-text">%
                                <?php esc_html_e( 'or higher', 'guard-dog' ); ?>
                            </label>
                            <p class="description">
                                <?php esc_html_e( 'Recommended: 80%. Higher values mean fewer blocked logins but may allow some suspicious IPs through.', 'guard-dog' ); ?>
                            </p>
                        </fieldset>
                    </td>
                </tr>
                <tr>
                    <th scope="row"><?php esc_html_e( 'Session Protection', 'guard-dog' ); ?></th>
                    <td>
                        <fieldset>
                            <label>
                                <input type="checkbox"
                                       id="guard_dog_ip_reputation_session_enabled"
                                       name="guard_dog_ip_reputation_session_enabled"
                                       value="1"
                                       <?php checked( get_option( 'guard_dog_ip_reputation_session_enabled', true ) ); ?>>
                                <?php esc_html_e( 'Terminate sessions when IP becomes high-risk', 'guard-dog' ); ?>
                            </label>
                            <br><br>
                            <label>
                                <?php esc_html_e( 'Terminate session when abuse confidence is', 'guard-dog' ); ?>
                                <input type="number"
                                       id="guard_dog_ip_reputation_session_threshold"
                                       name="guard_dog_ip_reputation_session_threshold"
                                       value="<?php echo esc_attr( get_option( 'guard_dog_ip_reputation_session_threshold', 90 ) ); ?>"
                                       min="0"
                                       max="100"
                                       class="small-text">%
                                <?php esc_html_e( 'or higher', 'guard-dog' ); ?>
                            </label>
                            <p class="description">
                                <?php esc_html_e( 'Recommended: 90%. This higher threshold ensures legitimate users on shared IPs are not falsely logged out.', 'guard-dog' ); ?>
                            </p>
                        </fieldset>
                    </td>
                </tr>
                <tr>
                    <th scope="row">
                        <label for="guard_dog_ipinfo_token"><?php esc_html_e( 'ipinfo.io token (optional)', 'guard-dog' ); ?></label>
                    </th>
                    <td>
                        <input type="text"
                               id="guard_dog_ipinfo_token"
                               name="guard_dog_ipinfo_token"
                               value="<?php echo esc_attr( get_option( 'guard_dog_ipinfo_token', '' ) ); ?>"
                               class="regular-text"
                               autocomplete="off">
                        <p class="description">
                            <?php esc_html_e( 'For HTTPS, higher limits, and commercial use. Leave empty to use ip-api.com only.', 'guard-dog' ); ?>
                        </p>
                    </td>
                </tr>
                <tr>
                    <th scope="row"><?php esc_html_e( 'Disable geo lookup', 'guard-dog' ); ?></th>
                    <td>
                        <input type="hidden" name="guard_dog_ip_reputation_disable_geo" value="0">
                        <label>
                            <input type="checkbox"
                                   id="guard_dog_ip_reputation_disable_geo"
                                   name="guard_dog_ip_reputation_disable_geo"
                                   value="1"
                                   <?php checked( get_option( 'guard_dog_ip_reputation_disable_geo', false ) ); ?>>
                            <?php esc_html_e( 'Use only DNS blocklists and Tor checks (no HTTP geo requests)', 'guard-dog' ); ?>
                        </label>
                    </td>
                </tr>
            </table>

            <?php submit_button(); ?>
        </form>
        <?php
    }

    /**
     * Parse user agent to get device/browser info.
     *
     * @param string $user_agent The user agent string.
     * @return string Parsed device description.
     */
    private function parse_user_agent( $user_agent ) {
        if ( empty( $user_agent ) ) {
            return __( 'Unknown device', 'guard-dog' );
        }

        $browser = 'Unknown Browser';
        $os = 'Unknown OS';

        // Detect browser
        if ( preg_match( '/Firefox\/[\d.]+/i', $user_agent ) ) {
            $browser = 'Firefox';
        } elseif ( preg_match( '/Edg\/[\d.]+/i', $user_agent ) ) {
            $browser = 'Edge';
        } elseif ( preg_match( '/Chrome\/[\d.]+/i', $user_agent ) && ! preg_match( '/Edg/i', $user_agent ) ) {
            $browser = 'Chrome';
        } elseif ( preg_match( '/Safari\/[\d.]+/i', $user_agent ) && ! preg_match( '/Chrome/i', $user_agent ) ) {
            $browser = 'Safari';
        } elseif ( preg_match( '/MSIE|Trident/i', $user_agent ) ) {
            $browser = 'Internet Explorer';
        }

        // Detect OS
        if ( preg_match( '/Windows NT 10/i', $user_agent ) ) {
            $os = 'Windows';
        } elseif ( preg_match( '/Windows/i', $user_agent ) ) {
            $os = 'Windows';
        } elseif ( preg_match( '/Mac OS X/i', $user_agent ) ) {
            $os = 'macOS';
        } elseif ( preg_match( '/Linux/i', $user_agent ) ) {
            $os = 'Linux';
        } elseif ( preg_match( '/iPhone|iPad/i', $user_agent ) ) {
            $os = 'iOS';
        } elseif ( preg_match( '/Android/i', $user_agent ) ) {
            $os = 'Android';
        }

        return $browser . ' on ' . $os;
    }

    /**
     * Format location from country and city.
     *
     * @param string|null $country Country code.
     * @param string|null $city City name.
     * @return string Formatted location.
     */
    private function format_location( $country, $city ) {
        if ( $city && $country ) {
            return $city . ', ' . $country;
        } elseif ( $country ) {
            return $country;
        } elseif ( $city ) {
            return $city;
        }

        return __( 'Unknown', 'guard-dog' );
    }

} // End class Guard_Dog_Sessions_Page
