from machine import Pin, ADC
import time
import network
import urequests
import gc
import machine

# ==========================================
# --- 1. CONFIGURATION ---
# ==========================================
CONFIG = {
    # WiFi Settings
    "SSID": "IoT@UMT",
    "PASS": "i00t@UMT",
    "MAX_RETRIES": 10,
    "RETRY_DELAY": 5,  # Seconds

    # ThingSpeak Settings
    "API_KEY": "DXA6PTCSYQ27ZI94",
    "URL": "http://api.thingspeak.com/update",

    # Timing Settings
    "INTERVAL_NORMAL": 30 * 60,  # 30 Minutes
    "INTERVAL_INITIAL": 15,      # 15 Seconds (First run only)
    "SENSOR_READ_DELAY": 10,      # Seconds between sensor reads

    # Pin Definitions
    "PIN_LED": "LED",
    "PIN_TEMP": 4,
    "PIN_LDR": 26,
    "PINS_WIND": [12, 13, 14, 15], # N, E, S, W
}

# ==========================================
# --- 2. SYSTEM UTILITIES (LOGGING & LED) ---
# ==========================================
led = Pin(CONFIG["PIN_LED"], Pin.OUT)
start_tick = time.ticks_ms()

def log(tag, message):
    """Prints a formatted log message with system timestamp."""
    seconds = time.ticks_diff(time.ticks_ms(), start_tick) / 1000
    print(f"[{seconds:>8.1f}s] [{tag:<6}] {message}")

def blink_success():
    """Rapidly blinks LED 3 times to indicate success."""
    for _ in range(3):
        led.off(); time.sleep(0.1)
        led.on(); time.sleep(0.1)
    # Restore state based on connection
    led.value(1 if wlan.isconnected() else 0)

# ==========================================
# --- 3. WIFI MANAGEMENT ---
# ==========================================
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.disconnect()

def connect_wifi():
    """
    Robust WiFi connection with visual feedback.
    Returns: True if connected, False if failed.
    """
    if wlan.isconnected():
        return True

    log("WIFI", f"Attempting connection to '{CONFIG['SSID']}'...")
    wlan.connect(CONFIG["SSID"], CONFIG["PASS"])
    
    # Retry Loop
    for attempt in range(1, CONFIG["MAX_RETRIES"] + 1):
        status = wlan.status()
        
        # STATUS 3 = Connected
        if status == 3:
            ip = wlan.ifconfig()[0]
            log("WIFI", f"Connected! IP: {ip}")
            wlan.config(pm=0xa11140) # Disable power save
            led.on()
            return True
        
        # Fail states
        elif status < 0 or status > 3:
            log("WIFI", f"Error state detected: {status}")
            break
            
        # Waiting... Blink LED slowly
        log("WIFI", f"Waiting... (Attempt {attempt}/{CONFIG['MAX_RETRIES']})")
        led.toggle()
        time.sleep(CONFIG["RETRY_DELAY"])
    
    log("WIFI", "Connection failed. System entering OFFLINE mode.")
    led.off()
    return False

# ==========================================
# --- 4. DATA QUEUE & UPLOAD ---
# ==========================================
data_queue = []

def upload_packet(packet):
    """Sends a single packet. Returns True on 200 OK."""
    url = (f"{CONFIG['URL']}?api_key={CONFIG['API_KEY']}"
           f"&field1={packet['temp']}"
           f"&field2={packet['light']}"
           f"&field3={packet['wind']}")
    try:
        res = urequests.get(url)
        code = res.status_code
        res.close()
        del res
        return code == 200
    except Exception as e:
        log("UPLOAD", f"Error: {e}")
        return False

def process_queue():
    """Processes the offline data queue (FIFO)."""
    # 1. Check/Fix Connection
    if not wlan.isconnected():
        log("QUEUE", "WiFi needed. Triggering connection...")
        if not connect_wifi():
            log("QUEUE", "WiFi failed. Aborting upload.")
            return

    # 2. Upload Loop
    if data_queue:
        log("QUEUE", f"Processing {len(data_queue)} pending packets.")
        
        while data_queue:
            packet = data_queue[0] # Peek
            log("UPLOAD", f"Sending: T={packet['temp']} L={packet['light']} W={packet['wind']} ...")
            
            if upload_packet(packet):
                log("UPLOAD", "Success [200 OK]")
                blink_success()
                data_queue.pop(0) # Remove from queue
                gc.collect()
            else:
                log("UPLOAD", "Failed. Keeping data in queue.")
                break # Stop processing to preserve order
    else:
        log("QUEUE", "Queue empty. No action needed.")

