#!/usr/bin/env python3
"""
DecentraBrowser DDNS Central Server Synchronization

This script enhances DecentraBrowser with two-way DDNS synchronization:
1. Adds domain availability checking before creation
2. Synchronizes the local DDNS with the central server at jcmc.serveminecraft.net
3. Implements automatic periodic syncing
4. Fixes domain creation issues

Usage:
    python ddns_central_sync.py
"""

import os
import sys
import shutil
import re
import json
import time
import threading

def backup_file(file_path):
    """Create a backup of a file before modifying it."""
    backup_path = file_path + '.sync.bak'
    print(f"Creating backup: {backup_path}")
    shutil.copy2(file_path, backup_path)
    return backup_path

def enhance_network_client(client_py):
    """Add central server domain check and sync to NetworkClient."""
    with open(client_py, 'r', encoding='utf-8') as f:
        content = f.read()
    
    # Check if functions already exist
    if "check_domain_availability" in content and "sync_domains_with_central_server" in content:
        print("Domain check and sync functions already exist in NetworkClient, skipping...")
        return True
    
    # Find a good place to add our new methods (before register_domain)
    register_domain_pattern = r'def register_domain\(self, domain\):'
    match = re.search(register_domain_pattern, content)
    
    if not match:
        print("Could not find register_domain method in client.py")
        return False
    
    insert_pos = match.start()
    
    # Create the new methods
    new_methods = """
    def check_domain_availability(self, domain):
        
        domain_name = domain
        if isinstance(domain, Domain):
            domain_name = domain.full_name
        
        # Query the central registration server
        try:
            # Construct the query URL
            query_url = "http://jcmc.serveminecraft.net/cent.php?check=" + domain_name
            
            # Send the request
            response = requests.get(query_url, timeout=10)
            
            if response.status_code == 200:
                # Parse the response
                try:
                    data = response.json()
                    if data.get('available') is True:
                        return True, "Domain is available"
                    else:
                        reason = data.get('reason', 'Domain is already taken')
                        return False, reason
                except json.JSONDecodeError:
                    # Check for simple text response
                    text = response.text.strip().lower()
                    if "available" in text:
                        return True, "Domain is available"
                    else:
                        return False, f"Domain is unavailable: {text}"
            else:
                logger.error(f"Error checking domain availability: HTTP {response.status_code}")
                return False, f"Error checking domain: HTTP {response.status_code}"
                
        except requests.RequestException as e:
            logger.error(f"Network error checking domain availability: {str(e)}")
            return False, f"Network error: {str(e)}"
        
        # Fallback (if we reach here, something unexpected happened)
        return False, "Could not determine domain availability"
    
    def sync_domains_with_central_server(self):
        
        try:
            # Get local domains from DDNS
            local_domains = self.ddns.list_entries()
            
            # Convert to format expected by central server
            domains_to_sync = {}
            for domain_name, entry in local_domains.items():
                domains_to_sync[domain_name] = {
                    'ip': entry['ip'],
                    'port': entry['port'],
                    'last_seen': entry.get('updated', time.time())
                }
            
            # Send to central server
            sync_url = "http://jcmc.serveminecraft.net/cent.php?sync=1"
            
            # Convert domains to JSON
            domains_json = json.dumps(domains_to_sync)
            
            # Send the request
            response = requests.post(
                sync_url,
                data={'domains': domains_json},
                timeout=15
            )
            
            if response.status_code == 200:
                try:
                    data = response.json()
                    if data.get('success'):
                        # Process returned domains
                        central_domains = data.get('domains', {})
                        domains_added = 0
                        
                        for domain_name, domain_info in central_domains.items():
                            if domain_name not in local_domains:
                                # Add to local DDNS
                                self.ddns.add_entry(
                                    domain_name,
                                    domain_info['ip'],
                                    domain_info['port']
                                )
                                domains_added += 1
                        
                        logger.info(f"Domain sync completed: {domains_added} domains added locally")
                        return True, f"Sync successful: {domains_added} domains added"
                    else:
                        error = data.get('error', 'Unknown error')
                        logger.error(f"Sync failed: {error}")
                        return False, f"Sync failed: {error}"
                except json.JSONDecodeError:
                    logger.error("Failed to parse sync response")
                    return False, "Failed to parse server response"
            else:
                logger.error(f"Sync request failed: HTTP {response.status_code}")
                return False, f"Sync failed: HTTP {response.status_code}"
                
        except Exception as e:
            logger.error(f"Error during domain sync: {str(e)}")
            return False, f"Sync error: {str(e)}"
    
    """
    
    # Insert the new methods
    updated_content = content[:insert_pos] + new_methods + content[insert_pos:]
    
    # Now update the register_domain method to check availability
    register_pattern = r'def register_domain\(self, domain\):.*?try:.*?response = requests\.post\('
    register_match = re.search(register_pattern, updated_content, re.DOTALL)
    
    if register_match:
        register_code = register_match.group(0)
        
        # Add domain availability check
        check_code = """def register_domain(self, domain):
        
        # First check if the domain is available
        availability_check, message = self.check_domain_availability(domain)
        if not availability_check:
            logger.error(f"Domain not available: {message}")
            return False, message
            
        try:
            response = requests.post("""
        
        updated_content = updated_content.replace(register_code, check_code)
    else:
        print("Could not find location to update register_domain method")
    
    # Write the updated content
    with open(client_py, 'w', encoding='utf-8') as f:
        f.write(updated_content)
    
    print(f"Enhanced NetworkClient with domain check and sync in {client_py}")
    return True

