#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
RuntimeBridge - A QObject that bridges between the Python browser and JavaScript chrome.runtime API
"""

import os
import json
import time
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot, QUrl, QTimer
from PyQt6.QtWebEngineCore import QWebEngineScript

class RuntimeBridge(QObject):
    """
    A QObject that provides a bridge between the Python browser and the JavaScript chrome.runtime API.
    This class is exposed to JavaScript via QWebChannel.
    """
    
    # Signals to communicate with JavaScript
    messageReceived = pyqtSignal(str)
    portMessageReceived = pyqtSignal(str, str)
    
    def __init__(self, browser, extensions_dir, parent=None):
        """Initialize the RuntimeBridge."""
        super().__init__(parent)
        self.browser = browser
        self.extensions_dir = extensions_dir
        self.extension_ports = {}  # Dictionary to track active port connections
        self.extension_pages = {}  # Dictionary to track active extension pages
        self.manifest_cache = {}   # Cache for extension manifests
    
    @pyqtSlot(str, str)
    def extensionLoaded(self, extension_id, page_path):
        """Called when an extension page is loaded and the runtime API is initialized."""
        print(f"Extension {extension_id} loaded page {page_path}")
        
        if extension_id not in self.extension_pages:
            self.extension_pages[extension_id] = []
        
        self.extension_pages[extension_id].append(page_path)
        
        # If this is a background page, we might want to trigger onInstalled or onStartup events
        if page_path.endswith('background.html') or page_path.endswith('background.js'):
            # In a real implementation, we would check if this is a first install or update
            # For now, we'll just simulate an install event
            self.triggerOnInstalledEvent(extension_id)
    
    def triggerOnInstalledEvent(self, extension_id):
        """Trigger the onInstalled event for an extension."""
        # This would be called when an extension is first installed or updated
        # For now, we'll just print a message
        print(f"Triggering onInstalled event for extension {extension_id}")
        # In a real implementation, we would send a message to the extension
    
    @pyqtSlot(str, result=str)
    def getManifest(self, extension_id):
        """Get the manifest for an extension."""
        if extension_id in self.manifest_cache:
            return self.manifest_cache[extension_id]
        
        # First try to load from the profile's extensions directory
        manifest_path = os.path.join(self.extensions_dir, extension_id, 'manifest.json')
        
        # If not found, try the assets extensions directory
        if not os.path.exists(manifest_path):
            assets_manifest_path = os.path.join("assets/browser/extensions", extension_id, 'manifest.json')
            if os.path.exists(assets_manifest_path):
                manifest_path = assets_manifest_path
        
        if os.path.exists(manifest_path):
            try:
                with open(manifest_path, 'r', encoding='utf-8') as f:
                    manifest_data = json.load(f)
                    
                    # For test-extension, ensure it has the right permissions
                    if extension_id == "test-extension":
                        # Make sure permissions are set correctly
                        if "permissions" not in manifest_data:
                            manifest_data["permissions"] = []
                        
                        # Add necessary permissions if not already present
                        required_permissions = ["storage"]
                        for perm in required_permissions:
                            if perm not in manifest_data["permissions"]:
                                manifest_data["permissions"].append(perm)
                    
                    manifest_json = json.dumps(manifest_data)
                    self.manifest_cache[extension_id] = manifest_json
                    return manifest_json
            except Exception as e:
                print(f"Error loading manifest for {extension_id}: {e}")
                return "{}"
        else:
            print(f"Manifest not found for {extension_id}")
            return "{}"
    
    @pyqtSlot(str, result=bool)
    def getBackgroundPage(self, extension_id):
        """Get the background page for an extension."""
        print(f"Getting background page for {extension_id}")
        
        # In a real implementation, we would return a reference to the background page
        # For now, we'll just simulate success for the test extension
        if extension_id == "test-extension":
            # Emit a message to simulate the background page responding
            background_message = {
                "action": "background_accessed",
                "from": "background",
                "timestamp": int(time.time() * 1000)
            }
            
            # Emit after a short delay to simulate async behavior
            QTimer.singleShot(100, lambda: self.messageReceived.emit(json.dumps(background_message)))
            return True
        
        return False
    
    @pyqtSlot(str, str, str, result=str)
    def sendMessage(self, target_extension_id, message, options, callback=None):
        """Send a message to an extension."""
        print(f"Sending message to {target_extension_id}: {message}")
        
        # Parse the message and options
        try:
            message_obj = json.loads(message)
            options_obj = json.loads(options)
            
            # Emit the messageReceived signal to notify all listeners
            # This will be received by any extension page that has registered a listener
            self.messageReceived.emit(message)
            
            # For our test extension, we'll simulate a response from the background script
            if target_extension_id == "test-extension":
                response = {
                    "success": True,
                    "from": "background",
                    "received": True,
                    "timestamp": int(__import__('time').time() * 1000),
                    "echo": message_obj
                }
            else:
                # Default response for other extensions
                response = {"success": True, "echo": message_obj}
            
            if callback:
                callback(json.dumps(response))
            
            return json.dumps(response)
        except Exception as e:
            print(f"Error sending message: {e}")
            if callback:
                callback('{"error": "Failed to send message"}')
            return '{"error": "Failed to send message"}'
    
    @pyqtSlot(str, str)
    def connectPort(self, extension_id, port_name):
        """Connect a port for message passing."""
        print(f"Connecting port {port_name} for extension {extension_id}")
        
        if extension_id not in self.extension_ports:
            self.extension_ports[extension_id] = {}
        
        self.extension_ports[extension_id][port_name] = True
        
        # For test extension, send an initial connection message
        if extension_id == "test-extension":
            # Send a welcome message after a short delay to simulate async behavior
            QTimer.singleShot(100, lambda: self.send_port_welcome_message(extension_id, port_name))
    
    def send_port_welcome_message(self, extension_id, port_name):
        """Send a welcome message to a newly connected port."""
        welcome_message = {
            "action": "connected",
            "from": "background",
            "timestamp": int(__import__('time').time() * 1000),
            "message": f"Welcome to port {port_name}!"
        }
        self.portMessageReceived.emit(port_name, json.dumps(welcome_message))
    
    @pyqtSlot(str, str)
    def disconnectPort(self, extension_id, port_name):
        """Disconnect a port."""
        print(f"Disconnecting port {port_name} for extension {extension_id}")
        
        if extension_id in self.extension_ports and port_name in self.extension_ports[extension_id]:
            del self.extension_ports[extension_id][port_name]
    
    @pyqtSlot(str, str, str)
    def postPortMessage(self, extension_id, port_name, message):
        """Post a message to a port."""
        print(f"Posting message to port {port_name} for extension {extension_id}: {message}")
        
        # Check if the port exists
        if extension_id in self.extension_ports and port_name in self.extension_ports[extension_id]:
            # For test extension, echo the message back with additional data
            if extension_id == "test-extension":
                try:
                    message_obj = json.loads(message)
                    response = {
                        "received": True,
                        "from": "background",
                        "timestamp": int(__import__('time').time() * 1000),
                        "echo": message_obj
                    }
                    # Emit the response after a short delay to simulate async processing
                    QTimer.singleShot(200, lambda: self.portMessageReceived.emit(port_name, json.dumps(response)))
                except Exception as e:
                    print(f"Error processing port message: {e}")
                    self.portMessageReceived.emit(port_name, json.dumps({"error": str(e)}))
            else:
                # For other extensions, just echo the message back
                self.portMessageReceived.emit(port_name, message)
        else:
            print(f"Port {port_name} for extension {extension_id} not found")
    
    @pyqtSlot(str, str)
    def sendResponse(self, extension_id, response):
        """Send a response to a message."""
        print(f"Sending response for extension {extension_id}: {response}")
        
        # In a real implementation, we would route this response to the appropriate sender
        # For now, we'll just print it
    
    @pyqtSlot(str)
    def reloadExtension(self, extension_id):
        """Reload an extension."""
        print(f"Reloading extension {extension_id}")
        
        # In a real implementation, we would reload the extension
        # For now, we'll just print a message
        
        # If the browser has a reload_profile_and_tabs method, call it
        if hasattr(self.browser, 'reload_profile_and_tabs'):
            self.browser.reload_profile_and_tabs()

def create_runtime_api_script():
    """Create a QWebEngineScript to inject the chrome.runtime API emulation."""
    script = QWebEngineScript()
    
    # Load the QWebChannel.js script
    qwebchannel_path = os.path.join(os.path.dirname(__file__), 'qwebchannel.js')
    with open(qwebchannel_path, 'r', encoding='utf-8') as f:
        qwebchannel_js = f.read()
    
    # Load the chrome-runtime-api.js script
    runtime_api_path = os.path.join(os.path.dirname(__file__), 'chrome-runtime-api.js')
    with open(runtime_api_path, 'r', encoding='utf-8') as f:
        runtime_api_js = f.read()
    
    # Create a wrapper script that ensures QWebChannel is loaded before chrome-runtime-api.js
    wrapper_js = """
    // Wrapper to ensure proper initialization sequence
    (function() {
        // Create global initialization tracking variables
        window.__qtWebChannelTransportReady = false;
        window.__chromeRuntimeInitialized = false;
        window.__initializationAttempts = 0;
        
        // First, make sure QWebChannel is available
        %s
        
        // Add helper functions to check initialization status
        window.isQWebChannelReady = function() {
            return typeof QWebChannel !== 'undefined' &&
                   typeof qt !== 'undefined' &&
                   qt.webChannelTransport !== undefined;
        };
        
        window.isChromeRuntimeInitialized = function() {
            return window.__chromeRuntimeInitialized === true ||
                   (typeof chrome !== 'undefined' &&
                    typeof chrome.runtime !== 'undefined' &&
                    window.__qtWebChannelTransportReady === true);
        };
        
        // Function to handle initialization failure
        window.reportInitializationStatus = function() {
            console.log('Initialization Status:');
            console.log('- QWebChannel available: ' + (typeof QWebChannel !== 'undefined'));
            console.log('- qt object available: ' + (typeof qt !== 'undefined'));
            console.log('- qt.webChannelTransport available: ' + (typeof qt !== 'undefined' && qt.webChannelTransport !== undefined));
            console.log('- chrome object available: ' + (typeof chrome !== 'undefined'));
            console.log('- chrome.runtime available: ' + (typeof chrome !== 'undefined' && chrome.runtime !== undefined));
            console.log('- __qtWebChannelTransportReady: ' + window.__qtWebChannelTransportReady);
            console.log('- __chromeRuntimeInitialized: ' + window.__chromeRuntimeInitialized);
            console.log('- __initializationAttempts: ' + window.__initializationAttempts);
        };
        
        // Set up event listeners for initialization events
        document.addEventListener('webChannelReady', function(event) {
            console.log('webChannelReady event received');
            window.__qtWebChannelTransportReady = true;
            window.reportInitializationStatus();
        });
        
        document.addEventListener('chrome.runtime.initialized', function(event) {
            console.log('chrome.runtime.initialized event received for extension: ' + event.detail.extensionId);
            window.__chromeRuntimeInitialized = true;
            window.reportInitializationStatus();
        });
        
        // Then initialize the chrome.runtime API
        %s
        
        // Log the initialization status
        console.log('Runtime API script injected. QWebChannel ready: ' + window.isQWebChannelReady());
        window.reportInitializationStatus();
        
        // Set up a periodic check for initialization status
        var statusCheckInterval = setInterval(function() {
            window.__initializationAttempts++;
            
            if (window.isChromeRuntimeInitialized()) {
                console.log('Chrome runtime API successfully initialized');
                clearInterval(statusCheckInterval);
            } else if (window.__initializationAttempts >= 50) {
                console.error('Failed to initialize Chrome runtime API after 50 attempts');
                window.reportInitializationStatus();
                clearInterval(statusCheckInterval);
            } else if (window.__initializationAttempts %% 10 === 0) {
                console.log('Still waiting for Chrome runtime API initialization (attempt ' + window.__initializationAttempts + ')');
                window.reportInitializationStatus();
                
                // Try to trigger initialization if not already initialized
                if (!window.__qtWebChannelTransportReady) {
                    console.log('Attempting to trigger initialization');
                    document.dispatchEvent(new CustomEvent('requestWebChannelTransport', {
                        detail: { timestamp: Date.now() }
                    }));
                }
            }
        }, 500);
    })();
    """ % (qwebchannel_js, runtime_api_js)
    
    script.setSourceCode(wrapper_js)
    script.setName("chrome-runtime-api")
    script.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld)
    script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentCreation)
    script.setRunsOnSubFrames(True)
    
    return script
