<?php
/**
 * Ticket Tailor Webhook Handler
 *
 * Handles real-time updates via webhooks from Ticket Tailor
 * FIXED VERSION: Added rate limiting (100 requests/hour)
 */

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

class Ticket_Tailor_Webhook_Handler {
    
    /**
     * Event Manager
     */
    private $events;
    
    /**
     * Order Manager
     */
    private $orders;
    
    /**
     * Webhook secret
     */
    private $webhook_secret;
    
    /**
     * Rate limiting settings
     */
    private $rate_limit = 100; // Max requests per hour
    private $rate_limit_window = 3600; // 1 hour in seconds

    /**
     * Allowed webhook IP ranges - SECURITY ENHANCEMENT
     * Note: Update this array with actual Ticket Tailor IP ranges
     */
    private $default_webhook_ips = array(
        // These are placeholder IPs - replace with actual Ticket Tailor IPs
        // Format: 'x.x.x.x' or 'x.x.x.x/24' for CIDR ranges
        '0.0.0.0/0', // Allow all by default - should be configured by admin
    );

    /**
     * Security logger
     */
    private $security_logger;
    
    /**
     * Constructor - SECURITY ENHANCEMENT: Added security logger
     */
    public function __construct($events, $orders) {
        $this->events = $events;
        $this->orders = $orders;
        $this->webhook_secret = get_option('ticket_tailor_webhook_secret', '');
        $this->security_logger = new Ticket_Tailor_Security_Logger();

        // Register webhook endpoint
        add_action('rest_api_init', array($this, 'register_webhook_endpoint'));
    }
    
    /**
     * Register REST API endpoint for webhooks
     */
    public function register_webhook_endpoint() {
        register_rest_route('ticket-tailor/v1', '/webhook', array(
            'methods' => 'POST',
            'callback' => array($this, 'handle_webhook'),
            'permission_callback' => '__return_true', // Public endpoint (verified via secret)
        ));
    }
    
    /**
     * Handle incoming webhook - SECURITY ENHANCEMENT: IP whitelisting & logging
     */
    public function handle_webhook($request) {
        $ip_address = $this->get_client_ip();

        // SECURITY ENHANCEMENT: Check IP whitelist first
        if (!$this->verify_webhook_ip($ip_address)) {
            $this->security_logger->log_failed_webhook($ip_address, 'IP not whitelisted');
            return new WP_Error(
                'ip_not_allowed',
                __('IP address not authorized for webhooks', 'ticket-tailor'),
                array('status' => 403)
            );
        }

        // SECURITY FIX: Verify webhook secret is configured (mandatory)
        if (empty($this->webhook_secret)) {
            $this->security_logger->log_failed_webhook($ip_address, 'Webhook secret not configured');
            return new WP_Error(
                'webhook_not_configured',
                __('Webhook secret not configured. Please set up webhook authentication.', 'ticket-tailor'),
                array('status' => 503)
            );
        }

        // Check rate limit
        if (!$this->check_rate_limit($ip_address)) {
            $this->security_logger->log_rate_limit_exceeded($ip_address, 'webhook');
            return new WP_Error(
                'rate_limit_exceeded',
                __('Rate limit exceeded. Please try again later.', 'ticket-tailor'),
                array('status' => 429)
            );
        }

        // Get raw body with size limit (SECURITY FIX: Prevent DoS)
        $body = $request->get_body();
        if (strlen($body) > 102400) { // 100KB limit
            return new WP_Error(
                'payload_too_large',
                __('Webhook payload too large', 'ticket-tailor'),
                array('status' => 413)
            );
        }

        $data = json_decode($body, true);

        // SECURITY FIX: Validate JSON
        if (json_last_error() !== JSON_ERROR_NONE) {
            return new WP_Error(
                'invalid_json',
                __('Invalid JSON payload', 'ticket-tailor'),
                array('status' => 400)
            );
        }

        // SECURITY FIX: Verify webhook signature (now mandatory)
        $signature = $request->get_header('X-TT-Signature');

        if (!$this->verify_signature($body, $signature)) {
            $this->security_logger->log_failed_webhook($ip_address, 'Invalid signature');
            return new WP_Error(
                'invalid_signature',
                __('Invalid webhook signature', 'ticket-tailor'),
                array('status' => 401)
            );
        }

        // ENTERPRISE: Idempotency check - prevent duplicate processing
        $webhook_id = $data['id'] ?? md5($body);
        if ($this->is_webhook_processed($webhook_id)) {
            // Already processed this webhook - return success to ack receipt
            return new WP_REST_Response(array(
                'success' => true,
                'message' => 'Webhook already processed (idempotent)',
                'webhook_id' => $webhook_id,
            ), 200);
        }

        // Log webhook for debugging (optional)
        $this->log_webhook($data);

        // SECURITY ENHANCEMENT: Log successful webhook
        $event_type = $data['type'] ?? 'unknown';
        $this->security_logger->log_successful_webhook($ip_address, $event_type);

        // ENTERPRISE: Mark webhook as processed
        $this->mark_webhook_processed($webhook_id, $event_type, $ip_address);
        
        // Process webhook based on type
        $event_type = $data['type'] ?? '';
        
        switch ($event_type) {
            case 'order.created':
            case 'order.updated':
                $this->handle_order_webhook($data);
                break;
                
            case 'issued_ticket.created':
            case 'issued_ticket.updated':
            case 'issued_ticket.voided':
                $this->handle_ticket_webhook($data);
                break;
                
            case 'event.created':
            case 'event.updated':
                $this->handle_event_webhook($data);
                break;
                
            default:
                // Unknown webhook type
                break;
        }
        
        // Trigger custom action for developers
        do_action('ticket_tailor_webhook_received', $event_type, $data);
        
        return new WP_REST_Response(array(
            'success' => true,
            'message' => 'Webhook processed',
        ), 200);
    }
    
