import serial
import time
import threading
from shared.logging import log
from sensors.lock import serial_lock

def calculate_nmea_checksum(line: str) -> bool:
    """Validates standard NMEA 0183 sentence checksum ($...*HH)."""
    if not line.startswith('$') or '*' not in line:
        return False
    
    try:
        content, checksum_str = line[1:].split('*', 1)
        calculated_checksum = 0
        for char in content:
            calculated_checksum ^= ord(char)
        
        return calculated_checksum == int(checksum_str[:2], 16)
    except Exception:
        return False


class GROVEGPS:
    def __init__(self, port='/dev/ttyAMA0', baud=9600, timeout=1):
        self.ser = serial.Serial(port, baud, timeout=timeout)
        self.clean_data()

    def clean_data(self):
        self.timestamp = ""
        self.quality = 0
        self.satellites = 0
        self.altitude = -1.0
        self.latitude = -1.0
        self.longitude = -1.0

    def read(self):
        """Reads the latest GGA sentence from serial, thread-safely."""
        with serial_lock:
            # 1. Flush accumulated stale data in the UART buffer
            if self.ser.in_waiting > 0:
                self.ser.reset_input_buffer()

            # 2. Try reading up to 15 lines to catch the freshest GGA sentence
            for _ in range(5):
                raw_bytes = self.ser.readline()
                try:
                    line = raw_bytes.decode('utf-8', errors='ignore').strip()
                    # log(f"GPS: Read line: {line}")
                except Exception:
                    continue

                # Supports both $GPGGA and modern $GNGGA sentences
                if (line.startswith('$GPGGA') or line.startswith('$GNGGA')) and calculate_nmea_checksum(line):
                    if self.parse_gga(line):
                        return True
        return False

    def parse_gga(self, line):
        self.clean_data()
        gga = line.split(',')
        
        if len(gga) < 10:
            return False

        try:
            self.timestamp = gga[1]
            self.quality = int(gga[6]) if gga[6] != "" else 0
            self.satellites = int(gga[7]) if gga[7] != "" else 0

            # If quality > 0 and coordinates exist, convert NMEA DDDMM.MMMM to decimal degrees
            if self.quality > 0 and gga[2] != "" and gga[4] != "":
                lat_raw = float(gga[2])
                ns = gga[3]
                lon_raw = float(gga[4])
                ew = gga[5]

                # Latitude calculation
                lat_deg = lat_raw // 100
                lat_min = lat_raw % 100
                self.latitude = lat_deg + (lat_min / 60.0)
                if ns == 'S':
                    self.latitude = -self.latitude

                # Longitude calculation
                lon_deg = lon_raw // 100
                lon_min = lon_raw % 100
                self.longitude = lon_deg + (lon_min / 60.0)
                if ew == 'W':
                    self.longitude = -self.longitude

                self.altitude = float(gga[9]) if gga[9] != "" else -1.0
                return True
            else:
                # No lock on this line
                return True

        except (ValueError, IndexError):
            return False


# Shared instance
gps = GROVEGPS()

def get_gps_data():
    """Returns GPS dictionary if fix is valid, otherwise returns None."""
    has_data = gps.read()
    
    # Strictly check that we have a valid GPS lock (quality > 0 and valid coordinates)
    if has_data and gps.quality > 0 and gps.latitude != -1.0:
        return {
            "timestamp": gps.timestamp,
            "latitude": round(gps.latitude, 6),
            "longitude": round(gps.longitude, 6),
            "altitude": gps.altitude,
            "quality": gps.quality,
            "satellites": gps.satellites
        }
    else:
        log(f"GPS: No valid fix or data available. Satellites: {gps.satellites}, Quality: {gps.quality}")
    
    # Return None so main.py doesn't process or log empty GPS data
    return None