#!/usr/bin/env python3
"""
Philips Hue Bridge Control Script
Uses local REST API v1 (CLIP v1, port 80) for local-only operation.
"""

import json
import os
import sys
import time
import socket
import struct
import requests
from pathlib import Path
from typing import Optional, Dict, List, Any

CONFIG_FILE = os.path.expanduser("~/.openclaw/hue-config.json")
CONFIG_DIR = os.path.dirname(CONFIG_FILE)

# Color mapping for simple names
COLOR_MAP = {
    "red": {"hue": 0, "sat": 254},
    "blue": {"hue": 43680, "sat": 254},
    "green": {"hue": 21845, "sat": 254},
    "yellow": {"hue": 12750, "sat": 254},
    "orange": {"hue": 6142, "sat": 254},
    "warm": {"hue": 3640, "sat": 144},  # Warm white
    "cool": {"hue": 43680, "sat": 100},  # Cool white
    "white": {"hue": 0, "sat": 0},
}


class HueBridge:
    def __init__(self, bridge_ip: str, username: str):
        self.bridge_ip = bridge_ip
        self.username = username
        self.base_url = f"http://{bridge_ip}/api/{username}"
        
    def _request(self, method: str, path: str, data: Optional[Dict] = None) -> Any:
        """Make HTTP request to Hue bridge."""
        url = f"{self.base_url}/{path}"
        try:
            if method == "GET":
                resp = requests.get(url, timeout=5)
            elif method == "PUT":
                resp = requests.put(url, json=data, timeout=5)
            elif method == "POST":
                resp = requests.post(url, json=data, timeout=5)
            else:
                raise ValueError(f"Unknown method: {method}")
            
            resp.raise_for_status()
            return resp.json() if resp.text else {}
        except requests.exceptions.RequestException as e:
            print(f"Error: {e}", file=sys.stderr)
            return None

    def get_lights(self) -> Optional[Dict]:
        """Retrieve all lights."""
        return self._request("GET", "lights")

    def get_groups(self) -> Optional[Dict]:
        """Retrieve all groups (rooms)."""
        return self._request("GET", "groups")

    def get_scenes(self) -> Optional[Dict]:
        """Retrieve all scenes."""
        return self._request("GET", "scenes")

    def set_light_state(self, light_id: str, state: Dict) -> bool:
        """Set state of a specific light."""
        result = self._request("PUT", f"lights/{light_id}/state", state)
        return result is not None

    def set_group_state(self, group_id: str, state: Dict) -> bool:
        """Set state of a group (room)."""
        result = self._request("PUT", f"groups/{group_id}/action", state)
        return result is not None

    def activate_scene(self, group_id: str, scene_id: str) -> bool:
        """Activate a scene in a group."""
        state = {"scene": scene_id}
        return self.set_group_state(group_id, state)


def discover_bridge() -> Optional[str]:
    """
    Discover Hue bridge on local network using N-UPnP.
    Falls back to mDNS if available.
    """
    print("🔍 Searching for Hue bridge on local network...")
    
    # Try N-UPnP first (simplest, no dependencies)
    try:
        resp = requests.get("http://www.meethue.com/api/nupnp", timeout=3)
        bridges = resp.json()
        if bridges:
            bridge_ip = bridges[0].get("internalipaddress")
            if bridge_ip:
                print(f"✓ Found bridge at {bridge_ip}")
                return bridge_ip
    except Exception:
        pass
    
    # Try scanning common subnet for bridge (192.168.x.x)
    print("Scanning local subnet...")
    hostname = socket.gethostname()
    try:
        local_ip = socket.gethostbyname(hostname)
        subnet = ".".join(local_ip.split(".")[:3])
        
        # Check common IP ranges for a Hue bridge
        for i in range(1, 30):
            test_ip = f"{subnet}.{i}"
            try:
                resp = requests.get(f"http://{test_ip}/description.xml", timeout=0.5)
                if "Philips Hue" in resp.text or "hue" in resp.text.lower():
                    print(f"✓ Found bridge at {test_ip}")
                    return test_ip
            except:
                pass
    except Exception as e:
        print(f"Subnet scan failed: {e}", file=sys.stderr)
    
    print("❌ Could not find Hue bridge. Make sure it's on your network and powered on.", file=sys.stderr)
    return None


