#!/usr/bin/env python3
"""
DecentraBrowser Extractor with Command Fixes

This script extracts the DecentraBrowser codebase from the install.py document
and creates the appropriate directory structure and files.
It also automatically fixes the command handling issues.

Usage:
    python runit.py

The script will create all necessary directories and files in the current directory
and apply fixes for the /help, /visit, and /download commands.
"""

import os
import re
import sys
from pathlib import Path

def extract_file_sections(content):
    """Extract file sections from the content."""
    # Pattern to match section headers like:
    # #####################################################################
    # /decentrabrowser/shared/constants.py
    # #####################################################################
    pattern = r'#{5,}\s*\n#\s*(/[\w/.-]+\.[\w]+)\s*\n#{5,}\s*\n(.*?)(?=#{5,}\s*\n#\s*/|$)'
    
    # Find all matches using the pattern
    matches = re.findall(pattern, content, re.DOTALL)
    
    file_sections = {}
    for file_path, code in matches:
        # Clean up the code by removing triple quotes at the beginning and end if present
        code = code.strip()
        if code.startswith('"""') and code.endswith('"""'):
            code = code[3:-3].strip()
        file_sections[file_path] = code
    
    return file_sections

def create_directory_structure(base_dir):
    """Create the base directory structure for DecentraBrowser."""
    # Main directories
    Path(base_dir).mkdir(exist_ok=True)
    Path(os.path.join(base_dir, "central_server")).mkdir(exist_ok=True)
    Path(os.path.join(base_dir, "client")).mkdir(exist_ok=True)
    Path(os.path.join(base_dir, "client", "gui")).mkdir(exist_ok=True)
    Path(os.path.join(base_dir, "client", "network")).mkdir(exist_ok=True)
    Path(os.path.join(base_dir, "client", "security")).mkdir(exist_ok=True)
    Path(os.path.join(base_dir, "shared")).mkdir(exist_ok=True)
    Path(os.path.join(base_dir, "tests")).mkdir(exist_ok=True)
    Path(os.path.join(base_dir, "scripts")).mkdir(exist_ok=True)
    Path(os.path.join(base_dir, "docs")).mkdir(exist_ok=True)
    
    # Create __init__.py files to make directories importable Python packages
    init_files = [
        os.path.join(base_dir, "__init__.py"),
        os.path.join(base_dir, "central_server", "__init__.py"),
        os.path.join(base_dir, "client", "__init__.py"),
        os.path.join(base_dir, "client", "gui", "__init__.py"),
        os.path.join(base_dir, "client", "network", "__init__.py"),
        os.path.join(base_dir, "client", "security", "__init__.py"),
        os.path.join(base_dir, "shared", "__init__.py"),
        os.path.join(base_dir, "tests", "__init__.py"),
    ]
    
    for init_file in init_files:
        with open(init_file, 'w') as f:
            f.write('"""' + os.path.basename(os.path.dirname(init_file)) + ' package."""\n')

def write_file_sections(file_sections, base_dir="."):
    """Write extracted file sections to their respective files."""
    # Create counts to track progress
    files_created = 0
    
    # Create each file
    for file_path, content in file_sections.items():
        # Strip leading slash to make the path relative
        if file_path.startswith('/'):
            file_path = file_path[1:]
        
        # Create full path including base directory
        full_path = os.path.join(base_dir, file_path)
        
        # Create the directory structure for this file if it doesn't exist
        directory = os.path.dirname(full_path)
        os.makedirs(directory, exist_ok=True)
        
        # Write content to file
        with open(full_path, 'w') as f:
            f.write(content)
        
        files_created += 1
        print(f"Created: {full_path}")
    
    # Also create a requirements.txt file
    requirements = """# Core dependencies
cryptography>=3.4.0
PyQt5>=5.15.4
flask>=2.0.1
requests>=2.25.1
pyOpenSSL>=20.0.1
pycryptodome>=3.10.1
click>=8.0.0
python-dotenv>=0.19.0
tinydb>=4.5.0
marshmallow>=3.13.0
aiohttp>=3.7.4
sqlalchemy>=1.4.23
pynacl>=1.4.0
qrcode>=7.3.1
pillow>=8.3.1
PyQtWebEngine>=5.15.4
colorama>=0.4.4
"""
    with open(os.path.join(base_dir, "requirements.txt"), 'w') as f:
        f.write(requirements)
    
    print(f"Created: {os.path.join(base_dir, 'requirements.txt')}")
    files_created += 1
    
    return files_created