def enhance_ddns_class(ddns_py):
    """Enhance the DDNS class with synchronization capabilities."""
    with open(ddns_py, 'r', encoding='utf-8') as f:
        content = f.read()
    
    # Check if sync method already exists
    if "sync_with_central_server" in content:
        print("DDNS sync method already exists, skipping...")
        return True
    
    # Find the DDNS class methods section (before the end of the class)
    # Looking for the last method in the class
    last_method_pattern = r'def update_from_registry.*?(?=\n\n)'
    match = re.search(last_method_pattern, content, re.DOTALL)
    
    if not match:
        print("Could not find a good location to insert sync method in DDNS class")
        return False
    
    last_method = match.group(0)
    insert_pos = content.find(last_method) + len(last_method)
    
    # Create the sync method
    sync_method = """

    def sync_with_central_server(self):
        
        try:
            # Get all local domains
            local_domains = self.list_entries()
            
            # Convert to format expected by central server
            domains_to_sync = {}
            for domain_name, entry in local_domains.items():
                domains_to_sync[domain_name] = {
                    'ip': entry['ip'],
                    'port': entry['port'],
                    'updated': entry.get('updated', '')
                }
            
            # Send to central server
            import requests
            import json
            
            sync_url = "http://jcmc.serveminecraft.net/cent.php?sync=1"
            
            # Convert domains to JSON
            domains_json = json.dumps(domains_to_sync)
            
            # Send the request
            response = requests.post(
                sync_url,
                data={'domains': domains_json},
                timeout=15
            )
            
            if response.status_code == 200:
                try:
                    data = response.json()
                    if data.get('success'):
                        # Process returned domains
                        central_domains = data.get('domains', {})
                        domains_added = 0
                        
                        for domain_name, domain_info in central_domains.items():
                            if domain_name not in local_domains:
                                # Add to local DDNS
                                self.add_entry(
                                    domain_name,
                                    domain_info['ip'],
                                    domain_info['port']
                                )
                                domains_added += 1
                        
                        return True, f"Sync successful: {domains_added} domains added"
                    else:
                        error = data.get('error', 'Unknown error')
                        return False, f"Sync failed: {error}"
                except json.JSONDecodeError:
                    return False, "Failed to parse server response"
            else:
                return False, f"Sync failed: HTTP {response.status_code}"
                
        except Exception as e:
            return False, f"Sync error: {str(e)}"
"""
    
    # Insert the sync method
    updated_content = content[:insert_pos] + sync_method + content[insert_pos:]
    
    # Write the updated content
    with open(ddns_py, 'w', encoding='utf-8') as f:
        f.write(updated_content)
    
    print(f"Enhanced DDNS class with sync method in {ddns_py}")
    return True

