import threading
import serial
import time
import pynmea2
from datetime import datetime, timedelta

class GPSReader(threading.Thread):
    def __init__(self, serial_port, baud_rate, data_callback, status_callback, sync_callback):
        super().__init__()
        self.serial_port = serial_port
        self.baud_rate = baud_rate
        self.data_callback = data_callback
        self.status_callback = status_callback
        self.sync_callback = sync_callback
        self.running = True
        self.last_data_time = 0
        self.sync_requested = False
        self.sync_lock = threading.Lock()
        self.last_gps_second = None

    def request_sync(self):
        """Request synchronization on the next GPS second boundary"""
        with self.sync_lock:
            if not self.sync_requested:  # Only set if not already requested
                self.sync_requested = True
                self.last_gps_second = None  # Reset to ensure fresh boundary detection
                self.status_callback("Waiting for GPS second boundary...")

    def run(self):
        try:
            self.status_callback("Connecting to GPS...")
            with serial.Serial(self.serial_port, self.baud_rate, timeout=1) as ser:
                self.status_callback(f"Connected to {self.serial_port} at {self.baud_rate} baud.")
                current_gps_data = {
                    'gps_datetime': None,
                    'lat': 'N/A',
                    'lon': 'N/A',
                    'sats': 'N/A'
                }

                while self.running:
                    line = ser.readline().decode('ascii', errors='replace').strip()
                    if line:
                        self.last_data_time = time.time()
                        try:
                            msg = pynmea2.parse(line)
                            if isinstance(msg, pynmea2.types.talker.RMC):
                                if msg.status == 'A':
                                    gps_dt = datetime.combine(msg.datestamp, msg.timestamp)
                                    current_gps_data['gps_datetime'] = gps_dt
                                    current_gps_data['lat'] = f"{msg.latitude:.5f} {msg.lat_dir}" if msg.latitude else 'N/A'
                                    current_gps_data['lon'] = f"{msg.longitude:.5f} {msg.lon_dir}" if msg.longitude else 'N/A'

                                    # Check for second boundary and sync if requested
                                    current_second = gps_dt.second
                                    with self.sync_lock:
                                        if (self.sync_requested and 
                                            self.last_gps_second is not None and 
                                            current_second != self.last_gps_second):
                                            
                                            # We've crossed a second boundary - sync now!
                                            # Add 1 second to account for the boundary we just crossed
                                            sync_time = gps_dt + timedelta(seconds=1)
                                            sync_time = sync_time.replace(microsecond=0)  # Ensure clean second
                                            
                                            self.sync_callback(sync_time)
                                            self.sync_requested = False
                                            
                                    self.last_gps_second = current_second

                            elif isinstance(msg, pynmea2.types.talker.GGA):
                                if msg.num_sats:
                                    current_gps_data['sats'] = str(msg.num_sats)

                            self.data_callback(current_gps_data)

                        except pynmea2.ParseError:
                            pass
                    elif time.time() - self.last_data_time > 5:
                        self.status_callback("Connected, but no valid GPS data received.")
        except serial.SerialException as e:
            self.status_callback(f"Serial Error: {e}")

    def stop(self):
        self.running = False