def setup(bridge_ip: str) -> bool:
    """
    Interactive setup: prompt user to press the bridge button and obtain API username.
    Saves config to ~/.openclaw/hue-config.json
    """
    print("\n🔧 Hue Setup")
    print(f"Bridge IP: {bridge_ip}")
    print("\nYou need to press the physical button on your Hue bridge.")
    input("Press Enter when you're ready, then press the bridge button within 30 seconds...")
    
    # Try to get username from bridge
    devicetype = "clawstin#openclaw"
    payload = {"devicetype": devicetype}
    
    for attempt in range(30):
        try:
            resp = requests.post(
                f"http://{bridge_ip}/api",
                json=payload,
                timeout=2
            )
            data = resp.json()
            
            # Check for success
            if isinstance(data, list) and len(data) > 0:
                if "success" in data[0]:
                    username = data[0]["success"]["username"]
                    print(f"\n✓ Got API username: {username}")
                    
                    # Save config
                    os.makedirs(CONFIG_DIR, exist_ok=True)
                    config = {
                        "bridge_ip": bridge_ip,
                        "username": username
                    }
                    with open(CONFIG_FILE, "w") as f:
                        json.dump(config, f, indent=2)
                    print(f"✓ Config saved to {CONFIG_FILE}")
                    return True
                elif "error" in data[0]:
                    error = data[0]["error"]["description"]
                    if "button not pressed" in error.lower():
                        print(f"⏳ Button not pressed yet... ({attempt+1}/30)")
                    else:
                        print(f"Error: {error}", file=sys.stderr)
        except Exception as e:
            print(f"Error: {e}", file=sys.stderr)
        
        time.sleep(1)
    
    print("❌ Setup failed: Button was not pressed within 30 seconds.", file=sys.stderr)
    return False


def load_config() -> Optional[HueBridge]:
    """Load config and return HueBridge instance, or None if not configured."""
    if not os.path.exists(CONFIG_FILE):
        print(f"Error: Config not found at {CONFIG_FILE}", file=sys.stderr)
        print("Run: python3 hue.py discover setup", file=sys.stderr)
        return None
    
    with open(CONFIG_FILE) as f:
        config = json.load(f)
    
    return HueBridge(config["bridge_ip"], config["username"])


def lights_on(bridge: HueBridge, group: Optional[str] = None) -> bool:
    """Turn on all lights or a specific group."""
    if group:
        groups = bridge.get_groups()
        if not groups:
            return False
        group_id = None
        for gid, gdata in groups.items():
            if gdata.get("name", "").lower() == group.lower():
                group_id = gid
                break
        if not group_id:
            print(f"Group '{group}' not found", file=sys.stderr)
            return False
        return bridge.set_group_state(group_id, {"on": True})
    else:
        lights = bridge.get_lights()
        if not lights:
            return False
        for light_id in lights:
            bridge.set_light_state(light_id, {"on": True})
        return True


def lights_off(bridge: HueBridge, group: Optional[str] = None) -> bool:
    """Turn off all lights or a specific group."""
    if group:
        groups = bridge.get_groups()
        if not groups:
            return False
        group_id = None
        for gid, gdata in groups.items():
            if gdata.get("name", "").lower() == group.lower():
                group_id = gid
                break
        if not group_id:
            print(f"Group '{group}' not found", file=sys.stderr)
            return False
        return bridge.set_group_state(group_id, {"on": False})
    else:
        lights = bridge.get_lights()
        if not lights:
            return False
        for light_id in lights:
            bridge.set_light_state(light_id, {"on": False})
        return True