def add_background_sync(app_py):
    """Add background sync task to client app."""
    with open(app_py, 'r', encoding='utf-8') as f:
        content = f.read()
    
    # Check if sync thread already exists
    if "DomainSyncThread" in content:
        print("Background sync thread already exists in app.py, skipping...")
        return True
    
    # Find imports section to add threading
    imports_pattern = r'(import os.*?import sys.*?import argparse)'
    match = re.search(imports_pattern, content, re.DOTALL)
    
    if not match:
        print("Could not find imports section in app.py")
        return False
    
    imports = match.group(1)
    updated_imports = imports + "\nimport threading\nimport time"
    
    # Create the sync thread class
    sync_thread_class = """

class DomainSyncThread(threading.Thread):
    
    
    def __init__(self, network_client):
        
        super().__init__(daemon=True)
        self.network_client = network_client
        self.running = False
        self.sync_interval = 300  # 5 minutes
    
    def run(self):
        
        self.running = True
        logger.info("Domain sync thread started")
        
        while self.running:
            try:
                # Perform sync
                logger.info("Performing domain sync with central server...")
                success, result = self.network_client.sync_domains_with_central_server()
                
                if success:
                    logger.info(f"Domain sync successful: {result}")
                else:
                    logger.error(f"Domain sync failed: {result}")
            except Exception as e:
                logger.error(f"Error in domain sync thread: {str(e)}")
            
            # Sleep for the sync interval
            for _ in range(self.sync_interval):
                if not self.running:
                    break
                time.sleep(1)
    
    def stop(self):
        
        self.running = False
        logger.info("Domain sync thread stopping...")
"""
    
    # Add the sync thread class
    updated_content = content.replace(imports, updated_imports)
    
    # Find the last class definition
    class_pattern = r'class RefreshDomainsThread.*?(?=\n\ndef main|\Z)'
    match = re.search(class_pattern, updated_content, re.DOTALL)
    
    if match:
        last_class = match.group(0)
        updated_content = updated_content.replace(last_class, last_class + sync_thread_class)
    else:
        print("Could not find a good location to insert sync thread class")
        return False
    
    # Add sync thread initialization to main function
    main_fn_pattern = r'def main\(\):.*?# Create and run application.*?app = QApplication\(sys\.argv\).*?main_window = MainWindow\((.*?)\)'
    match = re.search(main_fn_pattern, updated_content, re.DOTALL)
    
    if match:
        main_window_args = match.group(1)
        
        # Initialize sync thread after main window
        init_code = f"main_window = MainWindow({main_window_args})"
        sync_init_code = f"""main_window = MainWindow({main_window_args})
    
    # Start domain sync thread
    sync_thread = DomainSyncThread(network_client)
    sync_thread.start()"""
        
        updated_content = updated_content.replace(init_code, sync_init_code)
    else:
        print("Could not find main window initialization in main function")
        return False
    
    # Write the updated content
    with open(app_py, 'w', encoding='utf-8') as f:
        f.write(updated_content)
    
    print(f"Added background sync thread to {app_py}")
    return True

