"""
Unified Mining Controller that coordinates CPU and GPU mining operations
"""

from typing import Dict, List, Optional, Tuple, Any
import threading
import queue
import time
from concurrent.futures import ThreadPoolExecutor
import sys
import os

sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
from cpu.enhanced_cpu import CPUGrid
from .gpu_mining_manager import GPUMiningManager
from virtual_gpu_driver.src.driver_api import VirtualGPUDriver

class UnifiedMiningController:
    def __init__(self, num_gpus: int = 10, num_cpus: int = 5000):
        # Initialize hardware components
        self.cpu_grid = CPUGrid()  # 5000 CPUs
        self.gpu_manager = GPUMiningManager(num_gpus=num_gpus)  # 10 GPUs
        
        # Mining queues and coordination
        self.mining_queue = queue.Queue()
        self.result_queue = queue.Queue()
        self.mining_active = False
        
        # Thread pools
        self.cpu_pool = ThreadPoolExecutor(max_workers=num_cpus)
        self.gpu_pool = ThreadPoolExecutor(max_workers=num_gpus)
        
        # Performance tracking
        self.stats = {
            'cpu_hash_rate': 0,
            'gpu_hash_rate': 0,
            'total_hash_rate': 0,
            'blocks_found': 0,
            'mining_time': 0
        }
        
    def start_mining(self, block_template: bytes, target: bytes):
        """Start mining operations across all hardware"""
        self.mining_active = True
        
        # Start CPU mining threads
        cpu_future = self.cpu_pool.submit(
            self._cpu_mining_loop,
            block_template,
            target
        )
        
        # Start GPU mining threads
        gpu_future = self.gpu_pool.submit(
            self._gpu_mining_loop,
            block_template,
            target
        )
        
        # Start monitoring thread
        monitor_thread = threading.Thread(
            target=self._monitor_mining_progress,
            daemon=True
        )
        monitor_thread.start()
        
        return cpu_future, gpu_future
        
    def _cpu_mining_loop(self, block_template: bytes, target: bytes):
        """Main CPU mining loop"""
        while self.mining_active:
            try:
                # Distribute work across CPU grid
                cpu_results = self.cpu_grid.execute_parallel_mining(
                    block_template=block_template,
                    target=target,
                    nonce_range=(0, 2**32)
                )
                
                # Process results
                for result in cpu_results:
                    if result.get('valid_solution'):
                        self.result_queue.put({
                            'source': 'cpu',
                            'nonce': result['nonce'],
                            'hash_rate': result['hash_rate']
                        })
                        
                    # Update CPU stats
                    self.stats['cpu_hash_rate'] = result['hash_rate']
                    
            except Exception as e:
                print(f"CPU mining error: {str(e)}")
                time.sleep(1)
                
    def _gpu_mining_loop(self, block_template: bytes, target: bytes):
        """Main GPU mining loop"""
        while self.mining_active:
            try:
                # Execute mining round on GPUs
                nonce, hash_rate = self.gpu_manager.execute_mining_round(
                    block_template=block_template,
                    target=target
                )
                
                # Process results
                if nonce is not None:
                    self.result_queue.put({
                        'source': 'gpu',
                        'nonce': nonce,
                        'hash_rate': hash_rate
                    })
                    
                # Update GPU stats
                self.stats['gpu_hash_rate'] = hash_rate
                    
            except Exception as e:
                print(f"GPU mining error: {str(e)}")
                time.sleep(1)
                
    def _monitor_mining_progress(self):
        """Monitor and coordinate mining progress"""
        while self.mining_active:
            # Update total hash rate
            self.stats['total_hash_rate'] = (
                self.stats['cpu_hash_rate'] + 
                self.stats['gpu_hash_rate']
            )
            
            # Get detailed GPU stats
            gpu_stats = self.gpu_manager.get_mining_stats()
            
            # Process any found blocks
            while not self.result_queue.empty():
                result = self.result_queue.get()
                self.stats['blocks_found'] += 1
                print(f"Block found by {result['source']}! "
                      f"Nonce: {result['nonce']}")
                
            time.sleep(1)  # Update every second
            
    def get_mining_stats(self) -> Dict[str, Any]:
        """Get comprehensive mining statistics"""
        stats = self.stats.copy()
        
        # Add GPU-specific stats
        gpu_stats = self.gpu_manager.get_mining_stats()
        stats['gpu_details'] = gpu_stats
        
        # Add CPU grid stats
        cpu_stats = self.cpu_grid.get_grid_stats()
        stats['cpu_details'] = cpu_stats
        
        return stats
        
    def stop_mining(self):
        """Stop all mining operations"""
        self.mining_active = False
        self.cpu_pool.shutdown(wait=True)
        self.gpu_pool.shutdown(wait=True)
        
    def adjust_workload(self, cpu_percentage: float = 0.5):
        """Adjust workload distribution between CPU and GPU"""
        if not 0 <= cpu_percentage <= 1:
            raise ValueError("CPU percentage must be between 0 and 1")
            
        # Calculate work splits
        total_work = 2**32  # Total nonce space
        cpu_work = int(total_work * cpu_percentage)
        gpu_work = total_work - cpu_work
        
        # Update CPU grid allocation
        self.cpu_grid.set_work_range(0, cpu_work)
        
        # Update GPU allocation
        self.gpu_manager.set_work_range(cpu_work, total_work)
        
    def optimize_performance(self):
        """Auto-optimize mining performance based on hardware capabilities"""
        # Get current performance metrics
        cpu_perf = self.stats['cpu_hash_rate']
        gpu_perf = self.stats['gpu_hash_rate']
        total_perf = cpu_perf + gpu_perf
        
        if total_perf > 0:
            # Calculate optimal distribution based on performance
            cpu_percentage = cpu_perf / total_perf
            
            # Adjust workload with some margin for fluctuation
            self.adjust_workload(min(max(cpu_percentage, 0.1), 0.9))

import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parents[2]))





