<?php
/**
 * Webhook Service Class
 * Handles sending webhook notifications to external systems when order status changes
 */

class WebhookService {
    private $conn;
    private $timeout = 10; // seconds
    
    public function __construct($connection) {
        $this->conn = $connection;
    }
    
    /**
     * Send webhook notification for order status change
     * 
     * @param int $orderId Order ID
     * @param string $oldStatus Previous status
     * @param string $newStatus New status
     * @param string $webhookUrl Webhook URL to send notification to
     * @param int|null $apiKeyId API key ID used to create the order
     * @return array Result array with success status and message
     */
    public function sendOrderStatusWebhook($orderId, $oldStatus, $newStatus, $webhookUrl, $apiKeyId = null) {
        if (empty($webhookUrl)) {
            error_log("WebhookService - SKIPPED: Order #$orderId - Webhook URL is empty");
            return [
                'success' => false,
                'message' => 'Webhook URL is empty',
                'skipped' => true
            ];
        }
        
        // Validate webhook URL
        if (!filter_var($webhookUrl, FILTER_VALIDATE_URL)) {
            error_log("WebhookService - SKIPPED: Order #$orderId - Invalid webhook URL format: $webhookUrl");
            return [
                'success' => false,
                'message' => 'Invalid webhook URL format',
                'webhook_url' => $webhookUrl
            ];
        }
        
        // Get order details
        $order = $this->getOrderDetails($orderId);
        if (!$order) {
            error_log("WebhookService - FAILED: Order #$orderId not found in database");
            return [
                'success' => false,
                'message' => 'Order not found',
                'order_id' => $orderId
            ];
        }
        
        // Get API key details if available
        $apiKeyData = null;
        if ($apiKeyId) {
            $apiKeyData = $this->getApiKeyDetails($apiKeyId);
        }
        
        // Prepare webhook payload
        // Include both formats for compatibility:
        // 1. Standard format for external APIs
        // 2. Flat fields expected by Gigbundles status_update.php endpoint
        $transactionId = $order['external_transaction_id'] ?? $orderId;
        $agentUsername = $order['agent_username'] ?? null;
        
        $payload = [
            'event' => 'order.status.changed',
            'order_id' => $orderId,
            'old_status' => $oldStatus,
            'new_status' => $newStatus,
            // Flat fields expected by remote endpoint
            'transaction_id' => $transactionId,
            'external_transaction_id' => $order['external_transaction_id'] ?? null,
            'status' => $newStatus,
            'phoneNumber' => $order['beneficiary_number'],
            'packageSize' => $order['package_size'],
            'amount' => floatval($order['amount']),
            'username' => $agentUsername,
            'uniqueID' => null,
            'createdAt' => $order['created_at'],
            'type' => 'status_update',
            'order' => [
                'id' => $order['id'],
                'agent_id' => $order['agent_id'],
                'package_size' => $order['package_size'],
                'amount' => floatval($order['amount']),
                'beneficiary_number' => $order['beneficiary_number'],
                'network_type' => $order['network_type'],
                'status' => $newStatus,
                'created_at' => $order['created_at'],
                'status_updated_at' => date('Y-m-d H:i:s'),
                'external_transaction_id' => $order['external_transaction_id'] ?? null,
                'external_reference' => $order['external_reference'] ?? null
            ],
            'timestamp' => date('c'), // ISO 8601 format
            'api_key' => $apiKeyData ? [
                'id' => $apiKeyData['id'],
                'name' => $apiKeyData['name'] ?? null
            ] : null
        ];
        
        // Generate signature for webhook authentication
        $signature = $this->generateSignature($payload, $apiKeyData);
        if ($signature) {
            $payload['signature'] = $signature;
        }
        
        // Send webhook
        $result = $this->sendHttpRequest($webhookUrl, $payload);
        
        // Log webhook attempt to database
        $this->logWebhookAttempt($orderId, $webhookUrl, $payload, $result);
        
        // Log to error_log for easier debugging
        if ($result['success']) {
            error_log("WebhookService - SUCCESS: Order #$orderId status changed from '$oldStatus' to '$newStatus'. URL: $webhookUrl, HTTP: " . ($result['http_code'] ?? 'N/A') . ", Response time: " . ($result['response_time_ms'] ?? 'N/A') . "ms");
        } else {
            error_log("WebhookService - FAILED: Order #$orderId status changed from '$oldStatus' to '$newStatus'. URL: $webhookUrl, Error: " . ($result['message'] ?? 'Unknown error') . ", HTTP: " . ($result['http_code'] ?? 'N/A'));
        }
        
        return $result;
    }
    
