
print("[DEBUG] mining_test.py script started.")
import sys
import os
import time
import hashlib
import multiprocessing
import threading
import psutil
import signal
from concurrent.futures import ThreadPoolExecutor
from decimal import Decimal
from typing import Dict, Any, Tuple, List
from datetime import datetime
import struct
import json
from queue import Queue
import logging

# Import electron speed calculation
from electron_speed import max_switch_freq, drift_velocity, transit_time

# Import custom modules
from mining_config import MiningConfig
from cpu.enhanced_cpu import EnhancedCPU, VirtualCPU, CPUGroupType, RegisterState, ELECTRON_FREQ
from json_storage_manager import JSONStorageManager

# Set up logging
logging.basicConfig(
    filename='mining_stats.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

# Global stats tracking
class MiningStats:
    def __init__(self):
        self.stats_lock = threading.Lock()
        self.per_core_hashes = {}
        self.total_hashes = 0
        self.start_time = time.time()
        self.last_update_time = time.time()
        self.last_hash_count = 0
        self.current_hashrate = 0
        self.theoretical_max = 0
    
    def set_theoretical_max(self, max_rate: float):
        self.theoretical_max = max_rate
    
    def update_stats(self, core_id: int, hashes: int):
        with self.stats_lock:
            if core_id not in self.per_core_hashes:
                self.per_core_hashes[core_id] = 0
            self.per_core_hashes[core_id] += hashes
            self.total_hashes += hashes
            
            # Update current hash rate
            current_time = time.time()
            time_delta = current_time - self.last_update_time
            if time_delta >= 1.0:  # Update rate every second
                hash_delta = self.total_hashes - self.last_hash_count
                self.current_hashrate = hash_delta / time_delta
                self.last_hash_count = self.total_hashes
                self.last_update_time = current_time
    
    def get_hashrate(self) -> Dict[str, Any]:
        with self.stats_lock:
            elapsed = time.time() - self.start_time
            avg_rate = self.total_hashes / elapsed if elapsed > 0 else 0
            per_core_rates = {
                f"core_{k}": v / elapsed if elapsed > 0 else 0
                for k, v in self.per_core_hashes.items()
            }
            
            efficiency = (self.current_hashrate / self.theoretical_max * 100) if self.theoretical_max > 0 else 0
            
            return {
                "current": self.current_hashrate,
                "average": avg_rate,
                "per_core": per_core_rates,
                "total_hashes": self.total_hashes,
                "theoretical_max": self.theoretical_max,
                "efficiency": efficiency
            }

# Initialize global mining stats
mining_stats = MiningStats()

# Global stop flag for graceful shutdown
stop_mining = False

def signal_handler(signum, frame):
    """Handle Ctrl+C gracefully"""
    global stop_mining
    print("\nShutting down mining operations gracefully...")
    stop_mining = True

# Register signal handler
signal.signal(signal.SIGINT, signal_handler)

# Virtual CPU configuration
DEFAULT_TOTAL_CPUS = 1000  # 1k virtual CPUs
DEFAULT_CORES_PER_CPU = 1000  # 1k cores per CPU
DEFAULT_THREADS_PER_CORE = 1000  # 1k threads per core

def init_mining_config():
    """Initialize mining configuration with virtual CPU parameters"""
    config = MiningConfig(
        total_virtual_cpus=DEFAULT_TOTAL_CPUS,
        virtual_cores_per_cpu=DEFAULT_CORES_PER_CPU,
        virtual_threads_per_core=DEFAULT_THREADS_PER_CORE
    )
    config.cores_per_cpu = config.virtual_cores_per_cpu
    config.threads_per_core = config.virtual_threads_per_core
    config.total_cpus = config.total_virtual_cpus
    return config

# Define helper functions and classes before they are used


def sha256d(header: bytes) -> bytes:
    """Perform Bitcoin\'s double SHA256 with verification"""
    if not header:
        raise ValueError("Empty header")
    
    # First SHA256
    first_hash = hashlib.sha256(header).digest()
    if len(first_hash) != 32:
        raise ValueError("Invalid first hash length")
        
    # Second SHA256
    final_hash = hashlib.sha256(first_hash).digest()
    if len(final_hash) != 32:
        raise ValueError("Invalid final hash length")
        
    return final_hash

# Initialize global JSON storage manager
storage = JSONStorageManager(base_path="data")

def mine_on_virtual_cpu(header: bytes, target: bytes, nonce_start: int, nonce_range: int, mining_stats: MiningStats, config: MiningConfig = None) -> None:
    """Main mining function that distributes work across virtual CPUs"""
    global stop_mining
    if config is None:
        config = init_mining_config()
    
    print(f"\nInitializing Virtual CPU Mining System:")
    print(f"- Total Virtual CPUs: {config.total_virtual_cpus:,}")
    print(f"- Cores per CPU: {config.virtual_cores_per_cpu:,}")
    print(f"- Threads per Core: {config.virtual_threads_per_core:,}")
    
    # Calculate theoretical max hash rate based on electron speed
    max_freq = max_switch_freq()
    theoretical_hashrate = max_freq * config.total_virtual_cpus * config.virtual_cores_per_cpu * config.virtual_threads_per_core
    mining_stats.set_theoretical_max(theoretical_hashrate)
    
    print(f"\nTheoretical Performance:")
    print(f"- Max Switch Frequency: {max_freq:.2e} Hz")
    print(f"- Theoretical Hash Rate: {theoretical_hashrate:.2e} H/s")
    print("\nPress Ctrl+C to stop mining gracefully")
    
    # Create virtual CPUs with proper group type
    vcpus = []
    for cpu_id in range(config.total_virtual_cpus):
        vcpu = VirtualCPU(cpu_id=cpu_id, group_type=CPUGroupType.MINING)
        vcpu.initialize_registers()
        vcpus.append(vcpu)
    
    # Initialize pool with virtual CPUs
    pool = multiprocessing.Pool(config.total_virtual_cpus)
    
    try:
        # Prepare batch arguments
        batch_size = nonce_range // config.total_virtual_cpus
        pool_args = []
        
        for cpu_id in range(config.total_virtual_cpus):
            start = nonce_start + (cpu_id * batch_size)
            args_chunk = (header, target.hex(), start, batch_size)
            cpu_range = [cpu_id]  # CPU affinity
            pool_args.append((args_chunk, cpu_range, config))
            
        print("\nStarting virtual CPU mining operations...")
        
        # Start mining in parallel
        while not stop_mining:
            results = pool.map(process_mining_batch, pool_args)
            
            # Process results
            for result in results:
                if result[3] or stop_mining:  # If block found or stopping
                    if result[3]:
                        print(f"\nBlock found! Hash: {result[0].hex()}")
                    break
                
    finally:
        pool.close()
        pool.join()

def print_mining_stats():
    """Print real-time mining statistics to console"""
    global stop_mining
    while not stop_mining:
        stats = mining_stats.get_hashrate()
        os.system('cls' if os.name == 'nt' else 'clear')
        print("\n=== Mining Statistics ===")
        print(f"Current Hash Rate:     {stats['current']:,.2f} H/s")
        print(f"Average Hash Rate:     {stats['average']:,.2f} H/s")
        print(f"Theoretical Maximum:   {stats['theoretical_max']:.2e} H/s")
        print(f"Efficiency:           {stats['efficiency']:.2f}%")
        print(f"Total Hashes:         {stats['total_hashes']:,}")
        
        print("\nPer-Core Hash Rates (Average):")
        core_rates = stats['per_core']
        if len(core_rates) > 10:
            # Show top 5 and bottom 5 cores if there are many
            sorted_cores = sorted(core_rates.items(), key=lambda x: x[1], reverse=True)
            top_5 = sorted_cores[:5]
            bottom_5 = sorted_cores[-5:]
            
            print("\nTop 5 Performing Cores:")
            for core, rate in top_5:
                print(f"{core}: {rate:,.2f} H/s")
                
            print("\nBottom 5 Performing Cores:")
            for core, rate in bottom_5:
                print(f"{core}: {rate:,.2f} H/s")
        else:
            for core, rate in core_rates.items():
                print(f"{core}: {rate:,.2f} H/s")
        
        time.sleep(1)  # Update every second

def process_nonce_range(args: tuple) -> Tuple[int, int]:
    """Process a specific range of nonces on a single thread"""
    header, target, start_nonce, nonce_count, thread_id = args
    header = bytearray(header)
    nonce_index = len(header) - 4
    hashes_computed = 0
    
    for nonce in range(start_nonce, start_nonce + nonce_count):
        struct.pack_into(">I", header, nonce_index, nonce)
        hash_result = sha256d(header)
        if hash_result < target:
            return (nonce, hashes_computed)
        hashes_computed += 1
        
        # Update stats every 10000 hashes
        if hashes_computed % 10000 == 0:
            mining_stats.update_stats(thread_id, 10000)
            
    return (None, hashes_computed)

def process_mining_chunk(args: tuple, config: MiningConfig) -> tuple:
    """Process a chunk of nonces using virtual CPU system"""
    header, target_hex, start_nonce, batch_size = args
    header = bytearray(header)
    target = bytes.fromhex(target_hex) if isinstance(target_hex, str) else bytes(target_hex)
    
    # Initialize virtual CPU for this chunk
    vcpu = VirtualCPU(
        cpu_id=multiprocessing.current_process().pid,
        cores=config.cores_per_cpu,
        threads_per_core=config.threads_per_core
    )
    
    # Calculate nonce position
    nonce_index = len(header) - 4  # Last 4 bytes for nonce
    
    # Pack/unpack helpers for the nonce
    def pack_nonce(n: int) -> bytes:
        return struct.pack(">I", n)
    
    # Create thread pool for this CPU core
    num_threads = threading.active_count()
    thread_batch_size = batch_size // num_threads
    
    with ThreadPoolExecutor(max_workers=num_threads) as executor:
        thread_args = [
            (header, target, start_nonce + i * thread_batch_size, 
             thread_batch_size, cpu_core * num_threads + i)
            for i in range(num_threads)
        ]
        
        results = list(executor.map(process_nonce_range, thread_args))
    
    # Process results
    total_hashes = sum(hashes for _, hashes in results)
    found_nonces = [nonce for nonce, _ in results if nonce is not None]
    
    # Log progress
    hashrate = total_hashes / (time.time() - mining_stats.start_time)
    logging.info(f"Core {cpu_core}: Processed {total_hashes} hashes at {hashrate:.2f} H/s")
    pack_nonce = struct.Struct("!I").pack
    best_hash = b'\xff' * 32
    best_nonce = None
    
    # Calculate nonces per thread based on total virtual resources
    total_virtual_units = config.total_virtual_cpus * config.virtual_cores_per_cpu * config.virtual_threads_per_core
    nonces_per_thread = max(1, batch_size // total_virtual_units)
    
    # Use electron-speed core for SHA256 computation
    core_state = RegisterState(register_id=0)
    core_state.write(ELECTRON_FREQ)  # Set electron frequency
    
    total_hashes_in_chunk = 0

    for cpu_id in range(config.total_virtual_cpus):
        # cpu = EnhancedCPU(cpu_id=cpu_id, group_type=CPUGroupType.COMPUTATION) # Not directly used here
        
        for core_id in range(config.virtual_cores_per_cpu):
            for thread_id in range(config.virtual_threads_per_core):
                # Calculate the starting nonce for this specific thread
                thread_offset = (cpu_id * config.virtual_cores_per_cpu * config.virtual_threads_per_core) + \
                                (core_id * config.virtual_threads_per_core) + \
                                thread_id
                thread_start_nonce = start_nonce + (thread_offset * nonces_per_thread)
                
                hashes_processed_by_thread = 0
                for nonce_offset in range(nonces_per_thread):
                    nonce = thread_start_nonce + nonce_offset
                    header[nonce_index:] = pack_nonce(nonce)
                    
                    current_hash = sha256d(header)
                    hashes_processed_by_thread += 1
                    total_hashes_in_chunk += 1

                    if current_hash < best_hash:
                        best_hash = current_hash
                        best_nonce = nonce

                    if current_hash < target:
                        found_result = {
                            'cpu_id': cpu_id,
                            'core_id': core_id,
                            'thread_id': thread_id,
                            'nonce': nonce,
                            'hash': current_hash.hex(),
                            'timestamp': time.time()
                        }
                        storage.add_mining_result(found_result)
                        storage.record_hash(cpu_id, core_id, thread_id, hashes_processed_by_thread) # Record hashes for the found block
                        return (best_hash, best_nonce, total_hashes_in_chunk, True)
                
                # Record hashes processed by this thread if no block was found
                if hashes_processed_by_thread > 0:
                    storage.record_hash(cpu_id, core_id, thread_id, hashes_processed_by_thread)

    return (best_hash, best_nonce, total_hashes_in_chunk, False)

# Handle Ctrl+C gracefully
def signal_handler(signum, frame):
    """Handle interrupt signal"""
    print("\nInterrupt received, finishing up...")
    stats = storage.get_mining_stats()
    MiningStats().show_final_summary(stats)
    sys.exit(0)

# Register signal handler
import signal
signal.signal(signal.SIGINT, signal_handler)

# Project root path setup
project_root = os.path.dirname(os.path.abspath(__file__))
if project_root not in sys.path:
    sys.path.insert(0, project_root)

class MiningStats:
    """Track mining statistics with JSON storage"""
    def __init__(self):
        # Initialize timing
        self.start_time = time.time()
        self.last_update = time.time()
        
        # Set up logging with immediate flush
        self.log_file = open(os.path.join(project_root, 'mining_stats.log'), 'w')
        self.log_file.write(f"=== Mining Test Started at {datetime.now()} ===\n\n")
        self.log_file.flush()
        
        # Initialize JSON stats
        self._init_stats()
        
    def _init_stats(self):
        """Initialize JSON stats structure"""
        # Ensure system_config is initialized
        storage.update_system_config({
            'start_time': self.start_time,
            'initialized': True,
            'total_cpus': 1000,
            'cores_per_cpu': 600000,
            'threads_per_core': 400000
        })
        
        # Ensure hash_stats is initialized
        if not storage.files['hash_stats'].exists() or storage._load_json(storage.files['hash_stats']) == {}:
            storage._save_json(storage.files['hash_stats'], {
                'total_hashes': 0,
                'hashes_per_cpu': {},
                'hashes_per_core': {},
                'start_time': self.start_time
            })
        
    def log_stats(self, elapsed: float):
        """Log current statistics from JSON storage with terminal output"""
        stats = storage.get_mining_stats()
        print(f"[DEBUG-log_stats] Retrieved stats: {stats}")
        
        if not stats:
            stats = {"cpu_stats": {}, "hash_stats": {"total_hashes": 0, "start_time": self.start_time}}
        
        cpu_stats = stats.get("cpu_stats", {})
        hash_stats = stats.get("hash_stats", {"total_hashes": 0, "start_time": self.start_time, "hashes_per_cpu": {}, "hashes_per_core": {}})
        print(f"[DEBUG-log_stats] Processed hash_stats: {hash_stats}")
        
        now = time.time()
        runtime = now - hash_stats.get("start_time", self.start_time)

        total_hashrate = hash_stats['total_hashes'] / runtime if runtime > 0 else 0
        
        cpu_rates = []
        sorted_cpus = sorted(
            hash_stats.get('hashes_per_cpu', {}).items(),
            key=lambda x: int(x[0]), # Changed x[1] to x[0] as cpu_id is string key
            reverse=True
        )[:5]
        
        for cpu_id, cpu_hashes in sorted_cpus:
            rate = cpu_hashes / runtime if runtime > 0 else 0
            cpu_rates.append(f"CPU {cpu_id}: {rate:,.2f} H/s ({cpu_hashes:,} total)")
            
        time_remaining = max(0, 120 - runtime)
        progress = min(100, (runtime / 120) * 100)
            
        stats_output = (
            f"\n{'=' * 50}\n"
            f"Mining Progress: {progress:.1f}% ({time_remaining:.1f}s remaining)\n"
            f"{'=' * 50}\n"
            f"Overall Performance:\n"
            f"  Total Hashrate: {total_hashrate:,.2f} H/s\n"
            f"  Total Hashes: {hash_stats['total_hashes']:,}\n"
            f"  Runtime: {runtime:.1f}s / 120s\n"
            f"Best Result:\n"
            f"  Hash: {stats.get('best_result', {}).get('hash', 'N/A')}\n"
            f"  Nonce: {stats.get('best_result', {}).get('nonce', 'N/A')}\n"
            f"Top 5 CPU Performance:\n"
            + "\n".join(f"  {rate}" for rate in cpu_rates)
            + "\n"
            f"{'=' * 50}\n"
        )
        
        os.system('cls' if os.name == 'nt' else 'clear')
        print(stats_output)
        
        self.log_file.write(stats_output)
        self.log_file.flush()
        
        storage.update_cpu_stats('summary', {
            'total_hashrate': total_hashrate,
            'total_hashes': hash_stats['total_hashes'],
            'runtime': runtime,
            'progress': progress,
            'time_remaining': time_remaining,
            'timestamp': now
        })
        
        if runtime >= 120:
            self.show_final_summary(stats)
            return True
        return False
            
    def show_final_summary(self, stats):
        """Show final mining summary"""
        runtime = time.time() - stats['hash_stats']['start_time']
        total_hashrate = stats['hash_stats']['total_hashes'] / runtime if runtime > 0 else 0
        
        final_summary = (
            f"\n{'=' * 60}\n"
            f"MINING TEST COMPLETED\n"
            f"{'=' * 60}\n"
            f"Final Results:\n"
            f"  Total Runtime: {runtime:.2f} seconds\n"
            f"  Total Hashes: {stats['hash_stats']['total_hashes']:,}\n"
            f"  Average Hashrate: {total_hashrate:,.2f} H/s\n"
            f"  CPUs Used: 1,000\n"
            f"  Cores Per CPU: 600,000\n"
            f"  Threads Per Core: 400,000\n"
            f"Best Hash Found:\n"
            f"  Hash: {stats.get('best_result', {}).get('hash', 'N/A')}\n"
            f"  Nonce: {stats.get('best_result', {}).get('nonce', 'N/A')}\n"
            f"{'=' * 60}\n"
        )
        
        print(final_summary)
        self.log_file.write("\nFINAL SUMMARY\n" + final_summary)
        self.log_file.flush()
        
        storage.update_cpu_stats('final_summary', {
            'runtime': runtime,
            'total_hashes': stats['hash_stats']['total_hashes'],
            'average_hashrate': total_hashrate,
            'best_hash': stats.get('best_result', {}).get('hash', 'N/A'),
            'best_nonce': stats.get('best_result', {}).get('nonce', 'N/A'),
            'timestamp': time.time()
        })
        
    def close(self):
        """Clean up resources"""
        if self.log_file:
            self.log_file.close()

def process_mining_batch(args: tuple) -> tuple:
    """Process a batch of mining operations using virtual CPU architecture"""
    args_chunk, cpu_range_for_process, config = args
    header, target_hex, nonce_start_overall, batch_size_overall = args_chunk
    target = bytes.fromhex(target_hex)
    
    # Initialize the CPU system if not already done
    VirtualCPU.initialize_system(
        total_cpus=config.total_virtual_cpus,
        cores_per_cpu=config.virtual_cores_per_cpu,
        threads_per_core=config.virtual_threads_per_core,
        batch_size=config.virtual_batch_size
    )
    
    # Create virtual CPU for this batch
    vcpu = VirtualCPU(
        cpu_id=cpu_range_for_process[0],
        group_type=CPUGroupType.COMPUTATION
    )
    
    # Calculate batch distribution for virtual cores
    batch_per_core = batch_size_overall // (config.virtual_cores_per_cpu * config.virtual_threads_per_core)
    
    # Process mining with this virtual CPU
    total_hashes = 0
    best_hash = None
    best_nonce = None
    
    try:
        for core in range(config.virtual_cores_per_cpu):
            for thread in range(config.virtual_threads_per_core):
                start_nonce = nonce_start_overall + (core * config.virtual_threads_per_core + thread) * batch_per_core
                end_nonce = start_nonce + batch_per_core
                
                # Process this range of nonces
                header_copy = bytearray(header)
                nonce_index = len(header_copy) - 4
                
                for nonce in range(start_nonce, end_nonce):
                    struct.pack_into(">I", header_copy, nonce_index, nonce)
                    hash_result = sha256d(header_copy)
                    
                    if hash_result < target:
                        best_hash = hash_result
                        best_nonce = nonce
                        break
                    
                    total_hashes += 1
                    
                    # Update stats every 10000 hashes
                    if total_hashes % 10000 == 0:
                        mining_stats.update_stats(vcpu.cpu_id, 10000)
                
                if best_hash:
                    break
            
            if best_hash:
                break
                
        return best_hash, best_nonce, total_hashes, bool(best_hash)
        
    except Exception as e:
        logging.error(f"Error in mining batch: {e}")
        return None, None, 0, False
    
    # Create virtual CPU instance for this batch
    vcpu = VirtualCPU(
        cpu_id=cpu_range_for_process[0],
        cores=config.cores_per_cpu,
        threads_per_core=config.threads_per_core
    )
    
    # Initialize CPU registers and electron state
    vcpu.initialize_registers()
    electron_state = RegisterState()
    
    # Calculate optimal batch distribution for virtual cores
    batch_per_core = batch_size_overall // (config.cores_per_cpu * config.threads_per_core)
    
    # Process mining with virtual CPU
    total_hashes = 0
    best_hash = None
    best_nonce = None
    thread_results = []
    
    try:
        # Calculate batch sizes for parallel processing
        cores_per_cpu = config.virtual_cores_per_cpu
        threads_per_core = config.virtual_threads_per_core
        
        batch_per_thread = batch_size_overall // (cores_per_cpu * threads_per_core)
        
        # Process each virtual core and thread
        for core in range(cores_per_cpu):
            for thread in range(threads_per_core):
                start_nonce = nonce_start_overall + (core * threads_per_core + thread) * batch_per_thread
                
                # Process this thread's nonce range
                result = process_mining_chunk((header, target_hex, start_nonce, batch_per_thread), config)
                if result[0]:  # If a valid hash was found
                    best_hash = result[0]
                    best_nonce = result[1]
                total_hashes += result[2]
                
                # Update stats for this thread
                thread_id = core * threads_per_core + thread
                mining_stats.update_stats(thread_id, result[2])
                
                if best_hash:  # If we found a solution, stop processing
                    break
            if best_hash:  # If we found a solution, stop processing
                break
                
        return best_hash, best_nonce, total_hashes, bool(best_hash)
        
    except Exception as e:
        logging.error(f"Error in mining batch: {e}")
        return None, None, 0, False
    
    results = []
    with ThreadPoolExecutor(max_workers=num_threads) as executor:
        futures = []
        for i in range(num_threads):
            start_nonce = nonce_start_overall + i * thread_batch_size
            args = (header, target, start_nonce, thread_batch_size, 
                   cpu_range_for_process[0] * num_threads + i)
            futures.append(executor.submit(process_nonce_range, args))
        
        for future in futures:
            results.append(future.result())

    total_hashes_processed_in_batch = 0
    total_blocks_found_in_batch = 0
    best_hash_in_batch = b'\xff' * 32
    best_nonce_in_batch = None

    for cpu_id in cpu_range_for_process:
        cpu_nonce_start = (nonce_start_overall + cpu_id * batch_size_overall) % config.nonce_range
        
        process_best_hash, process_best_nonce, hashes_processed, block_found = process_mining_chunk(
            (header, target_hex, cpu_nonce_start, batch_size_overall), config
        )
        
        total_hashes_processed_in_batch += hashes_processed
        if block_found:
            total_blocks_found_in_batch += 1
        
        if process_best_hash and (not best_hash_in_batch or process_best_hash < best_hash_in_batch):
            best_hash_in_batch = process_best_hash
            best_nonce_in_batch = process_best_nonce

    return (best_hash_in_batch, best_nonce_in_batch, total_hashes_processed_in_batch, total_blocks_found_in_batch)

def mine_on_virtual_cpu(header: bytes, target: int, nonce_start: int, nonce_range: int, stats: MiningStats, config: MiningConfig) -> None:
    """Mine using enhanced CPUs with massive parallelization"""
    os.makedirs("data", exist_ok=True)
    
    print("\nInitializing virtual CPUs...")
    num_processes = multiprocessing.cpu_count()
    
    pool_args = []
    cpus_per_process = (config.total_virtual_cpus + num_processes - 1) // num_processes
    for i in range(num_processes):
        start_cpu = i * cpus_per_process
        end_cpu = min(start_cpu + cpus_per_process, config.total_virtual_cpus)
        if start_cpu >= end_cpu:
            continue
        cpu_range_for_process = range(start_cpu, end_cpu)
        pool_args.append(((header, target.to_bytes(32, 'big').hex(), nonce_start, config.virtual_batch_size), cpu_range_for_process, config))

    with multiprocessing.Pool(processes=num_processes) as pool:
        print("\nStarting parallel mining operations...")
        results = pool.map(process_mining_batch, pool_args)

    total_hashes_processed = 0
    total_blocks_found = 0
    best_hash_overall = None
    best_nonce_overall = None

    for result in results:
        r_best_hash, r_best_nonce, r_hashes, r_blocks = result
        total_hashes_processed += r_hashes
        total_blocks_found += r_blocks
        if r_best_hash and (not best_hash_overall or r_best_hash < best_hash_overall):
            best_hash_overall = r_best_hash
            best_nonce_overall = r_best_nonce

    # Update the JSON storage directly with aggregated results
    storage.update_mining_stats({
        'total_hashes': total_hashes_processed,
        'total_blocks_found': total_blocks_found,
        'best_hash': best_hash_overall.hex() if best_hash_overall else None,
        'best_nonce': best_nonce_overall
    })

if __name__ == "__main__":
    print("[DEBUG] Main execution block started.")
    
    # Initialize mining configuration
    config = init_mining_config()
    
    # Prepare mining parameters
    header = os.urandom(80)  # Random block header
    target = int.from_bytes(b'\x00\x00' + os.urandom(30), 'big')  # Random target
    nonce_start = 0
    
    # Start stats monitoring thread
    stats_thread = threading.Thread(target=print_mining_stats, daemon=True)
    stats_thread.start()
    
    try:
        # Start mining
        mine_on_virtual_cpu(header, target, nonce_start, config.nonce_range, mining_stats, config)
    except KeyboardInterrupt:
        print("\nReceived keyboard interrupt, shutting down...")
    finally:
        stop_mining = True
        # Show final stats
        print("\nFinal Mining Statistics:")
        stats = mining_stats.get_hashrate()
        print(f"Total Hashes: {stats['total_hashes']:,}")
        print(f"Average Hash Rate: {stats['average']:,.2f} H/s")
        print(f"Peak Hash Rate: {stats['current']:,.2f} H/s")
        print(f"Best Efficiency Achieved: {stats['efficiency']:.2f}%")

    # Update configuration with virtual parameters
    config.virtual_cores_per_cpu = 600000
    config.virtual_threads_per_core = 400000
    config.virtual_batch_size = 10000
    config.nonce_range = 2**32

    mining_stats = MiningStats()

    header = b"\x01\x00\x00\x00" + b"\x00" * 32 + b"\x00" * 32 + b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00"
    target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
    nonce_start = 0

    main_loop_start_time = time.time()
    while time.time() - main_loop_start_time < 120:
        mine_on_virtual_cpu(header, target, nonce_start, config.nonce_range, mining_stats, config)
        mining_stats.log_stats(time.time() - mining_stats.last_update)
        mining_stats.last_update = time.time()
        nonce_start += config.virtual_batch_size * config.total_virtual_cpus

    final_stats = storage.get_mining_stats()
    mining_stats.show_final_summary(final_stats)
    mining_stats.close()

