<?php
/**
 * Handles the Temporary Access feature for Guard Dog.
 * Based on proven patterns from WordPress.org temporary login plugins.
 *
 * @package GuardDog
 * @since 1.6.0
 */

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

class Guard_Dog_Temp_Access {

    /**
     * User meta key to identify temporary users.
     * @var string
     */
    const USER_META_TEMP_USER = '_guard_dog_temp_user';

    /**
     * User meta key for expiry timestamp.
     * @var string
     */
    const USER_META_EXPIRY = '_guard_dog_temp_expiry';

    /**
     * User meta key for creation timestamp.
     * @var string
     */
    const USER_META_CREATED = '_guard_dog_temp_created';

    /**
     * User meta key for login token.
     * @var string
     */
    const USER_META_TOKEN = '_guard_dog_temp_token';

    /**
     * User meta key for hashed login token.
     * @var string
     */
    const USER_META_TOKEN_HASH = '_guard_dog_temp_token_hash';

    /**
     * User meta key for max login limit.
     * @var string
     */
    const USER_META_MAX_LOGINS = '_guard_dog_temp_max_logins';

    /**
     * User meta key for login count.
     * @var string
     */
    const USER_META_LOGIN_COUNT = '_guard_dog_temp_login_count';

    /**
     * User meta key for last login.
     * @var string
     */
    const USER_META_LAST_LOGIN = '_guard_dog_temp_last_login';

    /**
     * User meta key for redirect after login.
     * @var string
     */
    const USER_META_REDIRECT = '_guard_dog_temp_redirect';

    /**
     * User meta key for the display email.
     * @var string
     */
    const USER_META_DISPLAY_EMAIL = '_guard_dog_temp_display_email';

    /**
     * Query variable for the token.
     * @var string
     */
    const QUERY_VAR = 'guard_dog_temp_token';
    private $activity_log = null;

    /**
     * Constructor. Adds hooks.
     *
     * @since 1.6.0
     */
    public function __construct($activity_log_instance) {
        $this->activity_log = $activity_log_instance;
        $this->add_hooks();
    }

    /**
     * Adds the necessary WordPress hooks.
     *
     * @since 1.6.0
     */
    public function add_hooks() {
        // Handle incoming access requests
        add_action('init', [$this, 'handle_temp_access_request']);

        // Prevent temporary users from logging in normally
        add_filter('wp_authenticate_user', [$this, 'prevent_temp_user_normal_login'], 10, 2);

        // Prevent password reset for temporary users
        add_filter('allow_password_reset', [$this, 'prevent_temp_user_password_reset'], 10, 2);

        // Show admin bar notice for temporary users
        add_action('admin_bar_menu', [$this, 'show_temp_access_admin_bar_notice'], 999);

        // Restrict access for temporary users
        add_action('current_screen', [$this, 'restrict_temp_user_access']);

        // Clean up expired users - use init with later priority to ensure user functions are loaded
        add_action('init', [$this, 'cleanup_expired_users'], 20);
    }

    //--------------------------------------------------------------------------
    // Public Methods for Creating Temporary Access
    //--------------------------------------------------------------------------

