<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }

abstract class Vulnity_Alert_Base {
    
    protected $alert_type;
    protected $severity_levels = ['info', 'low', 'medium', 'high', 'critical'];
    protected $enabled = true;
    
    // Alert types that should trigger inventory scan
    protected $inventory_trigger_types = [
        'plugin_change',
        'theme_change', 
        'core_updated'
    ];
    
    // Retry configuration by severity
    protected $retry_config = array(
        'critical' => array('max_attempts' => 5, 'delay' => 5),
        'high' => array('max_attempts' => 3, 'delay' => 5),
        'medium' => array('max_attempts' => 2, 'delay' => 10),
        'low' => array('max_attempts' => 1, 'delay' => 15),
        'info' => array('max_attempts' => 1, 'delay' => 15)
    );
    
    public function __construct() {
        if ($this->enabled) {
            $this->register_hooks();
        }
    }
    
    abstract protected function register_hooks();
    abstract protected function evaluate($data);
    
    protected function create_alert($data) {
        // Suppress alerts from whitelisted IPs if the setting is enabled (default: on)
        if (get_option('vulnity_suppress_blocked_alerts', true)) {
            $alert_ip = isset($data['details']['user_ip']) ? $data['details']['user_ip'] : $this->get_client_ip();
            if ($alert_ip && $alert_ip !== 'unknown') {
                $whitelist = get_option('vulnity_ip_whitelist', array());
                if (in_array($alert_ip, $whitelist, true)) {
                    return null;
                }
            }
        }

        // Generate unique alert ID with timestamp for better tracking
        $alert_id = 'alert_' . time() . '_' . wp_generate_password(8, false);
        
        $alert = array(
            'id' => $alert_id,
            'type' => $this->alert_type,
            'severity' => $data['severity'],
            'title' => $data['title'],
            'message' => $data['message'],
            'details' => $data['details'],
            'timestamp' => current_time('mysql'),
            'timestamp_utc' => current_time('c', true),
            'status' => 'new',
            'sent_to_siem' => false,
            'retry_count' => 0,
            'last_retry' => null,
            'wordpress_version' => get_bloginfo('version'),
            'plugin_version' => VULNITY_VERSION,
            'site_url' => get_site_url(),
            'site_name' => get_bloginfo('name')
        );
        
        // Save locally first
        $this->save_local_alert($alert);

        // Trigger mitigation check for auto-blocking
        do_action('vulnity_alert_created', $alert['id'], $alert);
        
        // Try to send to SIEM with retry logic
        $send_result = $this->send_to_siem_with_retry($alert);
        
        // If alert type triggers inventory scan, schedule it
        if ($send_result && in_array($this->alert_type, $this->inventory_trigger_types)) {
            $this->trigger_inventory_scan($alert);
        }
        
        vulnity_log('[Vulnity] Alert created: ' . $this->alert_type . ' - ' . $alert['severity'] . ' - ID: ' . $alert_id);
        
        return $alert['id'];
    }
    
    /**
     * Trigger inventory scan after specific alert types
     */
    protected function trigger_inventory_scan($alert) {
        vulnity_log('[Vulnity] Triggering inventory scan due to alert type: ' . $this->alert_type);
        
        // Check if we recently did a scan to avoid too many requests
        $last_trigger = get_transient('vulnity_inventory_trigger_cooldown');
        
        if ($last_trigger === false) {
            // Set cooldown to prevent multiple scans in quick succession
            set_transient('vulnity_inventory_trigger_cooldown', true, 30); // 30 seconds cooldown
            
            // Schedule immediate inventory sync
            wp_schedule_single_event(time() + 5, 'vulnity_triggered_inventory_sync', array($alert['type']));
            
            // Also try to do it immediately if possible
            $this->perform_immediate_inventory_sync($alert);
        } else {
            vulnity_log('[Vulnity] Inventory scan skipped - cooldown active');
        }
    }
    