def fix_constants_py(base_dir):
    """Fix the constants.py file to include missing commands."""
    constants_file = os.path.join(base_dir, "shared", "constants.py")
    
    if not os.path.exists(constants_file):
        print(f"Error: {constants_file} does not exist")
        return False
    
    # Create backup
    backup_file = constants_file + ".bak"
    with open(constants_file, 'r') as src:
        with open(backup_file, 'w') as dst:
            dst.write(src.read())
    
    # Read file content
    with open(constants_file, 'r') as f:
        content = f.read()
    
    # Update command constants section
    command_constants = """# Command constants
CMD_SOURCES = '/sources'
CMD_DOWNLOAD = '/download'
CMD_P2P = '/p2p'
CMD_VISIT = '/visit'  # Command for visiting domains
CMD_HELP = '/help'    # Command for displaying help
"""
    
    # Replace command constants section
    pattern = r'# Command constants.*?(?=\n\n)'
    if re.search(pattern, content, re.DOTALL):
        new_content = re.sub(pattern, command_constants, content, flags=re.DOTALL)
    else:
        # If not found, add it
        new_content = content + "\n" + command_constants
    
    # Write updated content
    with open(constants_file, 'w') as f:
        f.write(new_content)
    
    print(f"Fixed: {constants_file}")
    return True

def fix_network_client_py(base_dir):
    """Fix the network_client.py file to update process_command method."""
    client_file = os.path.join(base_dir, "client", "network", "client.py")
    
    if not os.path.exists(client_file):
        print(f"Error: {client_file} does not exist")
        return False
    
    # Create backup
    backup_file = client_file + ".bak"
    with open(client_file, 'r') as src:
        with open(backup_file, 'w') as dst:
            dst.write(src.read())
    
    # Read file content
    with open(client_file, 'r') as f:
        content = f.read()
    
    # Update imports
    imports_pattern = r'from \.\.\.shared\.constants import.*'
    imports_replacement = """from ...shared.constants import (
    DEFAULT_PORT, DEFAULT_CENTRAL_SERVER_PORT, DEFAULT_REGISTRY_REFRESH,
    CMD_SOURCES, CMD_DOWNLOAD, CMD_P2P, CMD_VISIT, CMD_HELP
)"""
    
    if re.search(imports_pattern, content):
        content = re.sub(imports_pattern, imports_replacement, content)
    
    # New process_command method
    process_command_method = """
    def process_command(self, command):
        
        parts = command.strip().split()
        if not parts:
            return False, "Empty command"
        
        cmd = parts[0].lower()
        
        # Process /help command
        if cmd == '/help':
            help_text = {
                '/sources': "List all available domains",
                '/download': "Download a file from a domain. Usage: /download file_path domain_name [save_dir]",
                '/p2p': "Send a P2P message to a domain. Usage: /p2p domain_name timer message",
                '/visit': "Visit a domain. Usage: /visit domain_name [seconds]",
                '/help': "Show this help message"
            }
            
            return True, {
                'command': '/help',
                'help': help_text
            }
        
        # Process /sources command
        elif cmd == '/sources':
            success, result = self.get_sources(force_refresh=True)
            if success:
                domains = result
                return True, {
                    'command': '/sources',
                    'count': len(domains),
                    'domains': [d.to_dict() for d in domains.values()]
                }
            else:
                return False, result
        
        # Process /download command
        elif cmd == '/download':
            if len(parts) < 3:
                return False, "Usage: /download file_path domain_name [save_dir]"
            
            file_path = parts[1]
            domain_name = parts[2]
            save_dir = parts[3] if len(parts) > 3 else os.path.expanduser("~/Downloads")
            
            success, result = self.download_file(domain_name, file_path, save_dir)
            if success:
                download_id = result
                return True, {
                    'command': '/download',
                    'download_id': download_id,
                    'domain': domain_name,
                    'file': file_path
                }
            else:
                return False, result
        
        # Process /p2p command
        elif cmd == '/p2p':
            if len(parts) < 4:
                return False, "Usage: /p2p domain_name timer message"
            
            domain_name = parts[1]
            try:
                timer = int(parts[2])
            except ValueError:
                return False, "Timer must be a number"
            
            message = ' '.join(parts[3:])
            
            success, result = self.p2p_send_message(domain_name, message, timer)
            if success:
                return True, {
                    'command': '/p2p',
                    'domain': domain_name,
                    'timer': timer,
                    'message': message
                }
            else:
                return False, result
                
        # Process /visit command - for browser navigation
        elif cmd == '/visit':
            if len(parts) < 2:
                return False, "Usage: /visit domain_name [seconds]"
            
            domain_name = parts[1]
            visit_seconds = 0
            if len(parts) >= 3:
                try:
                    visit_seconds = int(parts[2])
                except ValueError:
                    return False, "Seconds must be a number"
            
            # Return information about the visit command
            # The actual navigation will be handled by the main window
            return True, {
                'command': '/visit',
                'domain': domain_name,
                'seconds': visit_seconds
            }
        
        else:
            return False, f"Unknown command: {command}"
    """
    
    # Replace process_command method
    process_command_pattern = r'def process_command\(self, command\):.*?return False, f"Unknown command: {command}".*?(?=\n    def|\n\n|$)'
    
    if re.search(process_command_pattern, content, re.DOTALL):
        content = re.sub(process_command_pattern, process_command_method.rstrip(), content, flags=re.DOTALL)
    else:
        # If not found, add before the last method
        last_method_pattern = r'(def [^(]+\([^)]+\):.*?)$'
        if re.search(last_method_pattern, content, re.DOTALL):
            content = re.sub(last_method_pattern, process_command_method + r'\n\1', content, flags=re.DOTALL)
        else:
            # If no method found, add at the end of the class
            content += process_command_method
    
    # Write updated content
    with open(client_file, 'w') as f:
        f.write(content)
    
    print(f"Fixed: {client_file}")
    return True