def set_brightness(bridge: HueBridge, level_pct: int, group: Optional[str] = None) -> bool:
    """Set brightness 0-100%."""
    if level_pct < 0 or level_pct > 100:
        print("Brightness must be 0-100", file=sys.stderr)
        return False
    
    bri = int((level_pct / 100.0) * 254)
    state = {"bri": bri, "on": True if level_pct > 0 else False}
    
    if group:
        groups = bridge.get_groups()
        if not groups:
            return False
        group_id = None
        for gid, gdata in groups.items():
            if gdata.get("name", "").lower() == group.lower():
                group_id = gid
                break
        if not group_id:
            print(f"Group '{group}' not found", file=sys.stderr)
            return False
        return bridge.set_group_state(group_id, state)
    else:
        lights = bridge.get_lights()
        if not lights:
            return False
        for light_id in lights:
            bridge.set_light_state(light_id, state)
        return True


def set_color(bridge: HueBridge, color: str, group: Optional[str] = None) -> bool:
    """Set color (by name or hex)."""
    state = {"on": True}
    
    # Check color map first
    if color.lower() in COLOR_MAP:
        state.update(COLOR_MAP[color.lower()])
    elif color.startswith("#"):
        # Simple hex-to-Hue conversion (not perfect, but works)
        try:
            hex_color = color.lstrip("#")
            r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
            # Very basic RGB to HSV conversion
            max_c = max(r, g, b)
            min_c = min(r, g, b)
            delta = max_c - min_c
            
            if max_c == 0:
                hue = 0
            elif max_c == r:
                hue = int((60 * ((g - b) / delta) + 360) % 360 * 182.04)
            elif max_c == g:
                hue = int((60 * ((b - r) / delta) + 120) * 182.04)
            else:
                hue = int((60 * ((r - g) / delta) + 240) * 182.04)
            
            sat = int((delta / max_c) * 254) if max_c > 0 else 0
            state["hue"] = hue % 65535
            state["sat"] = sat
        except:
            print(f"Invalid color: {color}", file=sys.stderr)
            return False
    else:
        print(f"Unknown color: {color}", file=sys.stderr)
        return False
    
    if group:
        groups = bridge.get_groups()
        if not groups:
            return False
        group_id = None
        for gid, gdata in groups.items():
            if gdata.get("name", "").lower() == group.lower():
                group_id = gid
                break
        if not group_id:
            print(f"Group '{group}' not found", file=sys.stderr)
            return False
        return bridge.set_group_state(group_id, state)
    else:
        lights = bridge.get_lights()
        if not lights:
            return False
        for light_id in lights:
            bridge.set_light_state(light_id, state)
        return True


def set_scene(bridge: HueBridge, scene_name: str, group: Optional[str] = None) -> bool:
    """Activate a scene."""
    scenes = bridge.get_scenes()
    groups = bridge.get_groups()
    if not scenes or not groups:
        return False
    
    scene_id = None
    for sid, sdata in scenes.items():
        if sdata.get("name", "").lower() == scene_name.lower():
            scene_id = sid
            break
    
    if not scene_id:
        print(f"Scene '{scene_name}' not found", file=sys.stderr)
        return False
    
    if not group:
        # Use the scene's group
        group_id = scenes[scene_id].get("group")
    else:
        group_id = None
        for gid, gdata in groups.items():
            if gdata.get("name", "").lower() == group.lower():
                group_id = gid
                break
    
    if not group_id:
        print(f"Group '{group}' not found", file=sys.stderr)
        return False
    
    return bridge.activate_scene(group_id, scene_id)


def list_lights(bridge: HueBridge):
    """List all lights."""
    lights = bridge.get_lights()
    if not lights:
        return
    print("\nLights:")
    for light_id, data in lights.items():
        name = data.get("name", "Unknown")
        state = data.get("state", {})
        on = "ON" if state.get("on") else "OFF"
        bri = state.get("bri", 0)
        print(f"  {light_id}: {name} ({on}, brightness: {int((bri/254)*100)}%)")