    /**
     * Perform immediate inventory sync
     */
    protected function perform_immediate_inventory_sync($alert) {
        // Load inventory sync class if not loaded
        if (!class_exists('Vulnity_Inventory_Sync')) {
            $inventory_file = vulnity_plugin_path('includes/class-inventory-sync.php');
            if (file_exists($inventory_file)) {
                require_once $inventory_file;
            }
        }
        
        if (class_exists('Vulnity_Inventory_Sync')) {
            $inventory_sync = Vulnity_Inventory_Sync::get_instance();
            
            // Perform sync with special scan type
            $scan_type = 'triggered_by_' . $alert['type'];
            $result = $inventory_sync->perform_sync($scan_type);
            
            if ($result['success']) {
                vulnity_log('[Vulnity] Inventory sync triggered successfully by ' . $alert['type']);
                
                // Add note to alert details
                $alerts = get_option('vulnity_alerts', array());
                foreach ($alerts as &$stored_alert) {
                    if ($stored_alert['id'] === $alert['id']) {
                        $stored_alert['inventory_sync_triggered'] = true;
                        $stored_alert['inventory_sync_time'] = current_time('mysql');
                        break;
                    }
                }
                update_option('vulnity_alerts', $alerts);
            } else {
                vulnity_log('[Vulnity] Inventory sync triggered but failed: ' . $result['error']);
            }
        }
    }
    
    protected function save_local_alert($alert) {
        // Use transient-based locking to prevent race conditions
        $lock_key = 'vulnity_alerts_lock';
        $lock_timeout = 10; // seconds
        $lock_acquired = false;

        // Try to acquire lock with timeout
        $start = time();
        while (!$lock_acquired && (time() - $start) < $lock_timeout) {
            $existing_lock = get_transient($lock_key);

            if ($existing_lock === false) {
                // No lock exists, try to set it
                $lock_acquired = set_transient($lock_key, true, 30);

                if ($lock_acquired) {
                    break;
                }
            }

            // Wait a bit before retrying
            usleep(50000); // 50ms
        }

        if (!$lock_acquired) {
            vulnity_log('[Vulnity] Failed to acquire lock for alert save after ' . $lock_timeout . 's, proceeding anyway');
        }

        try {
            $alerts = get_option('vulnity_alerts', array());
            array_unshift($alerts, $alert);
            $alerts = array_slice($alerts, 0, 100);
            update_option('vulnity_alerts', $alerts);

            $unread = get_option('vulnity_alerts_unread', 0);
            update_option('vulnity_alerts_unread', $unread + 1);

            // Also add to retry queue if critical or high severity
            if (in_array($alert['severity'], array('critical', 'high'))) {
                $this->add_to_retry_queue($alert);
            }
        } finally {
            // Always release lock
            if ($lock_acquired) {
                delete_transient($lock_key);
            }
        }
    }
    
    protected function send_to_siem_with_retry($alert, $attempt = 1) {
        $severity = isset($this->retry_config[$alert['severity']]) ? $alert['severity'] : 'low';
        $max_attempts = $this->retry_config[$severity]['max_attempts'];

        $siem = Vulnity_SIEM_Connector::get_instance();
        $result = $siem->send_alert($alert);

        if ($result['success']) {
            $sent_alert = $this->mark_alert_sent($alert['id'], $result);
            $this->remove_from_retry_queue($alert['id']);
            $this->dispatch_alert_sent($alert['id'], $sent_alert ?: $alert);
            return true;
        } else {
            vulnity_log('[Vulnity] Failed to send alert to SIEM (attempt ' . $attempt . '/' . $max_attempts . '): ' . $result['error']);

            // Update retry count
            $this->update_alert_retry_info($alert['id'], $attempt);

            // Queue for async retry instead of blocking with sleep()
            // This prevents request timeouts and stack overflow
            if ($attempt < $max_attempts) {
                $this->add_to_retry_queue($alert);
                vulnity_log('[Vulnity] Alert ' . $alert['id'] . ' queued for async retry via cron');
                return false;
            } else {
                // Exceeded max attempts, add to failed queue
                $this->add_to_failed_queue($alert);
                vulnity_log('[Vulnity] Alert ' . $alert['id'] . ' added to failed queue after ' . $max_attempts . ' attempts');
                return false;
            }
        }
    }
    
    protected function mark_alert_sent($alert_id, $result) {
        $alerts = get_option('vulnity_alerts', array());
        $sent_alert = self::apply_sent_result_to_alerts($alerts, $alert_id, $result);
        update_option('vulnity_alerts', $alerts);
        return $sent_alert;
    }
    