def fix_main_window_py(base_dir):
    """Fix the main_window.py file to add help dialog and update process_url_command."""
    window_file = os.path.join(base_dir, "client", "gui", "main_window.py")
    
    if not os.path.exists(window_file):
        print(f"Error: {window_file} does not exist")
        return False
    
    # Create backup
    backup_file = window_file + ".bak"
    with open(window_file, 'r') as src:
        with open(backup_file, 'w') as dst:
            dst.write(src.read())
    
    # Read file content
    with open(window_file, 'r') as f:
        content = f.read()
    
    # Check if there's already a process_url_command method
    url_command_exists = 'def process_url_command' in content
    help_dialog_exists = 'def show_help_dialog' in content
    
    # Add help dialog method if it doesn't exist
    if not help_dialog_exists:
        help_dialog_method = """
    def show_help_dialog(self, help_data):
        
        dialog = QDialog(self)
        dialog.setWindowTitle("DecentraBrowser Commands")
        dialog.setMinimumSize(500, 300)
        
        layout = QVBoxLayout(dialog)
        
        # Add heading
        heading = QLabel("Available Commands")
        heading.setStyleSheet("font-size: 16px; font-weight: bold; margin-bottom: 10px;")
        layout.addWidget(heading)
        
        # Create scrollable text area
        help_text = QTextEdit()
        help_text.setReadOnly(True)
        
        # Format help text
        formatted_text = ""
        for cmd, description in help_data.items():
            formatted_text += f"<p><b>{cmd}</b>: {description}</p>"
        
        help_text.setHtml(formatted_text)
        layout.addWidget(help_text)
        
        # Add close button
        button_box = QHBoxLayout()
        close_button = QPushButton("Close")
        close_button.clicked.connect(dialog.accept)
        button_box.addStretch()
        button_box.addWidget(close_button)
        layout.addLayout(button_box)
        
        # Show dialog
        dialog.exec_()
    """
        
        # Add this method before the last closing brace of the class
        last_brace_pos = content.rfind('}')
        if last_brace_pos > 0:
            content = content[:last_brace_pos] + help_dialog_method + "\n" + content[last_brace_pos:]
        else:
            # If no closing brace found, add at the end
            content += help_dialog_method
    
    # Add or update process_url_command method
    if url_command_exists:
        # Update existing method to handle /help and /visit
        process_url_command_method = """
    def process_url_command(self, command):
        
        parts = command.strip().split()
        cmd = parts[0].lower()
        
        # Handle help command
        if cmd == '/help':
            # Process help command
            success, result = self.network_client.process_command(command)
            if success and isinstance(result, dict) and result.get('command') == '/help':
                self.show_help_dialog(result.get('help', {}))
            return
        
        # Handle visit command
        if cmd == '/visit' and len(parts) >= 2:
            domain_name = parts[1]
            # Optional time parameter
            visit_seconds = 0
            if len(parts) >= 3:
                try:
                    visit_seconds = int(parts[2])
                except ValueError:
                    pass
            
            # Visit the domain
            self.navigate_to_domain(domain_name)
            
            # If timer is set, schedule return to previous domain
            if visit_seconds > 0:
                prev_domain = self.current_domain
                QTimer.singleShot(visit_seconds * 1000, lambda: self.navigate_to_domain(prev_domain))
                
                # Show message about timer
                self.statusBar.showMessage(f"Visiting {domain_name} for {visit_seconds} seconds", 3000)
            
            return
        
        # Forward other commands to the command processor
        success, result = self.network_client.process_command(command)
        
        # Show result in status bar
        if success:
            if isinstance(result, dict):
                message = f"Command {cmd} executed successfully"
            else:
                message = str(result)
            self.statusBar.showMessage(message, 5000)
        else:
            self.statusBar.showMessage(f"Error: {result}", 5000)
        
        # Switch to command tab to show full result
        self.tabs.setCurrentIndex(2)  # Command tab
        
        # Add command to history
        self.txt_command_output.append(f"> {command}")
        
        # Format and add result to output
        if success:
            if isinstance(result, dict):
                if result.get('command') == '/sources':
                    self.txt_command_output.append(f"Found {result['count']} domains")
                    for domain in result['domains'][:5]:  # Show first 5
                        self.txt_command_output.append(f"  {domain['full_name']} - {domain.get('title', '')}")
                    if result['count'] > 5:
                        self.txt_command_output.append(f"  ...and {result['count'] - 5} more")
                
                elif result.get('command') == '/download':
                    self.txt_command_output.append(f"Downloading {result['file']} from {result['domain']}")
                    self.txt_command_output.append(f"Download ID: {result['download_id']}")
                
                elif result.get('command') == '/p2p':
                    self.txt_command_output.append(f"Message sent to {result['domain']}")
                    self.txt_command_output.append(f"Message: {result['message']}")
                    self.txt_command_output.append(f"Timer: {result['timer']} seconds")
            else:
                self.txt_command_output.append(str(result))
        else:
            self.txt_command_output.append(f"Error: {result}")
        
        # Scroll to bottom
        self.txt_command_output.moveCursor(QTextCursor.End)
    """
        
        # Replace the existing method
        process_url_pattern = r'def process_url_command\(self, command\):.*?(?=\n    def |\n\n|$)'
        content = re.sub(process_url_pattern, process_url_command_method.rstrip(), content, flags=re.DOTALL)
    else:
        # Add method if not exists (unlikely, but just in case)
        process_url_command_method = """
    def process_url_command(self, command):
        
        parts = command.strip().split()
        cmd = parts[0].lower()
        
        # Handle help command
        if cmd == '/help':
            # Process help command
            success, result = self.network_client.process_command(command)
            if success and isinstance(result, dict) and result.get('command') == '/help':
                self.show_help_dialog(result.get('help', {}))
            return
        
        # Handle visit command
        if cmd == '/visit' and len(parts) >= 2:
            domain_name = parts[1]
            # Optional time parameter
            visit_seconds = 0
            if len(parts) >= 3:
                try:
                    visit_seconds = int(parts[2])
                except ValueError:
                    pass
            
            # Visit the domain
            self.navigate_to_domain(domain_name)
            
            # If timer is set, schedule return to previous domain
            if visit_seconds > 0:
                prev_domain = self.current_domain
                QTimer.singleShot(visit_seconds * 1000, lambda: self.navigate_to_domain(prev_domain))
                
                # Show message about timer
                self.statusBar.showMessage(f"Visiting {domain_name} for {visit_seconds} seconds", 3000)
            
            return
        
        # Forward other commands to the command processor
        success, result = self.network_client.process_command(command)
        
        # Show result in status bar
        if success:
            if isinstance(result, dict):
                message = f"Command {cmd} executed successfully"
            else:
                message = str(result)
            self.statusBar.showMessage(message, 5000)
        else:
            self.statusBar.showMessage(f"Error: {result}", 5000)
        
        # Switch to command tab to show full result
        self.tabs.setCurrentIndex(2)  # Command tab
        
        # Add command to history
        self.txt_command_output.append(f"> {command}")
        
        # Format and add result to output
        if success:
            if isinstance(result, dict):
                if result.get('command') == '/sources':
                    self.txt_command_output.append(f"Found {result['count']} domains")
                    for domain in result['domains'][:5]:  # Show first 5
                        self.txt_command_output.append(f"  {domain['full_name']} - {domain.get('title', '')}")
                    if result['count'] > 5:
                        self.txt_command_output.append(f"  ...and {result['count'] - 5} more")
                
                elif result.get('command') == '/download':
                    self.txt_command_output.append(f"Downloading {result['file']} from {result['domain']}")
                    self.txt_command_output.append(f"Download ID: {result['download_id']}")
                
                elif result.get('command') == '/p2p':
                    self.txt_command_output.append(f"Message sent to {result['domain']}")
                    self.txt_command_output.append(f"Message: {result['message']}")
                    self.txt_command_output.append(f"Timer: {result['timer']} seconds")
            else:
                self.txt_command_output.append(str(result))
        else:
            self.txt_command_output.append(f"Error: {result}")
        
        # Scroll to bottom
        self.txt_command_output.moveCursor(QTextCursor.End)
    """
        
        # Add this method before the last closing brace of the class
        last_brace_pos = content.rfind('}')
        if last_brace_pos > 0:
            content = content[:last_brace_pos] + process_url_command_method + "\n" + content[last_brace_pos:]
        else:
            # If no closing brace found, add at the end
            content += process_url_command_method
    
    # Write updated content
    with open(window_file, 'w') as f:
        f.write(content)
    
    print(f"Fixed: {window_file}")
    return True