# ==========================================
# --- 5. SENSOR SETUP ---
# ==========================================
class WindVane:
    def __init__(self, pins):
        self.pins = [Pin(p, Pin.IN, Pin.PULL_DOWN) for p in pins]
        self.dirs = ["North", "North-East", "South-East", "South-West", 
                     "North-West", "East", "South", "West"] # Simplified mapping logic
    
    def get_data(self):
        # Read pins: N, E, S, W
        vals = [p.value() for p in self.pins]
        n, e, s, w = vals
        
        # Logic matches your previous specific requirements
        if n and e: return "North-East"
        if s and e: return "South-East"
        if s and w: return "South-West"
        if n and w: return "North-West"
        if n: return "North"
        if e: return "East"
        if s: return "South"
        if w: return "West"
        return "No_Wind"

wind_sensor = WindVane(CONFIG["PINS_WIND"])
temp_sensor = ADC(CONFIG["PIN_TEMP"])
ldr_sensor = ADC(CONFIG["PIN_LDR"])
wind_map = {
    "North": 0, "North-East": 45, "East": 90, "South-East": 135,
    "South": 180, "South-West": 225, "West": 270, "North-West": 315,
    "No_Wind": 0
}

# ==========================================
# --- 6. MAIN SYSTEM LOOP ---
# ==========================================
temp_list = []
wind_list = []
light_list = []
start_time_sec = time.time()
current_interval = CONFIG["INTERVAL_INITIAL"]
first_run = True

log("SYS", "System Initializing...")
log("SYS", f"First upload in {current_interval}s")
time.sleep(1)

# Initial Connect
connect_wifi()

while True:
    try:
        # --- A. SENSOR ACQUISITION ---
        # 1. Wind
        wind_txt = wind_sensor.get_data()
        
        # 2. Temp
        t_reading = temp_sensor.read_u16() * (3.3 / 65535)
        temp_c = 27 - (t_reading - 0.706) / 0.001721
        
        # 3. Light
        l_reading = ldr_sensor.read_u16()
        light_pct = round(100 - ((l_reading / 65535) * 100), 1)

        # --- B. DATA BUFFERING ---
        temp_list.append(temp_c)
        light_list.append(light_pct)
        if wind_txt != "No_Wind":
            wind_list.append(wind_txt)

        # Buffer Safety Limit
        if len(temp_list) > 1500:
            temp_list = temp_list[-100:]
            light_list = light_list[-100:]

        # --- C. LIVE MONITORING ---
        # Visual WiFi Status Check
        if wlan.isconnected():
            led.on()
            wifi_symbol = "[ONLINE]"
        else:
            led.off()
            wifi_symbol = "[OFFLINE]"

        # Console Output
        mem = gc.mem_free()
        print(f"{wifi_symbol} Wind: {wind_txt:<10} | Temp: {temp_c:>4.1f}°C | Light: {light_pct:>5.1f}% | RAM: {mem} | Queue: {len(data_queue)}")
        
        if mem < 30000: gc.collect()

        # --- D. INTERVAL HANDLER ---
        elapsed = time.time() - start_time_sec
        if elapsed >= current_interval:
            print("-" * 60)
            log("SYS", "INTERVAL TRIGGERED - PREPARING DATA")
            
            if temp_list:
                # 1. Aggregate
                avg_t = round(sum(temp_list) / len(temp_list), 2)
                avg_l = round(sum(light_list) / len(light_list), 2)
                
                dom_wind = "No_Wind"
                if wind_list:
                    dom_wind = max(set(wind_list), key=wind_list.count)
                
                wind_deg = wind_map.get(dom_wind, 0)

                log("DATA", f"Avg Temp: {avg_t}C | Avg Light: {avg_l}% | Wind: {dom_wind}")

                # 2. Enqueue
                new_packet = {'temp': avg_t, 'light': avg_l, 'wind': wind_deg}
                data_queue.append(new_packet)
                
                # 3. Clear Buffers
                temp_list = []
                light_list = []
                wind_list = []
            
            # 4. Process Queue (Upload)
            process_queue()

            # 5. Logic Updates
            start_time_sec = time.time()
            if first_run:
                log("SYS", "Initial phase done. Switching to 30 min interval.")
                first_run = False
                current_interval = CONFIG["INTERVAL_NORMAL"]
            
            print("-" * 60)

        time.sleep(CONFIG["SENSOR_READ_DELAY"])

    except Exception as e:
        log("CRITICAL", f"Main Loop Error: {e}")
        time.sleep(5)