import smbus
import time
from datetime import datetime, timezone
from logger import main_log
from config import RTC_I2C_BUS, RTC_I2C_ADDRESS

class RTCHandler:
    def __init__(self):
        try:
            self.bus = smbus.SMBus(RTC_I2C_BUS)
            self.address = RTC_I2C_ADDRESS
            self.rtc_available = True
            timestamp = datetime.now().strftime("%H:%M:%S")
            print(f"[{timestamp}] [RTC] DS3231 RTC initialized on bus {RTC_I2C_BUS}")
        except Exception as e:
            timestamp = datetime.now().strftime("%H:%M:%S")
            print(f"[{timestamp}] [RTC] Failed to initialize DS3231: {e}")
            self.rtc_available = False

    def bcd_to_decimal(self, bcd):
        """Convert BCD (Binary Coded Decimal) to decimal"""
        return (bcd >> 4) * 10 + (bcd & 0x0f)

    def decimal_to_bcd(self, decimal):
        """Convert decimal to BCD (Binary Coded Decimal)"""
        return ((decimal // 10) << 4) + (decimal % 10)

    def read_rtc_time(self):
        """Read current time from DS3231 RTC"""
        if not self.rtc_available:
            return None
        
        try:
            # Read 7 bytes starting from register 0x00
            data = self.bus.read_i2c_block_data(self.address, 0x00, 7)
            
            # Convert BCD to decimal
            second = self.bcd_to_decimal(data[0] & 0x7f)  # Mask out oscillator stop flag
            minute = self.bcd_to_decimal(data[1])
            hour = self.bcd_to_decimal(data[2] & 0x3f)    # Mask out 12/24 hour flag
            day = self.bcd_to_decimal(data[4])
            month = self.bcd_to_decimal(data[5] & 0x1f)   # Mask out century bit
            year = self.bcd_to_decimal(data[6]) + 2000    # Assume 21st century
            
            # Create datetime object in UTC
            return datetime(year, month, day, hour, minute, second, tzinfo=timezone.utc)
            
        except Exception as e:
            timestamp = datetime.now().strftime("%H:%M:%S")
            print(f"[{timestamp}] [RTC] Error reading RTC time: {e}")
            return None

    def set_rtc_time(self, dt):
        """Set DS3231 RTC time from datetime object (should be UTC)"""
        if not self.rtc_available:
            return False
        
        try:
            # Convert to BCD format
            data = [
                self.decimal_to_bcd(dt.second),
                self.decimal_to_bcd(dt.minute),
                self.decimal_to_bcd(dt.hour),      # 24-hour format
                self.decimal_to_bcd(dt.weekday() + 1),  # DS3231 uses 1-7 for weekdays
                self.decimal_to_bcd(dt.day),
                self.decimal_to_bcd(dt.month),
                self.decimal_to_bcd(dt.year - 2000)  # DS3231 stores years as offset from 2000
            ]
            
            # Write to RTC starting at register 0x00
            self.bus.write_i2c_block_data(self.address, 0x00, data)
            timestamp = datetime.now().strftime("%H:%M:%S")
            print(f"[{timestamp}] [RTC] RTC time updated to {dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
            main_log(f"RTC time synced to GPS: {dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
            return True
            
        except Exception as e:
            timestamp = datetime.now().strftime("%H:%M:%S")
            print(f"[{timestamp}] [RTC] Error setting RTC time: {e}")
            return False

    def sync_with_gps(self, gps_datetime):
        """Synchronize RTC with GPS time"""
        if not self.rtc_available:
            timestamp = datetime.now().strftime("%H:%M:%S")
            print(f"[{timestamp}] [RTC] Cannot sync - RTC not available")
            return False
        
        # Check if RTC time is significantly different from GPS time
        rtc_time = self.read_rtc_time()
        if rtc_time:
            time_diff = abs((gps_datetime - rtc_time).total_seconds())
            if time_diff > 2:  # More than 2 seconds difference
                timestamp = datetime.now().strftime("%H:%M:%S")
                print(f"[{timestamp}] [RTC] Time difference detected: {time_diff:.1f}s, syncing RTC...")
                return self.set_rtc_time(gps_datetime)
            else:
                timestamp = datetime.now().strftime("%H:%M:%S")
                print(f"[{timestamp}] [RTC] RTC time is already synchronized (diff: {time_diff:.1f}s)")
                return True
        else:
            # If we can't read RTC time, try to set it
            return self.set_rtc_time(gps_datetime)

    def get_utc_time(self):
        """Get current UTC time from RTC"""
        rtc_time = self.read_rtc_time()
        if rtc_time:
            return rtc_time
        else:
            # Fallback to system time if RTC fails
            timestamp = datetime.now().strftime("%H:%M:%S")
            print(f"[{timestamp}] [RTC] Warning: Using system time as RTC fallback")
            main_log("Warning: Using system time as RTC is unavailable")
            return datetime.now(timezone.utc)

    def wait_for_next_minute_rtc(self):
        """
        Wait for the next minute using RTC time.
        This is used when GPS PPS is not available.
        """
        timestamp = datetime.now().strftime("%H:%M:%S")
        print(f"[{timestamp}] [RTC] Waiting for next minute using RTC timing...")
        main_log("Using RTC timing for minute synchronization (GPS PPS unavailable)")
        
        current_time = self.get_utc_time()
        seconds_to_wait = 60 - current_time.second - (current_time.microsecond / 1000000.0)
        
        if seconds_to_wait <= 0:
            seconds_to_wait += 60  # Wait for the next minute if we're already at 0 seconds
        
        timestamp = datetime.now().strftime("%H:%M:%S")
        print(f"[{timestamp}] [RTC] Waiting {seconds_to_wait:.1f} seconds until next minute...")
        
        # Sleep until close to the target time
        if seconds_to_wait > 0.1:
            time.sleep(seconds_to_wait - 0.05)  # Wake up slightly early for fine-tuning
        
        # Fine-tune to the exact second boundary
        while True:
            current_time = self.get_utc_time()
            if current_time.second == 0:
                break
            if current_time.second > 30:  # If we've passed the target, wait for next minute
                time.sleep(60 - current_time.second)
                break
            time.sleep(0.01)  # Small sleep to avoid busy waiting
        
        timestamp = datetime.now().strftime("%H:%M:%S")
        print(f"[{timestamp}] [RTC] Synchronized to minute boundary using RTC")