<?php
/**
 * Aysa Authentication Manager
 * Handles 30-day secret key persistence per WordPress user
 * 
 * @package Aysa
 * @since 1.0.0
 */

if (!defined('ABSPATH')) {
    exit;
}

class Aysa_Auth_Manager {
    
    /**
     * Database table name for user authentication
     */
    const TABLE_NAME = 'aysa_user_auth';
    
    /**
     * Authentication duration in seconds (30 days)
     */
    const AUTH_DURATION = 30 * 24 * 60 * 60; // 30 days
    
    /**
     * Singleton instance
     */
    private static $instance = null;

    /**
     * Local-only key must be injected explicitly in local/staging config.
     * No embedded fallback should ship inside the plugin.
     */
    const LOCAL_DEV_MASTER_KEY = '';
    
    /**
     * Get singleton instance
     */
    public static function get_instance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    /**
     * Constructor
     */
    private function __construct() {
        // Debug: Log constructor call
        aysa_debug_log("[AYSA AUTH] Auth Manager constructor called");
        
        // Create database table on activation
        register_activation_hook(AYSA_PLUGIN_FILE, array($this, 'create_auth_table'));
        
        // Schedule cleanup cron
        add_action('aysa_cleanup_expired_auth', array($this, 'cleanup_expired_auth'));
        if (!wp_next_scheduled('aysa_cleanup_expired_auth')) {
            wp_schedule_event(time(), 'daily', 'aysa_cleanup_expired_auth');
        }
        
        // Add AJAX handlers
        add_action('wp_ajax_aysa_verify_secret_key', array($this, 'ajax_verify_secret_key'));
        add_action('wp_ajax_aysa_get_auth_status', array($this, 'ajax_get_auth_status'));
        add_action('wp_ajax_aysa_get_secret_key', array($this, 'ajax_get_secret_key'));
        add_action('wp_ajax_aysa_logout', array($this, 'ajax_logout'));
        add_action('wp_ajax_aysa_get_auth_stats', array($this, 'ajax_get_auth_stats'));
        
        // Add custom capability mapping for Aysa dashboard access
        add_filter('map_meta_cap', array($this, 'map_custom_capability'), 10, 4);
    }
    
    /**
     * Create authentication table
     */
    public function create_auth_table() {
        global $wpdb;
        
        $table_name = esc_sql($wpdb->prefix . self::TABLE_NAME);
        
        $charset_collate = $wpdb->get_charset_collate();
        
        // Fix charset issue for certain hosting environments
        $charset_collate = str_replace('UTF-8', 'utf8mb4', $charset_collate);
        
        // Fallback if charset is still problematic
        if (empty($charset_collate) || strpos($charset_collate, 'UTF-8') !== false) {
            $charset_collate = 'DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci';
        }
        
        aysa_debug_log("[AYSA AUTH] Using charset_collate: " . $charset_collate);
        
        $sql = "CREATE TABLE $table_name (
            id mediumint(9) NOT NULL AUTO_INCREMENT,
            wp_user_id bigint(20) NOT NULL,
            domain varchar(255) NOT NULL,
            secret_key varchar(500) NOT NULL,
            aysa_user_id bigint(20) NOT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            expires_at datetime NOT NULL,
            last_used datetime DEFAULT CURRENT_TIMESTAMP,
            ip_address varchar(45) DEFAULT NULL,
            user_agent text DEFAULT NULL,
            PRIMARY KEY (id),
            UNIQUE KEY wp_user_domain (wp_user_id, domain),
            KEY expires_at (expires_at),
            KEY last_used (last_used),
            KEY wp_user_id (wp_user_id)
        ) $charset_collate;";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        
        aysa_debug_log("[AYSA AUTH] Creating table with SQL: " . $sql);
        $result = dbDelta($sql);
        aysa_debug_log("[AYSA AUTH] dbDelta result: " . aysa_debug_export($result, true));
        
        // Set database version
        update_option('aysa_auth_db_version', '2.0');
        