    /**
     * Get order details from database
     */
    private function getOrderDetails($orderId) {
        $stmt = $this->conn->prepare("
            SELECT 
                ao.id,
                ao.agent_id,
                ao.package_size,
                ao.amount,
                ao.beneficiary_number,
                ao.network_type,
                ao.status,
                ao.created_at,
                ao.external_transaction_id,
                ao.external_reference,
                ao.order_source,
                ag.username AS agent_username
            FROM agent_orders ao
            LEFT JOIN agents ag ON ao.agent_id = ag.id
            WHERE ao.id = ?
        ");
        
        if (!$stmt) {
            return null;
        }
        
        $stmt->bind_param("i", $orderId);
        $stmt->execute();
        $result = $stmt->get_result();
        $order = $result->fetch_assoc();
        $stmt->close();
        
        return $order;
    }
    
    /**
     * Get API key details
     * Note: We don't retrieve the hashed secret as it's not needed for webhook signature
     * The webhook signature will use a different method if needed
     */
    private function getApiKeyDetails($apiKeyId) {
        $stmt = $this->conn->prepare("
            SELECT id, api_key, name, user_id 
            FROM api_keys 
            WHERE id = ?
        ");
        
        if (!$stmt) {
            return null;
        }
        
        $stmt->bind_param("i", $apiKeyId);
        $stmt->execute();
        $result = $stmt->get_result();
        $keyData = $result->fetch_assoc();
        $stmt->close();
        
        return $keyData;
    }
    
    /**
     * Generate signature for webhook authentication
     * Uses API key as the secret (since we can't retrieve the original secret)
     * External systems can verify using their stored API key
     */
    private function generateSignature($payload, $apiKeyData) {
        if (!$apiKeyData || !isset($apiKeyData['api_key'])) {
            return null;
        }
        
        // Create signature using HMAC SHA256 with API key as secret
        // External systems should verify using their stored API key
        $payloadString = json_encode($payload, JSON_UNESCAPED_SLASHES);
        $signature = hash_hmac('sha256', $payloadString, $apiKeyData['api_key']);
        
        return $signature;
    }
    
    /**
     * Send HTTP POST request to webhook URL
     */
    private function sendHttpRequest($url, $payload) {
        $ch = curl_init();
        
        $payloadJson = json_encode($payload, JSON_UNESCAPED_SLASHES);
        
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $payloadJson,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $this->timeout,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'Content-Length: ' . strlen($payloadJson),
                'User-Agent: D-Bundles Stores-Webhook/1.0'
            ],
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2
        ]);
        