    /**
     * Create a new temporary access user.
     *
     * @since 1.6.0
     * @param array $data User data array
     * @return array|WP_Error Success data or error
     */
    public function create_temporary_user($data) {
        // Validate inputs
        $validation = $this->validate_user_data($data);
        if (is_wp_error($validation)) {
            return $validation;
        }

        // Extract validated data
        $username = sanitize_user($data['username']);
        $display_email = sanitize_email($data['email']);
        $role = sanitize_text_field($data['role']);
        $expiry_days = absint($data['expiry']);
        $max_logins = absint($data['max_logins']);

        $password = wp_generate_password(20, true, true);

        // Generate a unique internal email to prevent conflicts with existing users.
        $internal_email = $username . '@temp.guarddog.localhost';

        // Create WordPress user
        $user_args = [
            'user_login' => $username,
            'user_pass' => $password,
            'user_email' => $internal_email,
            'first_name' => sanitize_text_field($data['first_name'] ?? $username),
            'last_name' => sanitize_text_field($data['last_name'] ?? ''),
            'role' => $role,
            'show_admin_bar_front' => false,
        ];

        $user_id = wp_insert_user($user_args);

        if (is_wp_error($user_id)) {
            return $user_id;
        }

        // Generate token
        $token = $this->generate_token($user_id);

        // Calculate expiry time (days from now)
        $expiry_time = time() + ($expiry_days * DAY_IN_SECONDS);

        // Store temporary user metadata
        update_user_meta($user_id, self::USER_META_TEMP_USER, true);
        update_user_meta($user_id, self::USER_META_CREATED, time());
        update_user_meta($user_id, self::USER_META_EXPIRY, $expiry_time);
        update_user_meta($user_id, self::USER_META_TOKEN_HASH, $this->hash_token($token));
        delete_user_meta($user_id, self::USER_META_TOKEN);
        update_user_meta($user_id, self::USER_META_MAX_LOGINS, $max_logins);
        update_user_meta($user_id, self::USER_META_LOGIN_COUNT, 0);
        update_user_meta($user_id, self::USER_META_REDIRECT, 'admin');
        update_user_meta($user_id, self::USER_META_DISPLAY_EMAIL, $display_email);

        // Hide admin welcome panel
        update_user_meta($user_id, 'show_welcome_panel', 0);

        // Insert into audit table for better auditability
        if (function_exists('Guard_Dog') && Guard_Dog()->db) {
            $audit_data = [
                'username' => $username,
                'email' => $display_email,
                'password' => $password,
                'role' => $role,
                'expires_at' => $expiry_time,
                'max_logins' => $max_logins,
            ];
            Guard_Dog()->db->insert_temp_access_user($audit_data, $user_id);
        }

        // Generate access URL
        $access_url = add_query_arg(self::QUERY_VAR, $token, home_url('/'));

        return [
            'success' => true,
            'user_id' => $user_id,
            'token' => $token,
            'url' => $access_url,
            'expires' => $expiry_time,
            'username' => $username
        ];
    }

    /**
     * Get all temporary users with their status.
     *
     * @since 1.6.0
     * @return array Array of temporary users
     */
    public function get_temporary_users() {
        // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value
        // This query is necessary to identify temporary users. The performance impact is acceptable
        // as this is only called in admin context and temporary users are typically few in number.
        $users = get_users([
            'meta_key' => self::USER_META_TEMP_USER,
            'meta_value' => true,
            'fields' => 'all'
        ]);
        // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value

        $temp_users = [];
        $current_time = time();

        foreach ($users as $user) {
            // Clear user meta cache to ensure fresh data
            wp_cache_delete($user->ID, 'user_meta');
            
            $raw_expiry = get_user_meta($user->ID, self::USER_META_EXPIRY, true);
            $expiry = (int) $raw_expiry;
            $created = get_user_meta($user->ID, self::USER_META_CREATED, true);
            $login_count = (int) get_user_meta($user->ID, self::USER_META_LOGIN_COUNT, true);
            $max_logins = (int) get_user_meta($user->ID, self::USER_META_MAX_LOGINS, true);
            $last_login = get_user_meta($user->ID, self::USER_META_LAST_LOGIN, true);
            $display_email = get_user_meta($user->ID, self::USER_META_DISPLAY_EMAIL, true);

            $is_expired = $current_time > $expiry;
            $is_overused = $max_logins > 0 && $login_count >= $max_logins;

            $status = 'active';
            if ($is_expired) {
                $status = 'expired';
            } elseif ($is_overused) {
                $status = 'used_up';
            } elseif ($current_time > ($expiry - 3600)) { // Less than 1 hour remaining
                $status = 'expiring_soon';
            }

            $temp_users[] = [
                'id' => $user->ID,
                'username' => $user->user_login,
                'display_name' => trim($user->first_name . ' ' . $user->last_name),
                'name' => trim($user->first_name . ' ' . $user->last_name),
                'email' => $display_email ?: $user->user_email,
                'role' => $user->roles[0] ?? 'subscriber',
                'created' => $created,
                'expires' => $expiry,
                'expires_at' => gmdate('Y-m-d H:i:s', $expiry),
                'login_count' => $login_count,
                'logins_used' => $login_count,
                'max_logins' => $max_logins,
                'last_login' => $last_login,
                'status' => $status,
                'token' => '',
                'url' => '',
                'login_url' => '',
                'link_available' => false
            ];
        }

        return $temp_users;
    }