def list_groups(bridge: HueBridge):
    """List all groups (rooms)."""
    groups = bridge.get_groups()
    if not groups:
        return
    print("\nGroups (Rooms):")
    for group_id, data in groups.items():
        name = data.get("name", "Unknown")
        lights = data.get("lights", [])
        on = data.get("action", {}).get("on", False)
        state = "ON" if on else "OFF"
        print(f"  {group_id}: {name} ({state}, {len(lights)} lights)")


def list_scenes(bridge: HueBridge):
    """List all scenes."""
    scenes = bridge.get_scenes()
    groups = bridge.get_groups()
    if not scenes:
        return
    print("\nScenes:")
    for scene_id, data in scenes.items():
        name = data.get("name", "Unknown")
        group_id = data.get("group")
        group_name = groups.get(group_id, {}).get("name", "Unknown") if groups else "Unknown"
        print(f"  {scene_id}: {name} (in {group_name})")


def main():
    if len(sys.argv) < 2:
        print("Usage: python3 hue.py <command> [args]")
        print("\nCommands:")
        print("  discover               - Find Hue bridge on network")
        print("  setup <bridge_ip>      - Interactive setup (press bridge button)")
        print("  on [group]             - Turn on lights (all or group)")
        print("  off [group]            - Turn off lights (all or group)")
        print("  brightness <0-100> [group] - Set brightness percentage")
        print("  color <name|#hex> [group]  - Set color (red, blue, warm, cool, etc.)")
        print("  scene <name> [group]   - Activate a scene")
        print("  list                   - List all lights, groups, and scenes")
        sys.exit(1)
    
    command = sys.argv[1].lower()
    
    if command == "discover":
        bridge_ip = discover_bridge()
        if bridge_ip:
            print(f"\nTo setup, run: python3 hue.py setup {bridge_ip}")
        sys.exit(0 if bridge_ip else 1)
    
    elif command == "setup":
        if len(sys.argv) < 3:
            bridge_ip = discover_bridge()
            if not bridge_ip:
                sys.exit(1)
        else:
            bridge_ip = sys.argv[2]
        success = setup(bridge_ip)
        sys.exit(0 if success else 1)
    
    # All other commands require config
    bridge = load_config()
    if not bridge:
        sys.exit(1)
    
    if command == "on":
        group = sys.argv[2] if len(sys.argv) > 2 else None
        success = lights_on(bridge, group)
        print("✓ Lights on" if success else "✗ Failed")
        sys.exit(0 if success else 1)
    
    elif command == "off":
        group = sys.argv[2] if len(sys.argv) > 2 else None
        success = lights_off(bridge, group)
        print("✓ Lights off" if success else "✗ Failed")
        sys.exit(0 if success else 1)
    
    elif command == "brightness":
        if len(sys.argv) < 3:
            print("Usage: python3 hue.py brightness <0-100> [group]")
            sys.exit(1)
        level = int(sys.argv[2])
        group = sys.argv[3] if len(sys.argv) > 3 else None
        success = set_brightness(bridge, level, group)
        print(f"✓ Brightness set to {level}%" if success else "✗ Failed")
        sys.exit(0 if success else 1)
    
    elif command == "color":
        if len(sys.argv) < 3:
            print("Usage: python3 hue.py color <name|#hex> [group]")
            sys.exit(1)
        color = sys.argv[2]
        group = sys.argv[3] if len(sys.argv) > 3 else None
        success = set_color(bridge, color, group)
        print(f"✓ Color set to {color}" if success else "✗ Failed")
        sys.exit(0 if success else 1)
    
    elif command == "scene":
        if len(sys.argv) < 3:
            print("Usage: python3 hue.py scene <name> [group]")
            sys.exit(1)
        scene_name = sys.argv[2]
        group = sys.argv[3] if len(sys.argv) > 3 else None
        success = set_scene(bridge, scene_name, group)
        print(f"✓ Scene '{scene_name}' activated" if success else "✗ Failed")
        sys.exit(0 if success else 1)
    
    elif command == "list":
        list_lights(bridge)
        list_groups(bridge)
        list_scenes(bridge)
    
    else:
        print(f"Unknown command: {command}")
        sys.exit(1)


if __name__ == "__main__":
    main()