    /**
     * Check rate limit - SECURITY FIX: IP-based rate limiting with database storage
     */
    private function check_rate_limit($ip_address) {
        global $wpdb;

        // Sanitize IP address
        $ip_address = filter_var($ip_address, FILTER_VALIDATE_IP);
        if (!$ip_address) {
            return false; // Invalid IP
        }

        // Create rate limit table if it doesn't exist
        $table_name = $wpdb->prefix . 'ticket_tailor_rate_limits';
        $this->maybe_create_rate_limit_table($table_name);

        // Clean up old entries (older than rate limit window)
        $wpdb->query($wpdb->prepare(
            "DELETE FROM {$table_name} WHERE timestamp < %s",
            gmdate('Y-m-d H:i:s', time() - $this->rate_limit_window)
        ));

        // Count requests from this IP in the current window
        $request_count = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table_name} WHERE ip_address = %s AND timestamp > %s",
            $ip_address,
            gmdate('Y-m-d H:i:s', time() - $this->rate_limit_window)
        ));

        if ($request_count >= $this->rate_limit) {
            return false; // Rate limit exceeded
        }

        // Record this request
        $wpdb->insert(
            $table_name,
            array(
                'ip_address' => $ip_address,
                'timestamp' => current_time('mysql', 1),
            ),
            array('%s', '%s')
        );

        return true;
    }

    /**
     * Create rate limit table if it doesn't exist - SECURITY FIX
     */
    private function maybe_create_rate_limit_table($table_name) {
        global $wpdb;

        $charset_collate = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            ip_address varchar(45) NOT NULL,
            timestamp datetime NOT NULL,
            PRIMARY KEY (id),
            KEY ip_timestamp (ip_address, timestamp)
        ) {$charset_collate};";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta($sql);
    }

    /**
     * Get client IP address safely - SECURITY FIX
     */
    private function get_client_ip() {
        $ip_keys = array(
            'HTTP_CF_CONNECTING_IP', // CloudFlare
            'HTTP_X_FORWARDED_FOR',  // Proxy
            'HTTP_X_REAL_IP',        // Nginx proxy
            'REMOTE_ADDR',           // Direct connection
        );

        foreach ($ip_keys as $key) {
            if (!empty($_SERVER[$key])) {
                $ip = $_SERVER[$key];

                // Handle comma-separated list (X-Forwarded-For)
                if (strpos($ip, ',') !== false) {
                    $ip = trim(explode(',', $ip)[0]);
                }

                // Validate IP
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }

        // Fallback to REMOTE_ADDR even if private
        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
    }
    
    /**
     * Get current rate limit status - NEW IN THIS VERSION
     */
    public function get_rate_limit_status() {
        $transient_key = 'tt_webhook_rate_limit';
        $requests = get_transient($transient_key);
        
        return array(
            'requests_made' => $requests !== false ? $requests : 0,
            'requests_remaining' => $this->rate_limit - ($requests !== false ? $requests : 0),
            'limit' => $this->rate_limit,
            'window' => $this->rate_limit_window,
        );
    }
    
    /**
     * Verify webhook IP address - SECURITY ENHANCEMENT
     */
    private function verify_webhook_ip($ip) {
        // Get configured IP whitelist (defaults to allow all if not configured)
        $allowed_ips = get_option('ticket_tailor_webhook_ips', $this->default_webhook_ips);

        // If whitelist is empty, allow all (for backward compatibility)
        if (empty($allowed_ips)) {
            return true;
        }

        foreach ($allowed_ips as $allowed_range) {
            if ($this->ip_in_range($ip, $allowed_range)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if IP is in CIDR range - SECURITY ENHANCEMENT
     */
    private function ip_in_range($ip, $range) {
        // Handle exact match
        if (strpos($range, '/') === false) {
            return $ip === $range;
        }

        // Handle CIDR notation
        list($subnet, $bits) = explode('/', $range);

        // Convert IPs to long integers
        $ip_long = ip2long($ip);
        $subnet_long = ip2long($subnet);

        if ($ip_long === false || $subnet_long === false) {
            return false;
        }

        // Create netmask
        $mask = -1 << (32 - (int)$bits);
        $subnet_long &= $mask;

        return ($ip_long & $mask) == $subnet_long;
    }

    /**
     * Verify webhook signature
     */
    private function verify_signature($payload, $signature) {
        if (empty($signature) || empty($this->webhook_secret)) {
            return false;
        }

        $expected_signature = hash_hmac('sha256', $payload, $this->webhook_secret);

        return hash_equals($expected_signature, $signature);
    }
    
    /**
     * Handle order webhook
     */
    private function handle_order_webhook($data) {
        if (empty($data['data']['id'])) {
            return;
        }
        
        $order_id = $data['data']['id'];
        
        // Force refresh this specific order
        $order = $this->orders->get_order($order_id, true);
        
        if (!is_wp_error($order)) {
            // Trigger action for custom handling
            do_action('ticket_tailor_order_webhook', $data['type'], $order);
        }
    }
    
    /**
     * Handle ticket webhook
     */
    private function handle_ticket_webhook($data) {
        if (empty($data['data']['order_id'])) {
            return;
        }
        
        $order_id = $data['data']['order_id'];
        
        // Refresh the related order
        $order = $this->orders->get_order($order_id, true);
        
        if (!is_wp_error($order)) {
            // Trigger action for custom handling
            do_action('ticket_tailor_ticket_webhook', $data['type'], $data['data'], $order);
        }
    }
    
    /**
     * Handle event webhook
     */
    private function handle_event_webhook($data) {
        if (empty($data['data']['id'])) {
            return;
        }
        
        $event_id = $data['data']['id'];
        
        // Force refresh this specific event
        $event = $this->events->get_event($event_id, true);
        
        if (!is_wp_error($event)) {
            // Trigger action for custom handling
            do_action('ticket_tailor_event_webhook', $data['type'], $event);
        }
    }
    
    /**
     * Log webhook for debugging
     */
    private function log_webhook($data) {
        // Only log if debug mode is enabled
        if (!get_option('ticket_tailor_debug_mode', false)) {
            return;
        }
        
        $log_entry = array(
            'timestamp' => current_time('mysql'),
            'type' => $data['type'] ?? 'unknown',
            'data' => $data,
        );
        
        // Store in transient (keep last 50 webhooks)
        $logs = get_transient('ticket_tailor_webhook_logs') ?: array();
        array_unshift($logs, $log_entry);
        $logs = array_slice($logs, 0, 50);
        set_transient('ticket_tailor_webhook_logs', $logs, DAY_IN_SECONDS);
    }
    
    /**
     * Get webhook URL
     */
    public function get_webhook_url() {
        return rest_url('ticket-tailor/v1/webhook');
    }
    
    /**
     * Generate webhook secret
     */
    public function generate_webhook_secret() {
        $secret = wp_generate_password(64, false);
        update_option('ticket_tailor_webhook_secret', $secret);
        $this->webhook_secret = $secret;
        return $secret;
    }
    
    /**
     * Set webhook secret
     */
    public function set_webhook_secret($secret) {
        $secret = sanitize_text_field($secret);
        update_option('ticket_tailor_webhook_secret', $secret);
        $this->webhook_secret = $secret;
    }
    
    /**
     * Clear webhook secret
     */
    public function clear_webhook_secret() {
        delete_option('ticket_tailor_webhook_secret');
        $this->webhook_secret = '';
    }
    
    /**
     * Get webhook logs
     */
    public function get_logs() {
        return get_transient('ticket_tailor_webhook_logs') ?: array();
    }
    
    /**
     * Clear webhook logs
     */
    public function clear_logs() {
        delete_transient('ticket_tailor_webhook_logs');
    }
    
    /**
     * Test webhook
     */
    public function test_webhook() {
        $test_data = array(
            'type' => 'test.webhook',
            'created_at' => current_time('mysql'),
            'data' => array(
                'message' => 'This is a test webhook',
            ),
        );

        $this->log_webhook($test_data);

        do_action('ticket_tailor_webhook_received', 'test.webhook', $test_data);

        return true;
    }

    /**
     * Check if webhook has been processed - ENTERPRISE: Idempotency
     */
    private function is_webhook_processed($webhook_id) {
        global $wpdb;

        $table = $wpdb->prefix . 'ticket_tailor_webhook_log';

        $exists = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$table} WHERE webhook_id = %s",
            $webhook_id
        ));

        return (bool) $exists;
    }

    /**
     * Mark webhook as processed - ENTERPRISE: Idempotency
     */
    private function mark_webhook_processed($webhook_id, $event_type, $ip_address) {
        global $wpdb;

        $table = $wpdb->prefix . 'ticket_tailor_webhook_log';

        $wpdb->insert(
            $table,
            array(
                'webhook_id' => $webhook_id,
                'event_type' => $event_type,
                'processed_at' => current_time('mysql', 1),
                'ip_address' => $ip_address,
            ),
            array('%s', '%s', '%s', '%s')
        );

        // ENTERPRISE: Cleanup old webhook logs (older than 30 days)
        $wpdb->query($wpdb->prepare(
            "DELETE FROM {$table} WHERE processed_at < %s",
            gmdate('Y-m-d H:i:s', time() - (30 * DAY_IN_SECONDS))
        ));
    }
}