    /**
     * Delete a temporary user.
     *
     * @since 1.6.0
     * @param int $user_id User ID to delete
     * @return bool Success status
     */
    public function delete_temporary_user($user_id) {
        if (!$this->is_temporary_user($user_id)) {
            return false;
        }

        // Get username before deletion for audit table cleanup
        $user = get_user_by('ID', $user_id);
        $username = $user ? $user->user_login : '';

        if (!function_exists('wp_delete_user')) {
            return false;
        }

        $result = wp_delete_user($user_id) !== false;

        // Clean up audit table entry
        if ($result && $username && function_exists('Guard_Dog') && Guard_Dog()->db) {
            Guard_Dog()->db->delete_temp_access_user($username);
        }

        return $result;
    }

    /**
     * Check if a user is a temporary user.
     *
     * @since 1.6.0
     * @param int $user_id User ID to check
     * @return bool True if user is temporary
     */
    public function is_temporary_user($user_id) {
        return (bool) get_user_meta($user_id, self::USER_META_TEMP_USER, true);
    }


    /**
     * Handle incoming temporary access requests.
     *
     * @since 1.6.0
     */
    public function handle_temp_access_request() {
        if (!isset($_GET[self::QUERY_VAR])) {
            return;
        }

        // Sanitize and validate token
        $token = sanitize_key(wp_unslash($_GET[self::QUERY_VAR]));
        if (empty($token)) {
            wp_safe_redirect(home_url());
            exit;
        }

        $token_hash = $this->hash_token($token);
        $users = $this->find_users_by_token_hash($token_hash);

        // Backwards compatibility: migrate existing plaintext-token rows on use.
        if (empty($users)) {
            $users = $this->find_users_by_legacy_token($token);
            if (!empty($users)) {
                update_user_meta($users[0]->ID, self::USER_META_TOKEN_HASH, $token_hash);
                delete_user_meta($users[0]->ID, self::USER_META_TOKEN);
            }
        }

        if (empty($users)) {
            wp_safe_redirect(home_url());
            exit;
        }

        $user = $users[0];
        $user_id = $user->ID;

        // Check if user is expired
        $expiry = get_user_meta($user_id, self::USER_META_EXPIRY, true);
        if (time() > $expiry) {
            // Delete expired user
            if (function_exists('wp_delete_user')) {
                wp_delete_user($user_id);
            }
            wp_safe_redirect(home_url());
            exit;
        }

        // Check login limit
        $login_count = get_user_meta($user_id, self::USER_META_LOGIN_COUNT, true);
        $max_logins = get_user_meta($user_id, self::USER_META_MAX_LOGINS, true);

        if ($max_logins > 0 && $login_count >= $max_logins) {
            // Delete user when max uses reached
            if (function_exists('wp_delete_user')) {
                wp_delete_user($user_id);
            }
            wp_safe_redirect(home_url());
            exit;
        }

        // Log out current user if different
        if (is_user_logged_in() && get_current_user_id() !== $user_id) {
            wp_logout();
        }

        // Update login count and last login
        update_user_meta($user_id, self::USER_META_LOGIN_COUNT, $login_count + 1);
        update_user_meta($user_id, self::USER_META_LAST_LOGIN, time());

        // Update audit table for better auditability
        if (function_exists('Guard_Dog') && Guard_Dog()->db) {
            Guard_Dog()->db->update_temp_access_login($user->user_login, $login_count + 1);
        }

        // Log the event
        if ($this->activity_log) {
            $this->activity_log->add_log_entry('temp_access_used', [
                'user_id' => $user_id,
                'username' => $user->user_login,
                'details' => [
                    'token_hash_prefix' => substr($token_hash, 0, 12) . '...',
                ]
            ]);
        }

        // Log the user in
        wp_set_current_user($user_id, $user->user_login);
        wp_set_auth_cookie($user_id);
        // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
        // wp_login is WordPress core's standard login hook that we're firing for compatibility.
        // This is not a custom hook that needs prefixing - it's WordPress core's hook that
        // other plugins and themes may be listening to. Firing this hook ensures proper
        // integration with WordPress's login system and third-party plugins.
        do_action('wp_login', $user->user_login, $user);
        // phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

        // Determine redirect URL
        $redirect_to = get_user_meta($user_id, self::USER_META_REDIRECT, true);
        switch ($redirect_to) {
            case 'front':
                $redirect_url = home_url();
                break;
            case 'profile':
                $redirect_url = admin_url('profile.php');
                break;
            default:
                $redirect_url = admin_url();
        }

        wp_safe_redirect($redirect_url);
        exit;
    }