    protected function update_alert_retry_info($alert_id, $retry_count) {
        $alerts = get_option('vulnity_alerts', array());
        foreach ($alerts as &$stored_alert) {
            if ($stored_alert['id'] === $alert_id) {
                $stored_alert['retry_count'] = $retry_count;
                $stored_alert['last_retry'] = current_time('mysql');
                break;
            }
        }
        update_option('vulnity_alerts', $alerts);
    }
    
    protected function add_to_retry_queue($alert) {
        $queue = get_option('vulnity_retry_queue', array());
        
        // Only add if not already in queue
        $exists = false;
        foreach ($queue as $queued_alert) {
            if ($queued_alert['id'] === $alert['id']) {
                $exists = true;
                break;
            }
        }
        
        if (!$exists) {
            $queue[] = array(
                'id' => $alert['id'],
                'added_at' => current_time('mysql'),
                'priority' => $this->get_severity_priority($alert['severity'])
            );
            
            // Sort by priority (higher priority first)
            usort($queue, function($a, $b) {
                return $a['priority'] - $b['priority'];
            });
            
            // Keep only 50 items in retry queue
            $queue = array_slice($queue, 0, 50);
            
            update_option('vulnity_retry_queue', $queue);
        }
    }
    
    protected function remove_from_retry_queue($alert_id) {
        self::remove_from_retry_queue_static($alert_id);
    }

    protected static function remove_from_retry_queue_static($alert_id) {
        $queue = get_option('vulnity_retry_queue', array());
        $queue = array_filter($queue, function($item) use ($alert_id) {
            return $item['id'] !== $alert_id;
        });
        update_option('vulnity_retry_queue', array_values($queue));
    }
    
    protected function add_to_failed_queue($alert) {
        $failed = get_option('vulnity_failed_alerts', array());
        
        $failed[] = array(
            'alert' => $alert,
            'failed_at' => current_time('mysql'),
            'reason' => 'Max retry attempts exceeded'
        );
        
        // Keep only last 100 failed alerts
        $failed = array_slice($failed, -100);
        
        update_option('vulnity_failed_alerts', $failed);
        
        // Trigger admin notification for critical alerts
        if ($alert['severity'] === 'critical') {
            $this->notify_admin_of_critical_failure($alert);
        }
    }
    
    protected function notify_admin_of_critical_failure($alert) {
        // Add admin notice
        set_transient('vulnity_critical_alert_failed', array(
            'message' => 'Critical security alert failed to send to SIEM: ' . $alert['title'],
            'alert_id' => $alert['id']
        ), HOUR_IN_SECONDS);
    }

    protected function dispatch_alert_sent($alert_id, $alert) {
        self::dispatch_alert_sent_static($alert_id, $alert);
    }

    public static function dispatch_alert_sent_static($alert_id, $alert) {
        if (empty($alert_id) || !is_array($alert)) {
            return;
        }

        do_action('vulnity_alert_sent', $alert_id, $alert);
    }

    public static function apply_sent_result_to_alerts(&$alerts, $alert_id, $result) {
        if (!is_array($alerts) || empty($alert_id)) {
            return null;
        }

        foreach ($alerts as &$stored_alert) {
            if (!isset($stored_alert['id']) || $stored_alert['id'] !== $alert_id) {
                continue;
            }

            $stored_alert['sent_to_siem'] = true;
            $stored_alert['siem_response'] = isset($result['data']) && is_array($result['data'])
                ? $result['data']
                : array();
            $stored_alert['sent_at'] = current_time('mysql');
            unset($stored_alert['last_error']);

            return $stored_alert;
        }

        return null;
    }
    
    protected function get_severity_priority($severity) {
        $priorities = array(
            'critical' => 1,
            'high' => 2,
            'medium' => 3,
            'low' => 4,
            'info' => 5
        );
        return isset($priorities[$severity]) ? $priorities[$severity] : 5;
    }
    
    protected function get_client_ip() {
        return function_exists('vulnity_resolve_client_ip')
            ? vulnity_resolve_client_ip()
            : 'unknown';
    }

