<?php
/**
 * Live Traffic Logger - Singleton logger managing request lifecycle
 *
 * @package VMPFence
 * @subpackage Tools
 * @since 2.1.0
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Class VMPFence_Live_Traffic_Logger
 *
 * Singleton logger that manages HTTP request logging lifecycle
 *
 * @since 2.1.0
 */
class VMPFence_Live_Traffic_Logger {

    /**
     * Singleton instance
     * @var VMPFence_Live_Traffic_Logger|null
     */
    private static $instance = null;

    /**
     * Current request model
     * @var VMPFence_Request_Model|null
     */
    private $request = null;

    /**
     * Whether logging has been initialized
     * @var bool
     */
    private $initialized = false;

    /**
     * Private constructor for singleton
     *
     * @since 2.1.0
     */
    private function __construct() {
        // Private constructor
    }

    /**
     * Get singleton instance
     *
     * @since 2.1.0
     * @return VMPFence_Live_Traffic_Logger
     */
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Initialize request logging
     *
     * Should be called early in WordPress lifecycle (init or earlier)
     *
     * @since 2.1.0
     */
    public function initLogRequest() {
        if ($this->initialized) {
            return; // Already initialized
        }

        // Create new request model
        $this->request = new VMPFence_Request_Model();

        // Set basic request data
        $this->request->setCtime(microtime(true));
        $this->request->setAttackLogTime(microtime(true));

        // Get IP address
        $ip = VMPFence_Utils::get_real_ip();
        if (!empty($ip)) {
            $this->request->setIP($ip);
        }

        // Set URL
        if (isset($_SERVER['REQUEST_URI'])) {
            $this->request->setURL($_SERVER['REQUEST_URI']);
        }

        // Set referer
        if (isset($_SERVER['HTTP_REFERER'])) {
            $this->request->setReferer($_SERVER['HTTP_REFERER']);
        }

        // Set user agent
        if (isset($_SERVER['HTTP_USER_AGENT'])) {
            $this->request->setUA($_SERVER['HTTP_USER_AGENT']);
        }

        // Set default values
        $this->request->setStatusCode(200);
        $this->request->setJsRun(0);
        $this->request->setIsGoogle(0);
        $this->request->setUserID(get_current_user_id());

        // Register shutdown hook to save on request end
        register_shutdown_function(array($this, 'logHit'));

        // Hook into wp_loaded for jsRun detection
        add_action('wp_loaded', array($this, 'detectJsRun'), 9999);

        // Hook into init (late) for final user/IP updates and human enhancement
        add_action('init', array($this, 'updateLateData'), 9999);
        add_action('init', array($this, 'enhanceHumanDetection'), 9999);

        $this->initialized = true;
    }

    /**
     * Detect JavaScript run and classify visitor
     *
     * Called on wp_loaded hook
     *
     * @since 2.1.0
     */
    public function detectJsRun() {
        if (!$this->request) {
            return;
        }

        // Get user agent and IP
        $ua = $this->request->getUA();
        $ip = $this->request->getIPString();

        // Use existing visitor classifier
        if (class_exists('VMPFence_Visitor_Classifier')) {
            // Classify visitor (human or crawler)
            $classification = VMPFence_Visitor_Classifier::classify_visitor($ua, $ip);
            
            if ($classification === 'human') {
                $this->request->setJsRun(1);
            } else {
                $this->request->setJsRun(0);
            }

            // Check if verified Google crawler
            if (VMPFence_Visitor_Classifier::is_verified_google($ip, $ua)) {
                $this->request->setIsGoogle(1);
            }
        }
    }

    /**
     * Enhance human detection for logged-in users
     *
     * Called on init hook (late priority) to override crawler classification
     * for authenticated users
     *
     * @since 2.1.0
     */
    public function enhanceHumanDetection() {
        if (!$this->request) {
            return;
        }

        // If user is logged in, they are definitely human
        $user_id = get_current_user_id();
        if ($user_id > 0) {
            // Override jsRun to mark as human
            $this->request->setJsRun(1);

            // Update cache to reflect this visitor as human
            if (class_exists('VMPFence_Visitor_Classifier')) {
                $ip = $this->request->getIPString();
                $ua = $this->request->getUA();
                
                // Cache classification as human for 1 hour
                VMPFence_Visitor_Classifier::cache_classification($ip, 'human', 3600);
            }
        }
    }

    /**
     * Update late data (user ID, IP)
     *
     * Called on init hook (late priority)
     *
     * @since 2.1.0
     */
    public function updateLateData() {
        if (!$this->request) {
            return;
        }

        // Update user ID in case it changed after authentication
        $this->request->setUserID(get_current_user_id());
    }