    //--------------------------------------------------------------------------
    // Security and Restriction Methods
    //--------------------------------------------------------------------------

    /**
     * Prevent temporary users from logging in with username/password.
     *
     * @since 1.6.0
     * @param WP_User|WP_Error $user User object or error
     * @param string $password Password
     * @return WP_User|WP_Error Modified user object or error
     */
    public function prevent_temp_user_normal_login($user, $password) {
        if ($user instanceof WP_User && $this->is_temporary_user($user->ID)) {
            return new WP_Error('temp_user_login_disabled', __('This account can only be accessed via the temporary access link.', 'guard-dog'));
        }
        return $user;
    }

    /**
     * Prevent password reset for temporary users.
     *
     * @since 1.6.0
     * @param bool $allow Whether to allow password reset
     * @param int $user_id User ID
     * @return bool Modified allow status
     */
    public function prevent_temp_user_password_reset($allow, $user_id) {
        if ($this->is_temporary_user($user_id)) {
            return false;
        }
        return $allow;
    }

    /**
     * Show admin bar notice for temporary users.
     *
     * @since 1.6.0
     * @param WP_Admin_Bar $wp_admin_bar Admin bar object
     */
    public function show_temp_access_admin_bar_notice($wp_admin_bar) {
        if (!is_user_logged_in() || !$this->is_temporary_user(get_current_user_id())) {
            return;
        }

        $expiry = get_user_meta(get_current_user_id(), self::USER_META_EXPIRY, true);
        $time_left = human_time_diff(time(), $expiry);

        $wp_admin_bar->add_node([
            'id' => 'guard-dog-temp-access-notice',
            'title' => sprintf(
                // Translators: %s is the time left for the temporary access link.
                __('Temporary Access - %s remaining', 'guard-dog'),
                $time_left
            ),
            'meta' => [
                'class' => 'guard-dog-temp-notice',
            ]
        ]);
    }

    /**
     * Restrict access to certain admin pages for temporary users.
     *
     * @since 1.6.0
     */
    public function restrict_temp_user_access() {
        if (!is_user_logged_in() || !$this->is_temporary_user(get_current_user_id())) {
            return;
        }

        $restricted_pages = [
            'users.php',
            'tools.php',
            'import.php',
            'export.php',
            'options-general.php'
        ];

        $current_screen = get_current_screen();
        if ($current_screen && in_array($current_screen->parent_file, $restricted_pages)) {
            wp_die(esc_html__('Access to this page is restricted for temporary users.', 'guard-dog'));
        }
    }