def apply_command_fixes(base_dir):
    """Apply all command fixes."""
    print("\nApplying command fixes...")
    
    # Fix constants.py
    constants_fixed = fix_constants_py(base_dir)
    
    # Fix network_client.py
    client_fixed = fix_network_client_py(base_dir)
    
    # Fix main_window.py
    window_fixed = fix_main_window_py(base_dir)
    
    # Print summary
    print("\nCommand Fixes Summary:")
    print(f"  Constants.py fixes: {'Applied' if constants_fixed else 'Failed'}")
    print(f"  NetworkClient.py fixes: {'Applied' if client_fixed else 'Failed'}")
    print(f"  MainWindow.py fixes: {'Applied' if window_fixed else 'Failed'}")
    
    return constants_fixed and client_fixed and window_fixed

def main():
    """Main function to extract and create the DecentraBrowser files."""
    print("DecentraBrowser Extractor with Command Fixes")
    print("===========================================")
    
    try:
        # Get the document content from install.py
        if os.path.exists("install.py"):
            with open("install.py", 'r') as f:
                content = f.read()
        else:
            print("The file 'install.py' does not exist in the current directory.")
            print("Please run this script in the same directory as install.py.")
            return 1
        
        # Extract file sections
        print("Extracting code sections...")
        file_sections = extract_file_sections(content)
        
        # Create base directory 'decentrabrowser'
        base_dir = "."
        print(f"Creating directory structure in '{base_dir}'...")
        create_directory_structure(base_dir)
        
        # Write file sections to their respective files
        print("Creating files...")
        files_created = write_file_sections(file_sections, base_dir)
        
        # Apply fixes for command handling
        fixes_applied = apply_command_fixes(base_dir)
        
        print("\nExtraction and Fixes Complete!")
        print(f"Created {files_created} files in the '{base_dir}' directory.")
        print(f"Command handling fixes: {'Successfully applied' if fixes_applied else 'Partially applied or failed'}")
        print("\nYou can now run the DecentraBrowser application:")
        print("1. Install dependencies: pip install -r requirements.txt")
        print("2. Run the client: python -m decentrabrowser.client.app")
        print("3. Run the central server: python -m decentrabrowser.central_server.app")
    
    except Exception as e:
        print(f"Error: {str(e)}")
        return 1
    
    return 0

if __name__ == "__main__":
    sys.exit(main())