"""
VINSIA IoT Client Library for Python & MicroPython
Works on ESP32 MicroPython, Raspberry Pi, Orange Pi, and Desktop Linux/macOS/Windows.
Website: https://iot.vinsia.web.id
"""

import time
import json

try:
    import paho.mqtt.client as mqtt
    HAS_PAHO = True
except ImportError:
    HAS_PAHO = False

try:
    import urequests as requests
except ImportError:
    import requests


class VinsiaIoT:
    def __init__(self, token, broker="iot.vinsia.web.id", port=1883, http_host="https://iot.vinsia.web.id"):
        self.token = token
        self.broker = broker
        self.port = port
        self.http_host = http_host.rstrip('/')
        self.handlers = {}
        self.client = None
        self.is_connected = False
        self.last_heartbeat = 0

    def on_write(self, pin, callback):
        """Register a callback for when an actuator pin is changed from web"""
        self.handlers[pin.upper()] = callback

    def connect(self):
        """Connect to VINSIA MQTT broker"""
        if not HAS_PAHO:
            print("[VINSIA] paho-mqtt not available, running in HTTP polling mode.")
            return

        client_id = f"vinsia_py_{int(time.time() * 1000) % 100000}"
        self.client = mqtt.Client(client_id=client_id, callback_api_version=mqtt.CallbackAPIVersion.VERSION2)
        self.client.on_connect = self._on_connect
        self.client.on_message = self._on_message
        self.client.connect(self.broker, self.port, keepalive=60)
        self.client.loop_start()

    def _on_connect(self, client, userdata, flags, rc, properties=None):
        if rc == 0:
            self.is_connected = True
            print(f"[VINSIA] Connected to MQTT broker: {self.broker}:{self.port}")
            # Subscribe to commands
            sub_topic = f"vinsia/{self.token}/read/#"
            self.client.subscribe(sub_topic)
            self.log("Python client connected successfully.")
        else:
            print(f"[VINSIA] Connection failed with code {rc}")

    def _on_message(self, client, userdata, msg):
        topic = msg.topic
        payload = msg.payload.decode('utf-8', errors='ignore').strip()
        # Topic format: vinsia/{token}/read/{pin}
        parts = topic.split('/')
        if len(parts) >= 4:
            pin = parts[3].upper()
            if pin in self.handlers:
                try:
                    self.handlers[pin](payload)
                except Exception as e:
                    print(f"[VINSIA] Error in handler for {pin}: {e}")

    def virtual_write(self, pin, value):
        """Publish a pin value to VINSIA IoT Platform"""
        pin = str(pin).upper()
        val_str = str(value)

        if self.is_connected and self.client:
            topic = f"vinsia/{self.token}/write/{pin}"
            self.client.publish(topic, val_str)
            return True
        else:
            # Fallback HTTP
            try:
                url = f"{self.http_host}/api/v1/update"
                r = requests.post(url, json={"token": self.token, "pin": pin, "value": val_str, "source": "hardware"}, timeout=3)
                return r.status_code == 200
            except Exception as e:
                print(f"[VINSIA] HTTP virtual_write error: {e}")
                return False

    def log(self, message):
        """Send a debug log to the web terminal"""
        if self.is_connected and self.client:
            topic = f"vinsia/{self.token}/terminal"
            self.client.publish(topic, str(message))
        else:
            try:
                requests.post(f"{self.http_host}/api/v1/terminal/log", json={"token": self.token, "message": str(message)}, timeout=2)
            except Exception:
                pass
        print(f"[VINSIA] {message}")

    def run(self):
        """Keepalive heartbeat"""
        now = time.time()
        if now - self.last_heartbeat > 30:
            self.last_heartbeat = now
            if self.is_connected and self.client:
                self.client.publish(f"vinsia/{self.token}/heartbeat", "1")
            else:
                try:
                    requests.post(f"{self.http_host}/api/v1/device/heartbeat", json={"token": self.token}, timeout=2)
                except Exception:
                    pass