    /**
     * Clean up expired temporary users.
     *
     * @since 1.6.0
     */
    public function cleanup_expired_users() {
        // Only run cleanup occasionally to avoid performance impact
        if (wp_cache_get('guard_dog_temp_cleanup_done', 'guard_dog')) {
            return;
        }

        // Ensure user functions are available
        if (!function_exists('wp_delete_user') || !function_exists('get_users')) {
            return;
        }

        // Additional safety check - ensure we're in a proper WordPress context
        if (!defined('ABSPATH') || !function_exists('get_option')) {
            return;
        }

        // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
        // This meta_query is necessary to find expired temporary users for cleanup. The performance
        // impact is acceptable as cleanup runs infrequently (cached for 1 hour) and temporary users
        // are typically few in number.
        $expired_users = get_users([
            'meta_query' => [
                [
                    'key' => self::USER_META_TEMP_USER,
                    'value' => true,
                    'compare' => '='
                ],
                [
                    'key' => self::USER_META_EXPIRY,
                    'value' => time(),
                    'compare' => '<',
                    'type' => 'NUMERIC'
                ]
            ]
        ]);
        // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_query

        foreach ($expired_users as $user) {
            try {
                // Get username before deletion for audit table cleanup
                $username = $user->user_login;
                
                $result = wp_delete_user($user->ID);
                
                // Clean up audit table entry
                if ($result && $username && function_exists('Guard_Dog') && Guard_Dog()->db) {
                    Guard_Dog()->db->delete_temp_access_user($username);
                }
            } catch (Exception $e) {
                // Log error but continue with other users
                if (defined('WP_DEBUG') && WP_DEBUG) {
                                    // Use Guard Dog debug system instead of error_log
	                if (class_exists('Guard_Dog_Debug')) {
	                    Guard_Dog_Debug::instance()->error('Failed to delete expired temporary user ' . $user->ID . ': ' . $e->getMessage());
	                }
                }
            }
        }

        // Cache cleanup for 1 hour
        wp_cache_set('guard_dog_temp_cleanup_done', true, 'guard_dog', HOUR_IN_SECONDS);
    }

    //--------------------------------------------------------------------------
    // Helper Methods
    //--------------------------------------------------------------------------

    /**
     * Validate user data for temporary access creation.
     *
     * @since 1.6.0
     * @param array $data User data
     * @return true|WP_Error True if valid, WP_Error if invalid
     */
    private function validate_user_data($data) {
        if (empty($data['username'])) {
            return new WP_Error('missing_username', __('Username is required.', 'guard-dog'));
        }

        if (empty($data['email']) || !is_email($data['email'])) {
            return new WP_Error('invalid_email', __('Please provide a valid email address.', 'guard-dog'));
        }

        $valid_roles = ['subscriber', 'contributor', 'author', 'editor', 'administrator'];
        if (!in_array($data['role'] ?? '', $valid_roles)) {
            return new WP_Error('invalid_role', __('Invalid user role specified.', 'guard-dog'));
        }

        if (empty($data['expiry']) || !is_numeric($data['expiry']) || $data['expiry'] < 1 || $data['expiry'] > 365) {
            return new WP_Error('invalid_expiry', __('Expiry must be between 1 and 365 days.', 'guard-dog'));
        }

        if (empty($data['max_logins']) || !is_numeric($data['max_logins']) || $data['max_logins'] < 1 || $data['max_logins'] > 1000) {
            return new WP_Error('invalid_max_logins', __('Max logins must be between 1 and 1000.', 'guard-dog'));
        }

        return true;
    }



    /**
     * Generate secure token for user.
     *
     * @since 1.6.0
     * @param int $user_id User ID
     * @return string Generated token
     */
    private function generate_token($user_id) {
        try {
            return bin2hex(random_bytes(32));
        } catch (Exception $e) {
            return wp_generate_password(64, false, false);
        }
    }

    /**
     * Hash a temporary access token for storage and lookup.
     *
     * @since 1.9.41
     * @param string $token Raw bearer token.
     * @return string Keyed token hash.
     */
    private function hash_token($token) {
        return hash_hmac('sha256', (string) $token, wp_salt('auth'));
    }

    /**
     * Find temporary users by hashed token.
     *
     * @since 1.9.41
     * @param string $token_hash Hashed token.
     * @return array Matching users.
     */
    private function find_users_by_token_hash($token_hash) {
        // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
        $users = get_users([
            'meta_query' => [
                [
                    'key' => self::USER_META_TOKEN_HASH,
                    'value' => $token_hash,
                    'compare' => '='
                ]
            ],
            'number' => 1
        ]);
        // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_query

        return $users;
    }

    /**
     * Find temporary users by legacy plaintext token.
     *
     * @since 1.9.41
     * @param string $token Raw bearer token.
     * @return array Matching users.
     */
    private function find_users_by_legacy_token($token) {
        // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
        $users = get_users([
            'meta_query' => [
                [
                    'key' => self::USER_META_TOKEN,
                    'value' => $token,
                    'compare' => '='
                ]
            ],
            'number' => 1
        ]);
        // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_query

        return $users;
    }

} // End class Guard_Dog_Temp_Access