def update_create_domain_dialog(main_window_py):
    """Update domain creation dialog to check availability before creating."""
    with open(main_window_py, 'r', encoding='utf-8') as f:
        content = f.read()
    
    # Find the create_domain_dialog method
    dialog_pattern = r'def create_domain_dialog\(self\):.*?def create_domain\(\):.*?domain = Domain\('
    match = re.search(dialog_pattern, content, re.DOTALL)
    
    if not match:
        print("Could not find domain creation code in create_domain_dialog method")
        return False
    
    dialog_code = match.group(0)
    
    # Check if availability check already exists
    if "check_domain_availability" in dialog_code:
        print("Create domain dialog already includes availability check, skipping...")
        return True
    
    # Add domain availability check before creating domain
    check_code = """            # Create domain
            domain = Domain("""
    
    availability_check = """            # Check domain availability first
            temp_domain = Domain(
                prefix=prefix,
                main=main,
                suffix=suffix,
                ip=get_local_ip(),
                port=port
            )
            
            # Check with central server
            self.statusBar.showMessage(f"Checking if {temp_domain.full_name} is available...", 2000)
            availability, message = self.network_client.check_domain_availability(temp_domain)
            
            if not availability:
                QMessageBox.warning(dialog, "Domain Not Available", f"{message}")
                return
            
            # Domain is available, create it
            domain = Domain("""
    
    updated_dialog_code = dialog_code.replace(check_code, availability_check)
    updated_content = content.replace(dialog_code, updated_dialog_code)
    
    # Add a "Check Availability" button if not exists
    if "check_availability" not in content and "check_button" not in content:
        # Find the button creation in the dialog
        button_pattern = r'(# Create button\s*create_button = QPushButton\("Create Domain"\)\s*)'
        match = re.search(button_pattern, updated_content)
        
        if match:
            button_code = match.group(1)
            
            # Add check availability button
            check_button_code = """# Create buttons
            button_layout = QHBoxLayout()
            check_button = QPushButton("Check Availability")
            create_button = QPushButton("Create Domain")
            
            button_layout.addWidget(check_button)
            button_layout.addWidget(create_button)
            
            def check_availability():
                prefix = prefix_input.text()
                main = main_input.text()
                suffix = suffix_input.text()
                
                if not prefix or not main or not suffix:
                    QMessageBox.warning(dialog, "Error", "All domain name parts are required")
                    return
                
                # Create a temporary domain
                temp_domain = Domain(
                    prefix=prefix,
                    main=main,
                    suffix=suffix,
                    ip=get_local_ip(),
                    port=port_spin.value()
                )
                
                # Check availability
                self.statusBar.showMessage(f"Checking if {temp_domain.full_name} is available...", 2000)
                availability, message = self.network_client.check_domain_availability(temp_domain)
                
                if availability:
                    QMessageBox.information(dialog, "Domain Available", 
                                          f"The domain {temp_domain.full_name} is available!")
                else:
                    QMessageBox.warning(dialog, "Domain Not Available", message)
            
            check_button.clicked.connect(check_availability)
            """
            
            updated_content = updated_content.replace(button_code, check_button_code)
            
            # Update layout code
            layout_pattern = r'(# Add widgets to layout\s*layout\.addLayout\(name_layout\)\s*layout\.addWidget\(create_button\)\s*)'
            match = re.search(layout_pattern, updated_content)
            
            if match:
                layout_code = match.group(1)
                new_layout_code = """# Add widgets to layout
            layout.addLayout(name_layout)
            layout.addLayout(button_layout)
            """
                
                updated_content = updated_content.replace(layout_code, new_layout_code)
        else:
            print("Could not find button creation section")
    
    # Write the updated content
    with open(main_window_py, 'w', encoding='utf-8') as f:
        f.write(updated_content)
    
    print(f"Updated domain creation dialog in {main_window_py}")
    return True

def main():
    """Main function to implement DDNS synchronization with central server."""
    # Find DecentraBrowser installation directory
    base_dir = None
    possible_paths = [
        os.path.join(".", "decentrabrowser"),
        os.path.join("..", "decentrabrowser"),
        os.path.join(os.path.dirname(os.path.abspath(__file__)), "decentrabrowser")
    ]
    
    for path in possible_paths:
        if os.path.isdir(path):
            base_dir = path
            break
    
    if not base_dir:
        print("Error: Could not find DecentraBrowser directory.")
        print("Please run this script from the same directory as the 'decentrabrowser' folder.")
        return 1
    
    print(f"Found DecentraBrowser at: {base_dir}")
    
    # Target files
    client_py = os.path.join(base_dir, "client", "network", "client.py")
    ddns_py = os.path.join(base_dir, "shared", "ddns.py")
    app_py = os.path.join(base_dir, "client", "app.py")
    main_window_py = os.path.join(base_dir, "client", "gui", "main_window.py")
    
    required_files = [client_py, ddns_py, app_py, main_window_py]
    missing_files = [f for f in required_files if not os.path.exists(f)]
    
    if missing_files:
        print("Error: Required files not found:")
        for file in missing_files:
            print(f"  - {file}")
        return 1
    
    # Create backups
    for file in required_files:
        backup_file(file)
    
    # Enhance NetworkClient with domain check and sync
    enhance_network_client(client_py)
    
    # Enhance DDNS with sync method
    enhance_ddns_class(ddns_py)
    
    # Add background sync thread to client app
    add_background_sync(app_py)
    
    # Update domain creation dialog
    update_create_domain_dialog(main_window_py)
    
    print("\nDomain synchronization with central server added successfully!")
    print("\nThese changes include:")
    print("1. Domain availability checking with the central server before creation")
    print("2. Automatic periodic synchronization of domains with the central server")
    print("3. Two-way synchronization - local domains are sent to the server, server domains are added locally")
    print("4. Added Check Availability button to the domain creation dialog")
    
    print("\nPlease restart DecentraBrowser for the changes to take effect.")
    print("After restart, your DDNS will automatically sync with jcmc.serveminecraft.net/cent.php")
    
    return 0

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