        $startTime = microtime(true);
        $response = curl_exec($ch);
        $responseTime = round((microtime(true) - $startTime) * 1000);
        
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);
        
        if ($error) {
            return [
                'success' => false,
                'message' => 'CURL error: ' . $error,
                'http_code' => $httpCode,
                'response_time_ms' => $responseTime
            ];
        }
        
        // Consider 2xx status codes as success
        $success = ($httpCode >= 200 && $httpCode < 300);
        
        return [
            'success' => $success,
            'message' => $success ? 'Webhook sent successfully' : 'Webhook returned error status',
            'http_code' => $httpCode,
            'response' => $response,
            'response_time_ms' => $responseTime
        ];
    }
    
    /**
     * Log webhook attempt to database
     */
    private function logWebhookAttempt($orderId, $webhookUrl, $payload, $result) {
        // Check if webhook_logs table exists
        $checkTable = $this->conn->query("SHOW TABLES LIKE 'webhook_logs'");
        if (!$checkTable || $checkTable->num_rows === 0) {
            // Table doesn't exist, create it
            $this->createWebhookLogsTable();
        }
        if ($checkTable) {
            $checkTable->close();
        }
        
        $stmt = $this->conn->prepare("
            INSERT INTO webhook_logs 
            (order_id, webhook_url, payload, response, http_code, success, response_time_ms, created_at) 
            VALUES (?, ?, ?, ?, ?, ?, ?, NOW())
        ");
        
        if (!$stmt) {
            error_log("WebhookService: Failed to prepare webhook log statement: " . $this->conn->error);
            return;
        }
        
        $payloadJson = json_encode($payload, JSON_UNESCAPED_SLASHES);
        $responseJson = json_encode($result, JSON_UNESCAPED_SLASHES);
        $httpCode = $result['http_code'] ?? null;
        $success = $result['success'] ? 1 : 0;
        $responseTime = $result['response_time_ms'] ?? null;
        
        $stmt->bind_param("issssii", $orderId, $webhookUrl, $payloadJson, $responseJson, $httpCode, $success, $responseTime);
        $stmt->execute();
        $stmt->close();
    }
    
    /**
     * Create webhook_logs table if it doesn't exist
     */
    private function createWebhookLogsTable() {
        $sql = "
        CREATE TABLE IF NOT EXISTS `webhook_logs` (
            `id` INT(11) NOT NULL AUTO_INCREMENT,
            `order_id` INT(11) NOT NULL,
            `webhook_url` VARCHAR(500) NOT NULL,
            `payload` TEXT NOT NULL,
            `response` TEXT NULL,
            `http_code` INT(11) NULL,
            `success` TINYINT(1) NOT NULL DEFAULT 0,
            `response_time_ms` INT(11) NULL,
            `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (`id`),
            INDEX `idx_order_id` (`order_id`),
            INDEX `idx_created_at` (`created_at`),
            INDEX `idx_success` (`success`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
        ";
        
        $this->conn->query($sql);
    }
    
    /**
     * Send webhooks for multiple orders
     * 
     * @param array $orderIds Array of order IDs
     * @param string $oldStatus Previous status
     * @param string $newStatus New status
     * @return array Results for each order
     */
    public function sendBulkOrderStatusWebhooks($orderIds, $oldStatus, $newStatus) {
        $results = [];
        
        foreach ($orderIds as $orderId) {
            // Get order webhook URL and API key ID
            $orderInfo = $this->getOrderWebhookInfo($orderId);
            
            if ($orderInfo && !empty($orderInfo['webhook_url'])) {
                $result = $this->sendOrderStatusWebhook(
                    $orderId,
                    $oldStatus,
                    $newStatus,
                    $orderInfo['webhook_url'],
                    $orderInfo['api_key_id']
                );
                $results[$orderId] = $result;
            } else {
                $results[$orderId] = [
                    'success' => false,
                    'message' => 'No webhook URL configured for this order',
                    'skipped' => true
                ];
            }
        }
        
        return $results;
    }
    
    /**
     * Get webhook URL and API key ID for an order
     */
    private function getOrderWebhookInfo($orderId) {
        $stmt = $this->conn->prepare("
            SELECT webhook_url, api_key_id 
            FROM agent_orders 
            WHERE id = ?
        ");
        
        if (!$stmt) {
            error_log("WebhookService - Failed to prepare webhook info query for order $orderId: " . $this->conn->error);
            return null;
        }
        
        $stmt->bind_param("i", $orderId);
        if (!$stmt->execute()) {
            error_log("WebhookService - Error selecting webhook info for order $orderId: " . $stmt->error);
            $stmt->close();
            return null;
        }
        
        $result = $stmt->get_result();
        $info = $result->fetch_assoc();
        $stmt->close();
        
        return $info;
    }
}
?>