    protected function get_current_user_info() {
        $current_user = wp_get_current_user();

        $user_agent = vulnity_get_server_var('HTTP_USER_AGENT');
        $request_uri = vulnity_get_server_var('REQUEST_URI');
        $referer = vulnity_get_server_var('HTTP_REFERER');

        return array(
            'user_id' => $current_user->ID,
            'user_login' => $current_user->user_login ?: 'anonymous',
            'user_email' => $current_user->user_email ?: '',
            'user_roles' => $current_user->roles ?: array(),
            'user_ip' => $this->get_client_ip(),
            'user_agent' => $user_agent !== '' ? $user_agent : 'Unknown',
            'request_uri' => $request_uri !== '' ? esc_url_raw($request_uri) : '',
            'referer' => $referer !== '' ? esc_url_raw($referer) : 'Direct'
        );
    }
    
    /**
     * Process retry queue - called by cron
     */
    public static function process_retry_queue() {
        $queue = get_option('vulnity_retry_queue', array());
        
        if (empty($queue)) {
            return;
        }
        
        $alerts = get_option('vulnity_alerts', array());
        $siem = Vulnity_SIEM_Connector::get_instance();
        $processed = array();
        $sent_alerts = array();
        
        foreach ($queue as $queue_item) {
            // Find the alert
            $alert = null;
            foreach ($alerts as $stored_alert) {
                if ($stored_alert['id'] === $queue_item['id']) {
                    $alert = $stored_alert;
                    break;
                }
            }
            
            if ($alert && !$alert['sent_to_siem']) {
                $result = $siem->send_alert($alert);
                
                if ($result['success']) {
                    $sent_alert = self::apply_sent_result_to_alerts($alerts, $alert['id'], $result);
                    if ($sent_alert !== null) {
                        $sent_alerts[] = array(
                            'id' => $alert['id'],
                            'alert' => $sent_alert,
                        );
                    }
                    $processed[] = $alert['id'];
                    vulnity_log('[Vulnity] Alert ' . $alert['id'] . ' sent successfully from retry queue');
                }
            }
        }
        
        // Update alerts
        update_option('vulnity_alerts', $alerts);
        
        // Remove processed items from queue
        if (!empty($processed)) {
            $queue = array_filter($queue, function($item) use ($processed) {
                return !in_array($item['id'], $processed);
            });
            update_option('vulnity_retry_queue', array_values($queue));
        }

        foreach ($sent_alerts as $sent_entry) {
            self::dispatch_alert_sent_static($sent_entry['id'], $sent_entry['alert']);
        }
    }
    
    /**
     * Manually retry sending a specific alert
     */
    public static function retry_single_alert($alert_id) {
        $alerts = get_option('vulnity_alerts', array());
        $alert = null;
        
        foreach ($alerts as &$stored_alert) {
            if ($stored_alert['id'] === $alert_id) {
                $alert = &$stored_alert;
                break;
            }
        }
        
        if (!$alert) {
            return array('success' => false, 'error' => 'Alert not found');
        }
        
        if ($alert['sent_to_siem']) {
            return array('success' => false, 'error' => 'Alert already sent');
        }
        
        $siem = Vulnity_SIEM_Connector::get_instance();
        $result = $siem->send_alert($alert);
        
        if ($result['success']) {
            $sent_alert = self::apply_sent_result_to_alerts($alerts, $alert_id, $result);
            update_option('vulnity_alerts', $alerts);
            
            // Remove from retry queue if present
            self::remove_from_retry_queue_static($alert_id);

            if ($sent_alert !== null) {
                self::dispatch_alert_sent_static($alert_id, $sent_alert);
            }
            
            return array('success' => true, 'message' => 'Alert sent successfully');
        } else {
            $alert['retry_count'] = isset($alert['retry_count']) ? $alert['retry_count'] + 1 : 1;
            $alert['last_retry'] = current_time('mysql');
            $alert['last_error'] = $result['error'];
            update_option('vulnity_alerts', $alerts);
            
            return array('success' => false, 'error' => $result['error']);
        }
    }
    
    /**
     * Retry all failed alerts
     */
    public static function retry_all_failed_alerts() {
        $alerts = get_option('vulnity_alerts', array());
        $failed_count = 0;
        $success_count = 0;
        
        foreach ($alerts as $alert) {
            if (!$alert['sent_to_siem']) {
                $result = self::retry_single_alert($alert['id']);
                if ($result['success']) {
                    $success_count++;
                } else {
                    $failed_count++;
                }
            }
        }
        
        return array(
            'success' => true,
            'message' => sprintf('%d alerts sent successfully, %d failed', $success_count, $failed_count),
            'sent' => $success_count,
            'failed' => $failed_count
        );
    }
}