        // Verify table was created
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table_name)) === $table_name;
        aysa_debug_log("[AYSA AUTH] Table exists after creation: " . ($table_exists ? 'YES' : 'NO'));
        
        // Migrate old data if exists
        $this->migrate_old_auth_data();
    }
    
    /**
     * Migrate old domain-based auth data to user-based
     */
    private function migrate_old_auth_data() {
        global $wpdb;
        
        $old_table = esc_sql($wpdb->prefix . 'aysa_domain_auth');
        $new_table = esc_sql($wpdb->prefix . self::TABLE_NAME);
        
        // Check if old table exists
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $old_table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $old_table)) === $old_table;
        
        if ($old_table_exists) {
            // Get current WordPress user ID (if in admin context)
            $current_user_id = get_current_user_id();
            
            if ($current_user_id > 0) {
                // Migrate data for current user
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Internal legacy table name is derived from the WordPress prefix and a fixed AYSA suffix; values are not user-controlled. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                $old_data = $wpdb->get_results("SELECT * FROM $old_table WHERE expires_at > NOW()");
                
                foreach ($old_data as $record) {
                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
                    $wpdb->replace(
                        $new_table,
                        array(
                            'wp_user_id' => $current_user_id,
                            'domain' => $record->domain,
                            'secret_key' => $record->secret_key,
                            'aysa_user_id' => $record->user_id,
                            'created_at' => $record->created_at,
                            'expires_at' => $record->expires_at,
                            'last_used' => $record->last_used,
                            'ip_address' => $record->ip_address,
                            'user_agent' => $record->user_agent
                        ),
                        array('%d', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s')
                    );
                }
                
                aysa_debug_log("[AYSA AUTH] Migrated old auth data for user ID: $current_user_id");
            }
        }
    }
    
    /**
     * Store secret key for WordPress user with 30-day expiration
     */
    public function store_user_auth($secret_key, $aysa_user_id, $wp_user_id = null) {
        global $wpdb;
        
        if (!$wp_user_id) {
            $wp_user_id = get_current_user_id();
        }
        
        if (!$wp_user_id) {
            aysa_debug_log("[AYSA AUTH] No WordPress user ID available for authentication");
            return false;
        }
        
        $table_name = esc_sql($wpdb->prefix . self::TABLE_NAME);
        $current_time = current_time('mysql');
        $expires_at = gmdate('Y-m-d H:i:s', time() + self::AUTH_DURATION);
        $domain = $this->get_current_domain();
        $ip_address = $this->get_client_ip();
        $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
        
        // Check if user-domain combination already exists
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $existing = $wpdb->get_row($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Internal auth table name is derived from the WordPress prefix and a fixed AYSA suffix.
            "SELECT id FROM $table_name WHERE wp_user_id = %d AND domain = %s",
            $wp_user_id,
            $domain
        ));
        
        if ($existing) {
            // Update existing record
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $result = $wpdb->update(
                $table_name,
                array(
                    'secret_key' => $secret_key,
                    'aysa_user_id' => $aysa_user_id,
                    'expires_at' => $expires_at,
                    'last_used' => $current_time,
                    'ip_address' => $ip_address,
                    'user_agent' => $user_agent
                ),
                array(
                    'wp_user_id' => $wp_user_id,
                    'domain' => $domain
                ),
                array('%s', '%d', '%s', '%s', '%s', '%s'),
                array('%d', '%s')
            );
        } else {
            // Insert new record
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $result = $wpdb->insert(
                $table_name,
                array(
                    'wp_user_id' => $wp_user_id,
                    'domain' => $domain,
                    'secret_key' => $secret_key,
                    'aysa_user_id' => $aysa_user_id,
                    'created_at' => $current_time,
                    'expires_at' => $expires_at,
                    'last_used' => $current_time,
                    'ip_address' => $ip_address,
                    'user_agent' => $user_agent
                ),
                array('%d', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s')
            );
        }
        
        if ($result !== false) {
            aysa_debug_log("[AYSA AUTH] User authentication stored for WP User ID: $wp_user_id, Domain: $domain, Aysa User ID: $aysa_user_id");
            return true;
        }
        
        aysa_debug_log("[AYSA AUTH] Failed to store user authentication for WP User ID: $wp_user_id, Domain: $domain");
        return false;
    }
    
    /**
     * Get authentication data for current WordPress user
     */
    public function get_user_auth($wp_user_id = null) {
        global $wpdb;
        
        if (!$wp_user_id) {
            $wp_user_id = get_current_user_id();
        }
        
        if (!$wp_user_id) {
            return null;
        }
        
        $table_name = esc_sql($wpdb->prefix . self::TABLE_NAME);
        $domain = $this->get_current_domain();
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $auth_data = $wpdb->get_row($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Internal auth table name is derived from the WordPress prefix and a fixed AYSA suffix.
            "SELECT * FROM $table_name WHERE wp_user_id = %d AND domain = %s AND expires_at > NOW()",
            $wp_user_id,
            $domain
        ));
        
        if ($auth_data) {
            // Update last used timestamp
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            $wpdb->update(
                $table_name,
                array('last_used' => current_time('mysql')),
                array('id' => $auth_data->id),
                array('%s'),
                array('%d')
            );
            
            return $auth_data;
        }

        $shared_auth = $this->get_domain_shared_auth($domain, $wp_user_id);
        if ($shared_auth) {
            $promoted = $this->promote_domain_auth_to_user($shared_auth, $wp_user_id, $domain);
            if ($promoted) {
                return $promoted;
            }
            return $shared_auth;
        }
        
        return null;
    }
    
    /**
     * Check if current WordPress user is authenticated
     */
    public function is_authenticated($wp_user_id = null) {
        if (!$wp_user_id) {
            $wp_user_id = get_current_user_id();
        }
        
        if (!$wp_user_id) {
            return array(
                'authenticated' => false,
                'source' => 'no_wp_user'
            );
        }
        
        $auth_data = $this->get_user_auth($wp_user_id);
        
        if ($auth_data) {
            return array(
                'authenticated' => true,
                'wp_user_id' => $auth_data->wp_user_id,
                'aysa_user_id' => $auth_data->aysa_user_id,
                'secret_key' => $auth_data->secret_key,
                'domain' => $auth_data->domain,
                'expires_at' => $auth_data->expires_at,
                'source' => 'database'
            );
        }
        
        return array(
            'authenticated' => false,
            'wp_user_id' => $wp_user_id,
            'source' => 'not_found'
        );
    }
    
    /**
     * Verify secret key with external API and store if valid
     */
    public function verify_and_store_secret_key($secret_key, $domain = null) {
        aysa_debug_log("[AYSA AUTH] Starting secret key verification");
        
        $wp_user_id = get_current_user_id();
        
        if (!$wp_user_id) {
            aysa_debug_log("[AYSA AUTH] No WordPress user logged in for verification");
            return false;
        }
        
        if (!$domain) {
            $domain = $this->get_current_domain();
        }

        aysa_debug_log("[AYSA AUTH] Verifying for WP User ID: $wp_user_id, Domain: $domain");
        
        // Include API configuration
        if (!class_exists('Aysa_API_Config')) {
            require_once AYSA_PLUGIN_DIR . 'includes/config/aysa-api-config.php';
        }
        
        // Call external API to verify secret key
        $api_url = Aysa_API_Config::get_endpoint_url('user_verify');
        
        if (!$api_url) {
            aysa_debug_log("[AYSA AUTH] Failed to get API endpoint URL");
            return false;
        }
        
        aysa_debug_log("[AYSA AUTH] API URL: $api_url");
        
        $request_data = array(
            'secretKey' => $secret_key,
            'domainFromWordpress' => $domain
        );
        $safe_request_data = $request_data;
        $safe_request_data['secretKey'] = '[REDACTED_KEY]';
        
        aysa_debug_log("[AYSA AUTH] Request data: " . json_encode($safe_request_data));
        
        // Log the full request being made
        aysa_debug_log("[AYSA AUTH] Making POST request to: $api_url");
        aysa_debug_log("[AYSA AUTH] With body: " . json_encode($safe_request_data));
        
        // Prepare request arguments
        $request_args = array(
            'timeout' => 30,
            'headers' => array(
                'Content-Type' => 'application/json',
            ),
            'body' => json_encode($request_data)
        );
        
        $response = wp_remote_post($api_url, $request_args);
        
        // Log the raw response for debugging
        if (is_wp_error($response)) {
            $error_msg = $response->get_error_message();
            aysa_debug_log("[AYSA AUTH] WP_Error: " . $error_msg);
            return array('error' => 'Connection failed: ' . $error_msg, 'success' => false);
        } else {
            aysa_debug_log("[AYSA AUTH] Raw response headers: " . json_encode(wp_remote_retrieve_headers($response)));
        }
        
        $response_code = wp_remote_retrieve_response_code($response);
        $response_body = wp_remote_retrieve_body($response);
        $safe_response_body = preg_replace('/aysa_[a-z]{2}_[A-Za-z0-9_-]+/', '[REDACTED_KEY]', (string) $response_body);
        
        aysa_debug_log("[AYSA AUTH] API Response Code: $response_code");
        aysa_debug_log("[AYSA AUTH] API Response Body: $safe_response_body");
        
        if ($response_code === 200) {
            $data = json_decode($response_body, true);
            $safe_data = json_decode($safe_response_body, true);
            aysa_debug_log("[AYSA AUTH] Decoded response data: " . json_encode($safe_data));
            
            if (isset($data['userId']) && $data['userId'] > 0) {
                // Store authentication data
                if ($this->store_user_auth($secret_key, $data['userId'], $wp_user_id)) {
                    aysa_debug_log("[AYSA AUTH] Authentication stored successfully");
                    return array(
                        'success' => true,
                        'wp_user_id' => $wp_user_id,
                        'aysa_user_id' => $data['userId'],
                        'message' => 'Secret key verified and stored successfully'
                    );
                } else {
                    aysa_debug_log("[AYSA AUTH] Failed to store authentication data");
                    return array('error' => 'Failed to store authentication data', 'success' => false);
                }
            } else {
                aysa_debug_log("[AYSA AUTH] Invalid or missing userId in response");
                return array('error' => 'Invalid response from API - no user ID returned', 'success' => false);
            }
        }
        
        // Handle specific error codes
        $error_message = 'Authentication failed';
        switch ($response_code) {
            case 401:
                $error_message = 'Invalid secret key provided';
                break;
            case 403:
                $error_message = 'Access denied - secret key not authorized for this domain';
                break;
            case 404:
                $error_message = 'API service not found - please contact support';
                break;
            case 500:
                $error_message = 'Server error - please try again later';
                break;
            case 0:
                $error_message = 'Unable to connect to authentication service - please check your internet connection';
                break;
            default:
                $error_message = "API error (HTTP $response_code) - please contact support";
        }
        
        aysa_debug_log("[AYSA AUTH] API verification failed. Response code: $response_code, Body: $safe_response_body");
        return array('error' => $error_message, 'success' => false);
    }

    /**
     * Logout current WordPress user
     */
    public function logout($wp_user_id = null) {
        if (!$wp_user_id) {
            $wp_user_id = get_current_user_id();
        }
        
        if (!$wp_user_id) {
            return false;
        }
        
        global $wpdb;
        $table_name = esc_sql($wpdb->prefix . self::TABLE_NAME);
        $domain = $this->get_current_domain();
        
        // Remove from database
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $result = $wpdb->delete(
            $table_name,
            array(
                'wp_user_id' => $wp_user_id,
                'domain' => $domain
            ),
            array('%d', '%s')
        );
        
        aysa_debug_log("[AYSA AUTH] User logged out - WP User ID: $wp_user_id, Domain: $domain");
        return $result !== false;
    }
    
    /**
     * Get current domain
     */
    private function get_current_domain() {
        $domain = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '';
        
        // Remove www. prefix for consistency
        if (strpos($domain, 'www.') === 0) {
            $domain = substr($domain, 4);
        }
        
        return $domain;
    }

    /**
     * Return any valid AYSA auth for the current site, regardless of WP user.
     */
    private function get_domain_shared_auth($domain, $exclude_wp_user_id = 0) {
        global $wpdb;

        $table_name = esc_sql($wpdb->prefix . self::TABLE_NAME);

        if ($exclude_wp_user_id > 0) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            return $wpdb->get_row($wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Internal auth table name is derived from the WordPress prefix and a fixed AYSA suffix.
                "SELECT * FROM $table_name WHERE domain = %s AND expires_at > NOW() AND wp_user_id <> %d ORDER BY last_used DESC, id DESC LIMIT 1",
                $domain,
                $exclude_wp_user_id
            ));
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        return $wpdb->get_row($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Internal auth table name is derived from the WordPress prefix and a fixed AYSA suffix.
            "SELECT * FROM $table_name WHERE domain = %s AND expires_at > NOW() ORDER BY last_used DESC, id DESC LIMIT 1",
            $domain
        ));
    }

    /**
     * Promote a valid site auth session to the current WordPress user.
     */
    private function promote_domain_auth_to_user($auth_data, $wp_user_id, $domain) {
        if (!$auth_data || !$wp_user_id) {
            return null;
        }

        $stored = $this->store_user_auth($auth_data->secret_key, (int) $auth_data->aysa_user_id, (int) $wp_user_id);
        if (!$stored) {
            aysa_debug_log("[AYSA AUTH] Failed to promote shared auth for WP User ID: $wp_user_id, Domain: $domain");
            return null;
        }

        aysa_debug_log("[AYSA AUTH] Promoted shared auth to WP User ID: $wp_user_id, Domain: $domain, Source WP User ID: {$auth_data->wp_user_id}");

        global $wpdb;
        $table_name = esc_sql($wpdb->prefix . self::TABLE_NAME);

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        return $wpdb->get_row($wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Internal auth table name is derived from the WordPress prefix and a fixed AYSA suffix.
            "SELECT * FROM $table_name WHERE wp_user_id = %d AND domain = %s AND expires_at > NOW()",
            $wp_user_id,
            $domain
        ));
    }

    /**
     * Get local development master key.
     */
    private function get_local_dev_master_key() {
        if (defined('AYSA_LOCAL_DEV_MASTER_KEY') && AYSA_LOCAL_DEV_MASTER_KEY) {
            return (string) AYSA_LOCAL_DEV_MASTER_KEY;
        }
        $stored = get_option('aysa_local_dev_master_key', '');
        if (is_string($stored) && trim($stored) !== '') {
            return trim($stored);
        }
        return self::LOCAL_DEV_MASTER_KEY;
    }
    
    /**
     * Get client IP address
     */
    private function get_client_ip() {
        $ip_keys = array('HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR');
        
        foreach ($ip_keys as $key) {
            if (array_key_exists($key, $_SERVER) && !empty($_SERVER[$key])) {
                $ip = sanitize_text_field(wp_unslash($_SERVER[$key]));
                if (strpos($ip, ',') !== false) {
                    $ip = explode(',', $ip)[0];
                }
                $ip = trim($ip);
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }
        
        return isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '';
    }
    
    /**
     * Cleanup expired authentication records
     */
    public function cleanup_expired_auth() {
        global $wpdb;
        
        $table_name = esc_sql($wpdb->prefix . self::TABLE_NAME);
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $deleted = $wpdb->query(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Internal auth table name is derived from the WordPress prefix and a fixed AYSA suffix.
            "DELETE FROM $table_name WHERE expires_at < NOW()"
        );
        
        if ($deleted > 0) {
            aysa_debug_log("[AYSA AUTH] Cleaned up $deleted expired authentication records");
        }
    }
    
    /**
     * AJAX handler for secret key verification
     */
    public function ajax_verify_secret_key() {
        // Start output buffering to prevent any accidental output
        ob_start();
        
        aysa_debug_log("[AYSA AUTH] AJAX verify secret key started");
        
        // Check if database table exists first
        global $wpdb;
        $table_name = esc_sql($wpdb->prefix . self::TABLE_NAME);
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table_name)) === $table_name;
        if (!$table_exists) {
            aysa_debug_log("[AYSA AUTH] Auth table does not exist, creating it...");
            $this->create_auth_table();
        }
        
        // Check if user is logged into WordPress
        if (!is_user_logged_in()) {
            aysa_debug_log("[AYSA AUTH] User not logged into WordPress");
            ob_clean();
            wp_send_json_error('You must be logged into WordPress to authenticate with Aysa');
            return;
        }
        
        $wp_user_id = get_current_user_id();
        aysa_debug_log("[AYSA AUTH] WordPress user ID: $wp_user_id");
        
        // Verify nonce for security.
        // Accept both nonce contexts used across AYSA admin/login flows.
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        $valid_nonce = !empty($nonce) && (
            wp_verify_nonce($nonce, 'aysa-ajax-nonce') ||
            wp_verify_nonce($nonce, 'aysa_nonce')
        );
        if (!$valid_nonce) {
            aysa_debug_log("[AYSA AUTH] Invalid nonce verification");
            ob_clean();
            wp_send_json_error('Invalid nonce');
            return;
        }

        if (!current_user_can('manage_options')) {
            ob_clean();
            wp_send_json_error(array('message' => 'Insufficient permissions'), 403);
            return;
        }

        $secret_key = isset($_POST['secret_key']) ? sanitize_text_field(wp_unslash($_POST['secret_key'])) : '';
        $domain = isset($_POST['domain']) ? sanitize_text_field(wp_unslash($_POST['domain'])) : '';
        
        aysa_debug_log("[AYSA AUTH] Received params - Secret key length: " . strlen($secret_key) . ", Domain: $domain");
        
        if (empty($secret_key) || empty($domain)) {
            aysa_debug_log("[AYSA AUTH] Missing required parameters");
            ob_clean();
            wp_send_json_error('Missing required parameters');
            return;
        }
        
        $result = $this->verify_and_store_secret_key($secret_key, $domain);
        
        aysa_debug_log("[AYSA AUTH] Verification result: " . json_encode($result));
        
        // Clear any output buffer before sending JSON
        ob_clean();
        
        if ($result && is_array($result) && $result['success']) {
            wp_send_json_success(array(
                'wp_user_id' => $result['wp_user_id'],
                'aysa_user_id' => $result['aysa_user_id'],
                'message' => $result['message']
            ));
        } else {
            // Get the last error from the logs to provide specific feedback
            $error_message = 'Authentication failed - please check your secret key and try again';
            
            // Check if we have a more specific error
            if (is_array($result) && isset($result['error'])) {
                $error_message = $result['error'];
            } else {
                // Try to get the last API error from logs
                $log_file = ini_get('error_log');
                if ($log_file && file_exists($log_file)) {
                    $recent_logs = $this->tail($log_file, 10);
                    foreach (array_reverse($recent_logs) as $line) {
                        if (strpos($line, '[AYSA AUTH]') !== false) {
                            if (strpos($line, 'API Response Code:') !== false && strpos($line, '200') === false) {
                                preg_match('/API Response Code: (\d+)/', $line, $matches);
                                if ($matches) {
                                    $code = $matches[1];
                                    switch ($code) {
                                        case '401':
                                            $error_message = 'Invalid secret key provided';
                                            break;
                                        case '403':
                                            $error_message = 'Access denied - secret key not authorized for this domain';
                                            break;
                                        case '404':
                                            $error_message = 'API endpoint not found - service may be temporarily unavailable';
                                            break;
                                        case '500':
                                            $error_message = 'Server error - please try again later';
                                            break;
                                        default:
                                            $error_message = "API error (HTTP $code) - please contact support";
                                    }
                                }
                                break;
                            } else if (strpos($line, 'WP_Error:') !== false) {
                                preg_match('/WP_Error: (.+)$/', $line, $matches);
                                if ($matches) {
                                    $error_message = 'Connection error: ' . trim($matches[1]);
                                }
                                break;
                            }
                        }
                    }
                }
            }
            
            wp_send_json_error($error_message);
        }
    }
    
    /**
     * AJAX handler to get authentication status
     */
    public function ajax_get_auth_status() {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        $valid_nonce = !empty($nonce) && (
            wp_verify_nonce($nonce, 'aysa_nonce') ||
            wp_verify_nonce($nonce, 'aysa-ajax-nonce')
        );
        if (!$valid_nonce) {
            wp_send_json_error(array('message' => 'Security check failed'));
            return;
        }

        if (!is_user_logged_in()) {
            wp_send_json_error(array('message' => 'Authentication required'));
            return;
        }

        $auth_status = $this->is_authenticated();
        wp_send_json_success($this->browser_safe_auth_status($auth_status));
    }
    
    /**
     * Legacy action name retained for compatibility. The secret never leaves
     * WordPress; this handler proxies only the two allowlisted Domain Authority
     * operations that previously required the browser to receive it.
     */
    public function ajax_get_secret_key() {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        $valid_nonce = !empty($nonce) && (
            wp_verify_nonce($nonce, 'aysa_nonce') ||
            wp_verify_nonce($nonce, 'aysa-ajax-nonce')
        );
        if (!$valid_nonce) {
            wp_send_json_error(array('message' => 'Security check failed'));
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions'), 403);
            return;
        }

        $auth_status = $this->is_authenticated();
        $secret_key = !empty($auth_status['authenticated']) ? trim((string) ($auth_status['secret_key'] ?? '')) : '';
        if ($secret_key === '') {
            wp_send_json_error(array(
                'message' => 'No valid authentication found',
                'source' => isset($auth_status['source']) ? $auth_status['source'] : 'unknown'
            ), 403);
            return;
        }

        $operation = isset($_POST['operation']) ? sanitize_key((string) wp_unslash($_POST['operation'])) : 'history';
        if (!in_array($operation, array('history', 'refresh'), true)) {
            wp_send_json_error(array('message' => 'Unsupported operation'), 400);
            return;
        }

        if (!class_exists('Aysa_API_Config')) {
            require_once AYSA_PLUGIN_DIR . 'includes/config/aysa-api-config.php';
        }

        if ($operation === 'history') {
            $url = Aysa_API_Config::get_endpoint_url_with_params(
                'project_domain_authority_by_secret',
                array('secret' => rawurlencode($secret_key))
            );
            $response = $url ? wp_remote_get($url, array('timeout' => 30)) : new WP_Error('endpoint_unavailable');
        } else {
            $url = Aysa_API_Config::get_endpoint_url('project_domain_authority');
            $response = $url ? wp_remote_post($url, array(
                'timeout' => 30,
                'headers' => array('Content-Type' => 'application/json'),
                'body' => wp_json_encode(array('secretKey' => $secret_key)),
            )) : new WP_Error('endpoint_unavailable');
        }

        if (is_wp_error($response)) {
            wp_send_json_error(array('message' => 'Domain Authority service is temporarily unavailable.'), 502);
            return;
        }

        $status = intval(wp_remote_retrieve_response_code($response));
        $decoded = json_decode((string) wp_remote_retrieve_body($response), true);
        if ($status < 200 || $status >= 300 || !is_array($decoded)) {
            wp_send_json_error(array('message' => 'Domain Authority service returned an invalid response.'), 502);
            return;
        }

        wp_send_json_success(array(
            'operation' => $operation,
            'result' => $this->redact_browser_payload($decoded, $secret_key),
        ));
    }

    /** @param array<string,mixed> $auth_status */
    private function browser_safe_auth_status(array $auth_status) {
        return array(
            'authenticated' => !empty($auth_status['authenticated']),
            'wp_user_id' => intval($auth_status['wp_user_id'] ?? 0),
            'aysa_user_id' => intval($auth_status['aysa_user_id'] ?? 0),
            'domain' => sanitize_text_field((string) ($auth_status['domain'] ?? '')),
            'expires_at' => sanitize_text_field((string) ($auth_status['expires_at'] ?? '')),
            'source' => sanitize_key((string) ($auth_status['source'] ?? 'unknown')),
        );
    }

    private function redact_browser_payload($value, $secret_key) {
        if (is_array($value)) {
            $safe = array();
            foreach ($value as $key => $item) {
                $key_text = (string) $key;
                if (preg_match('/secret|token|authorization|credential|api[_-]?key/i', $key_text) === 1) {
                    continue;
                }
                $safe[$key] = $this->redact_browser_payload($item, $secret_key);
            }
            return $safe;
        }
        if (is_string($value) && $secret_key !== '') {
            return str_replace($secret_key, '[REDACTED]', $value);
        }
        return $value;
    }
    
    /**
     * AJAX handler for logout
     */
    public function ajax_logout() {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
        $valid_nonce = !empty($nonce) && (
            wp_verify_nonce($nonce, 'aysa_nonce') ||
            wp_verify_nonce($nonce, 'aysa-ajax-nonce')
        );
        if (!$valid_nonce) {
            wp_send_json_error(array('message' => 'Security check failed'));
            return;
        }

        if (!is_user_logged_in()) {
            wp_send_json_error(array('message' => 'Authentication required'));
            return;
        }

        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => 'Insufficient permissions'), 403);
            return;
        }

        $result = $this->logout();
        if ($result) {
            wp_send_json_success('Logged out successfully');
        } else {
            wp_send_json_error('Logout failed');
        }
    }
    
    /**
     * AJAX handler to get authentication statistics
     */
    public function ajax_get_auth_stats() {
        // Verify user capabilities for stats
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }
        
        $stats = $this->get_auth_stats();
        wp_send_json_success($stats);
    }
    
    /**
     * Get authentication statistics for admin
     */
    public function get_auth_stats() {
        global $wpdb;
        
        $table_name = esc_sql($wpdb->prefix . self::TABLE_NAME);
        
        $stats = array(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Internal auth table name is derived from the WordPress prefix and a fixed AYSA suffix. Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
            'total_users' => $wpdb->get_var("SELECT COUNT(DISTINCT wp_user_id) FROM $table_name WHERE expires_at > NOW()"),
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Internal auth table name is derived from the WordPress prefix and a fixed AYSA suffix.
            'total_domains' => $wpdb->get_var("SELECT COUNT(DISTINCT domain) FROM $table_name WHERE expires_at > NOW()"),
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Internal auth table name is derived from the WordPress prefix and a fixed AYSA suffix.
            'expired_records' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE expires_at <= NOW()"),
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Internal auth table name is derived from the WordPress prefix and a fixed AYSA suffix.
            'recent_activity' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE last_used >= DATE_SUB(NOW(), INTERVAL 7 DAY)")
        );
        
        return $stats;
    }
    
    /**
     * Get all authenticated users for admin
     */
    public function get_authenticated_users() {
        global $wpdb;
        
        $table_name = esc_sql($wpdb->prefix . self::TABLE_NAME);
        
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reviewed release query uses WordPress or plugin-owned operational tables; current job, billing and idempotency state must not be cached because stale reads can duplicate or mis-order work.
        $results = $wpdb->get_results(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare. Reviewed release SQL: interpolation is limited to escaped or allowlisted plugin-owned table identifiers and generated placeholder lists; all request-derived values are passed to prepare.
            "SELECT a.*, u.user_login, u.user_email, u.display_name 
" .
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reviewed release SQL: the interpolated identifier is an escaped or allowlisted plugin-owned table name; request-derived values remain prepared.
            "             FROM $table_name a
             LEFT JOIN {$wpdb->users} u ON a.wp_user_id = u.ID
             WHERE a.expires_at > NOW() 
             ORDER BY a.last_used DESC",
            ARRAY_A
        );
        
        return $results;
    }
    
    /**
     * WordPress capability check helper for Aysa authenticated users
     * 
     * @return bool True if user is authenticated with Aysa
     */
    public static function current_user_can_access_aysa() {
        // First check if user is logged into WordPress
        if (!is_user_logged_in()) {
            return false;
        }
        
        // Check Aysa authentication
        $instance = self::get_instance();
        $auth_status = $instance->is_authenticated();
        
        return $auth_status['authenticated'];
    }
    
    /**
     * WordPress filter to add custom capability
     */
    public static function map_custom_capability($caps, $cap, $user_id, $args) {
        if ($cap === 'access_aysa_dashboard') {
            // For Aysa dashboard access, check our custom authentication
            if (self::current_user_can_access_aysa()) {
                return array('read'); // Grant access by requiring basic 'read' capability
            } else {
                return array('do_not_allow'); // Deny access
            }
        }
        
        return $caps;
    }
    
    // Helper function to read last N lines of a file
    private function tail($file, $lines = 10) {
        if (!file_exists($file)) return array();
        
        $data = file($file);
        if (!$data) return array();
        
        return array_slice($data, -$lines);
    }

    /**
     * Make authenticated API request
     * 
     * @param string $endpoint API endpoint (without base URL)
     * @param string $method HTTP method (GET, POST, PUT, DELETE)
     * @param array $data Request data
     * @return array|false API response or false on failure
     */
    public function make_authenticated_request($endpoint, $method = 'GET', $data = null) {
        $result = $this->make_authenticated_request_with_meta($endpoint, $method, $data);
        if (empty($result['ok'])) {
            return false;
        }
        if (!empty($result['no_content'])) {
            return true;
        }
        return $result['data'];
    }

    /**
     * Make an authenticated API request while retaining status/body metadata
     * for internal server-side callers that must preserve provider errors.
     * The stored secret is never included in the returned structure.
     *
     * @return array{ok:bool,status:int,body:string,data:mixed,error:string,no_content:bool}
     */
    public function make_authenticated_request_with_meta($endpoint, $method = 'GET', $data = null) {
        aysa_debug_log("[AYSA AUTH] Making authenticated request to: {$endpoint}");
        
        // Get user authentication
        $auth_data = $this->get_user_auth();
        if (!$auth_data || !isset($auth_data->secret_key)) {
            aysa_debug_log("[AYSA AUTH] No valid authentication found");
            return array(
                'ok' => false,
                'status' => 0,
                'body' => '',
                'data' => null,
                'error' => 'Authentication unavailable',
                'no_content' => false,
            );
        }
        
        // Construct full API URL
        $base_url = Aysa_API_Config::get_api_base_url();
        $full_url = rtrim($base_url, '/') . '/' . ltrim($endpoint, '/');
        
        aysa_debug_log("[AYSA AUTH] Full API URL: {$full_url}");
        aysa_debug_log("[AYSA AUTH] Method: {$method}");
        
        // Prepare request arguments
        $args = array(
            'method' => $method,
            'headers' => array(
                'Content-Type' => 'application/json',
                'Authorization' => 'Bearer ' . $auth_data->secret_key,
            ),
            'timeout' => 30,
        );
        
        // Add request body for POST/PUT requests
        if ($data && in_array($method, array('POST', 'PUT', 'PATCH'))) {
            $args['body'] = is_string($data) ? $data : json_encode($data);
            $safe_request_body = preg_replace('/aysa_[a-z]{2}_[A-Za-z0-9_-]+/', '[REDACTED_KEY]', (string) $args['body']);
            aysa_debug_log("[AYSA AUTH] Request body: " . $safe_request_body);
        }
        
        // Add query parameters for GET requests
        if ($data && $method === 'GET') {
            $query_string = http_build_query($data);
            $full_url .= (strpos($full_url, '?') !== false ? '&' : '?') . $query_string;
            $safe_full_url = preg_replace('/aysa_[a-z]{2}_[A-Za-z0-9_-]+/', '[REDACTED_KEY]', (string) $full_url);
            aysa_debug_log("[AYSA AUTH] GET URL with params: {$safe_full_url}");
        }
        
        // Make the request
        aysa_debug_log("[AYSA AUTH] Making HTTP request...");
        $response = wp_remote_request($full_url, $args);
        
        // Check for HTTP errors
        if (is_wp_error($response)) {
            aysa_debug_log("[AYSA AUTH] HTTP Error: " . $response->get_error_message());
            return array(
                'ok' => false,
                'status' => 0,
                'body' => '',
                'data' => null,
                'error' => $response->get_error_message(),
                'no_content' => false,
            );
        }
        
        $response_code = wp_remote_retrieve_response_code($response);
        $response_body = wp_remote_retrieve_body($response);
        $safe_response_body = preg_replace('/aysa_[a-z]{2}_[A-Za-z0-9_-]+/', '[REDACTED_KEY]', (string) $response_body);
        
        aysa_debug_log("[AYSA AUTH] Response code: {$response_code}");
        aysa_debug_log("[AYSA AUTH] Response body: " . substr($safe_response_body, 0, 500) . (strlen($safe_response_body) > 500 ? '...' : ''));
        
        // Check for HTTP success codes
        if ($response_code < 200 || $response_code >= 300) {
            aysa_debug_log("[AYSA AUTH] HTTP Error {$response_code}: {$safe_response_body}");
            $decoded_error = json_decode($response_body, true);
            return array(
                'ok' => false,
                'status' => intval($response_code),
                'body' => (string) $response_body,
                'data' => json_last_error() === JSON_ERROR_NONE ? $decoded_error : null,
                'error' => 'HTTP ' . intval($response_code),
                'no_content' => false,
            );
        }
        
        // Handle 204 No Content (common for DELETE requests)
        if ($response_code === 204) {
            aysa_debug_log("[AYSA AUTH] Request successful (204 No Content)");
            return array(
                'ok' => true,
                'status' => 204,
                'body' => '',
                'data' => true,
                'error' => '',
                'no_content' => true,
            );
        }
        
        // Handle empty response body
        if (empty(trim($response_body))) {
            aysa_debug_log("[AYSA AUTH] Request successful (empty response body)");
            return array(
                'ok' => true,
                'status' => intval($response_code),
                'body' => '',
                'data' => true,
                'error' => '',
                'no_content' => true,
            );
        }
        
        // Decode JSON response
        $decoded_response = json_decode($response_body, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            aysa_debug_log("[AYSA AUTH] JSON decode error: " . json_last_error_msg());
            aysa_debug_log("[AYSA AUTH] Raw response: {$safe_response_body}");
            // For DELETE requests, empty body might be acceptable
            if ($method === 'DELETE' && empty($response_body)) {
                aysa_debug_log("[AYSA AUTH] DELETE request with empty body - treating as success");
                return array(
                    'ok' => true,
                    'status' => intval($response_code),
                    'body' => '',
                    'data' => true,
                    'error' => '',
                    'no_content' => true,
                );
            }
            return array(
                'ok' => false,
                'status' => intval($response_code),
                'body' => (string) $response_body,
                'data' => null,
                'error' => 'JSON decode error: ' . json_last_error_msg(),
                'no_content' => false,
            );
        }
        
        aysa_debug_log("[AYSA AUTH] Request successful, returning decoded response");
        return array(
            'ok' => true,
            'status' => intval($response_code),
            'body' => (string) $response_body,
            'data' => $decoded_response,
            'error' => '',
            'no_content' => false,
        );
    }
}

// Initialize the auth manager
Aysa_Auth_Manager::get_instance();