    /**
     * Log hit to database
     *
     * Called via shutdown hook
     *
     * @since 2.1.0
     * @return int|false Record ID on success, false on failure
     */
    public function logHit() {
        if (!$this->request) {
            return false;
        }

        // Check if logging is allowed
        if (!$this->logHitOK()) {
            return false;
        }

        // Check if security-only mode and no action set
        $mode = get_option('vmpfence_traffic_logging_mode', 'security_only');
        if ($mode === 'security_only' && empty($this->request->getAction())) {
            return false;
        }

        // Save to database
        return $this->request->save();
    }

    /**
     * Check if hit should be logged
     *
     * Applies filter rules
     *
     * @since 2.1.0
     * @return bool
     */
    private function logHitOK() {
        // Don't log if no request initialized
        if (!$this->request) {
            return false;
        }

        // Check if admin requests should be ignored
        $ignore_admin = get_option('vmpfence_ignore_admin_traffic', 0);
        if ($ignore_admin && is_admin() && !wp_doing_ajax()) {
            return false;
        }

        // Ignore WordPress internal requests by URL
        $request_uri = $_SERVER['REQUEST_URI'] ?? '';
        if (!empty($request_uri)) {
            // Skip wp-cron.php requests
            if (strpos($request_uri, 'wp-cron.php') !== false) {
                return false;
            }
            
            // Skip admin-ajax.php for background tasks
            if (strpos($request_uri, 'admin-ajax.php') !== false) {
                return false;
            }
            
            // Skip xmlrpc.php requests
            if (strpos($request_uri, 'xmlrpc.php') !== false) {
                return false;
            }
        }

        // Check if publishing users should be ignored
        $ignore_publishers = get_option('vmpfence_ignore_publishing_users', 0);
        if ($ignore_publishers) {
            $user_id = get_current_user_id();
            if ($user_id > 0) {
                $user = get_userdata($user_id);
                if ($user && ($user->has_cap('edit_posts') || $user->has_cap('publish_posts'))) {
                    return false;
                }
            }
        }

        // Check ignored usernames
        $ignored_usernames = get_option('vmpfence_ignore_usernames', '');
        if (!empty($ignored_usernames)) {
            $user_id = get_current_user_id();
            if ($user_id > 0) {
                $user = get_userdata($user_id);
                if ($user) {
                    $ignored_array = array_map('trim', explode(',', $ignored_usernames));
                    if (in_array($user->user_login, $ignored_array)) {
                        return false;
                    }
                }
            }
        }

        // Check ignored IPs
        $ignored_ips = get_option('vmpfence_ignore_ips', '');
        if (!empty($ignored_ips)) {
            $current_ip = $this->request->getIPString();
            $ignored_array = array_map('trim', explode(',', $ignored_ips));
            if (in_array($current_ip, $ignored_array)) {
                return false;
            }
        }

        // Check ignored user agent patterns
        $ignored_ua = get_option('vmpfence_ignore_user_agent', '');
        if (!empty($ignored_ua) && !empty($ua)) {
            if (stripos($ua, $ignored_ua) !== false) {
                return false;
            }
        }

        return true;
    }

    /**
     * Set action for current request
     *
     * @since 2.1.0
     * @param string $action Action type
     * @param string $description Action description
     * @param mixed $data Action data (will be JSON encoded)
     */
    public function setAction($action, $description = '', $data = null) {
        if ($this->request) {
            $this->request->setAction($action, $description, $data);
        }
    }

    /**
     * Set status code for current request
     *
     * @since 2.1.0
     * @param int $code HTTP status code
     */
    public function setStatusCode($code) {
        if ($this->request) {
            $this->request->setStatusCode($code);
        }
    }

    /**
     * Set jsRun flag for current request
     *
     * @since 2.1.0
     * @param int $jsRun 1=human, 0=bot
     */
    public function setJsRun($jsRun) {
        if ($this->request) {
            $this->request->setJsRun($jsRun);
        }
    }

    /**
     * Get current request model
     *
     * @since 2.1.0
     * @return VMPFence_Request_Model|null
     */
    public function getRequestModel() {
        return $this->request;
    }

    /**
     * Update request data
     *
     * @since 2.1.0
     * @param array $data Associative array of field => value
     */
    public function updateRequestData($data) {
        if ($this->request) {
            $this->request->updateRequestData($data);
        }
    }

    /**
     * Log security event
     *
     * Convenience method for logging security events
     *
     * @since 2.1.0
     * @param string $action Action type
     * @param string $description Action description
     * @param mixed $data Action data
     */
    public function logSecurityEvent($action, $description = '', $data = null) {
        $this->setAction($action, $description, $data);
    }
}
