import threading
import time
from datetime import datetime, timedelta

class TimingController:
    """Handles synchronized timing operations for the CSEM Triggerbox"""
    
    def __init__(self, rtc, update_callback=None):
        self.rtc = rtc
        self.update_callback = update_callback or (lambda times: None)
        
        # Timing state
        self.gps_time_obj = None
        self.last_display_second = -1
        self.display_lock = threading.Lock()
        
        # Cache for synchronized time values
        self.cached_times = {
            'rpi': None,
            'gps': None,
            'rtc': None,
            'timestamp': 0
        }
        
        # Precision timing thread
        self.sync_timer_thread = None
        self.sync_timer_running = False
        
        # Countdown state
        self.countdown_active = False
        self.countdown_callback = None
        self.transmission_start_callback = None
        self.countdown_completed = False  # Track if countdown has completed
    
    def start_synchronized_timing(self):
        """Start synchronized timing system with precise second boundaries"""
        self.sync_timer_running = True
        self.sync_timer_thread = threading.Thread(target=self._precision_sync_loop, daemon=True)
        self.sync_timer_thread.start()
    
    def stop_synchronized_timing(self):
        """Stop the synchronized timing system"""
        self.sync_timer_running = False
        if self.sync_timer_thread and self.sync_timer_thread.is_alive():
            self.sync_timer_thread.join(timeout=1.0)
    
    def update_gps_time(self, gps_datetime):
        """Update GPS time from GPS reader"""
        with self.display_lock:
            self.gps_time_obj = gps_datetime
    
    def start_countdown(self, countdown_callback, transmission_start_callback):
        """
        Start countdown to next minute based on RTC time
        
        Args:
            countdown_callback: Function to call with countdown updates
            transmission_start_callback: Function to call when transmission should start
        """
        try:
            rtc_time = self.rtc.read_time()
            
            # Simple countdown: count down the remaining seconds in the current minute
            remaining_seconds = 60 - rtc_time.second
            
            # Handle edge case where we're exactly at 00 seconds
            if rtc_time.second == 0:
                remaining_seconds = 60  # Full minute countdown
            
            self.countdown_active = True
            self.countdown_completed = False  # Reset completion flag
            self.countdown_callback = countdown_callback
            self.transmission_start_callback = transmission_start_callback
            
            print(f"[COUNTDOWN] Starting {remaining_seconds} second countdown to next minute boundary")
            return True
            
        except Exception as e:
            print(f"Error reading RTC for countdown: {e}")
            return False
    
    def stop_countdown(self):
        """Stop the active countdown"""
        self.countdown_active = False
        self.countdown_completed = False
        self.countdown_callback = None
        self.transmission_start_callback = None
    
    def get_cached_times(self):
        """Get the most recent cached synchronized times"""
        return self.cached_times.copy()
    
    def _precision_sync_loop(self):
        """High precision timing loop that synchronizes all time readings"""
        while self.sync_timer_running:
            # Wait for the next second boundary with high precision
            now = datetime.utcnow()
            
            # Calculate microseconds until next second
            microseconds_to_next_second = 1000000 - now.microsecond
            sleep_time = microseconds_to_next_second / 1000000.0
            
            # Sleep until just before the second boundary
            if sleep_time > 0.01:  # Only sleep if we have more than 10ms
                time.sleep(sleep_time - 0.005)  # Wake up 5ms early
            
            # Busy wait for the exact second boundary
            while True:
                precise_now = datetime.utcnow()
                if precise_now.microsecond < 10000:  # Within 10ms of second boundary
                    break
                time.sleep(0.001)  # 1ms sleep in busy wait
            
            # NOW capture all times at the exact same moment
            self._capture_synchronized_times(precise_now)
            
            # Small sleep to avoid immediate re-trigger
            time.sleep(0.1)
    
    def _capture_synchronized_times(self, reference_time):
        """Capture all time sources at the exact same moment"""
        current_second = reference_time.second
        
        # Only update when the second actually changes
        if current_second != self.last_display_second:
            with self.display_lock:
                self.last_display_second = current_second
                
                # Capture all times at this exact moment
                rpi_time = reference_time
                
                # For GPS time, if we have a recent GPS time, project it forward to this exact second
                gps_time = None
                if self.gps_time_obj:
                    try:
                        # Ensure both times are timezone-naive for comparison
                        ref_time_naive = reference_time.replace(tzinfo=None) if reference_time.tzinfo else reference_time
                        gps_time_naive = self.gps_time_obj.replace(tzinfo=None) if self.gps_time_obj.tzinfo else self.gps_time_obj
                        
                        # Calculate how old the GPS time is
                        time_diff = (ref_time_naive - gps_time_naive).total_seconds()
                        if abs(time_diff) < 5:  # Only use if GPS time is less than 5 seconds old
                            # Project GPS time forward to match current second boundary
                            seconds_to_add = round(time_diff)
                            gps_time = gps_time_naive + timedelta(seconds=seconds_to_add)
                        else:
                            gps_time = gps_time_naive  # Use as-is if too old to project
                    except (TypeError, AttributeError) as e:
                        print(f"[GPS SYNC ERROR] {e}")
                        gps_time = self.gps_time_obj  # Fall back to original GPS time
                
                # Get fresh RTC time - this is the critical sync point
                rtc_time = None
                try:
                    rtc_time = self.rtc.read_time()
                except Exception as e:
                    print(f"[RTC ERROR] {e}")
                
                # Update cache with synchronized times
                self.cached_times = {
                    'rpi': rpi_time,
                    'gps': gps_time,
                    'rtc': rtc_time,
                    'timestamp': time.time()
                }
                
                # Call update callback
                if self.update_callback:
                    self.update_callback(self.cached_times)
                
                # Update countdown synchronized to RTC time
                if self.countdown_active and rtc_time and self.countdown_callback:
                    self._update_countdown_sync(rtc_time)
                
                # Debug sync accuracy
                self._debug_sync_accuracy()
    
    def _update_countdown_sync(self, rtc_time):
        """Update countdown display synchronized to RTC time"""
        if self.countdown_active and not self.countdown_completed:
            # Simple countdown: seconds remaining until next minute (00 seconds)
            remaining_seconds = 60 - rtc_time.second
            
            # Handle the case where we're exactly at 00 seconds
            if rtc_time.second == 0:
                remaining_seconds = 0
            
            if remaining_seconds > 0:
                mins, secs = divmod(remaining_seconds, 60)
                countdown_text = f"Countdown: {mins:02d}:{secs:02d}"
                if self.countdown_callback:
                    self.countdown_callback(countdown_text)
            else:
                # Time to start transmission (we've hit the minute boundary)
                if not self.countdown_completed:
                    self.countdown_completed = True
                    if self.countdown_callback:
                        self.countdown_callback("Countdown: 00:00")
                    
                    # Small delay to allow GUI to show 00:00 before clearing
                    def delayed_start():
                        self.countdown_active = False
                        if self.transmission_start_callback:
                            self.transmission_start_callback()
                    
                    # Use threading.Timer for the delay instead of root.after
                    delay_timer = threading.Timer(0.5, delayed_start)  # 500ms delay
                    delay_timer.start()
    
    def _debug_sync_accuracy(self):
        """Debug output to console for sync verification"""
        cached = self.cached_times
        
        if cached['rpi'] and cached['rtc']:
            rpi_total_seconds = cached['rpi'].hour * 3600 + cached['rpi'].minute * 60 + cached['rpi'].second
            rtc_total_seconds = cached['rtc'].hour * 3600 + cached['rtc'].minute * 60 + cached['rtc'].second
            time_diff = rpi_total_seconds - rtc_total_seconds
            
            # Handle day boundary crossings
            if time_diff > 43200:  # More than 12 hours difference
                time_diff -= 86400
            elif time_diff < -43200:  # Less than -12 hours difference  
                time_diff += 86400
                
            if abs(time_diff) > 1:
                print(f"[SYNC DEBUG] Time difference: RPI-RTC = {time_diff} seconds")
        
        # Debug GPS sync
        if cached['rpi'] and cached['gps']:
            rpi_total_seconds = cached['rpi'].hour * 3600 + cached['rpi'].minute * 60 + cached['rpi'].second
            gps_total_seconds = cached['gps'].hour * 3600 + cached['gps'].minute * 60 + cached['gps'].second
            gps_diff = rpi_total_seconds - gps_total_seconds
            
            # Handle day boundary crossings
            if gps_diff > 43200:
                gps_diff -= 86400
            elif gps_diff < -43200:
                gps_diff += 86400
                
            if abs(gps_diff) > 1:
                print(f"[SYNC DEBUG] GPS difference: RPI-GPS = {gps_diff} seconds")