from __future__ import annotations

import os
from typing import Any
from urllib.parse import urlparse

import requests
from mcp.server.fastmcp import FastMCP

from tools._std import tool_error, tool_ok


mcp = FastMCP("hue-bridge")


def _bridge_ip(bridge_ip: str = "", bridge_url: str = "") -> str:
    value_ip = str(bridge_ip or os.getenv("HUE_BRIDGE_IP", "")).strip()
    if value_ip:
        return value_ip

    value_url = str(bridge_url or os.getenv("HUE_BRIDGE_URL", "")).strip()
    if value_url:
        parsed = urlparse(value_url if "://" in value_url else f"http://{value_url}")
        host = str(parsed.hostname or "").strip()
        if host:
            return host
        raise ValueError("bridge_url is invalid. Use e.g. https://192.168.1.50:443")

    raise ValueError("bridge_ip or bridge_url is required (arg or HUE_BRIDGE_IP/HUE_BRIDGE_URL env)")


def _app_key(application_key: str = "") -> str:
    value = str(application_key or os.getenv("HUE_APPLICATION_KEY", "")).strip()
    if not value:
        raise ValueError("application_key is required (arg or HUE_APPLICATION_KEY env)")
    return value


def _http_json(method: str, url: str, *, payload: dict | None = None) -> Any:
    response = requests.request(method=method, url=url, json=payload, timeout=10)
    response.raise_for_status()
    return response.json()


def _validate_range(name: str, value: int | None, minimum: int, maximum: int) -> int | None:
    if value is None:
        return None
    ivalue = int(value)
    if ivalue < minimum or ivalue > maximum:
        raise ValueError(f"{name} must be between {minimum} and {maximum}")
    return ivalue


@mcp.tool()
def hue_discover_bridges() -> dict:
    """Discover Hue Bridges on the local network using official discovery service."""
    try:
        data = _http_json("GET", "https://discovery.meethue.com/")
        bridges = []
        for item in (data or []):
            ip = item.get("internalipaddress")
            port = int(item.get("port") or 443)
            scheme = "https" if port == 443 else "http"
            base_url = f"{scheme}://{ip}:{port}" if ip else None
            bridges.append(
                {
                    "id": item.get("id"),
                    "ip": ip,
                    "port": port,
                    "base_url": base_url,
                }
            )
        return {"ok": True, "bridges": bridges}
    except Exception as e:
        return {"ok": False, "error": str(e)}


@mcp.tool()
def hue_create_application_key(
    bridge_ip: str = "",
    bridge_url: str = "",
    device_type: str = "agent#copilot",
) -> dict:
    """Create a Hue application key. Press the physical Bridge button first, then call this tool within ~30s."""
    try:
        ip = _bridge_ip(bridge_ip, bridge_url)
        payload = {"devicetype": str(device_type or "agent#copilot")}
        data = _http_json("POST", f"http://{ip}/api", payload=payload)
        if not isinstance(data, list) or not data:
            return {"ok": False, "error": f"Unexpected Hue response: {data}"}

        item = data[0]
        if "success" in item and item["success"].get("username"):
            username = item["success"]["username"]
            return {
                "ok": True,
                "bridge_ip": ip,
                "application_key": username,
                "next_step": "Set HUE_APPLICATION_KEY to this value in MCP server env and refresh MCP status.",
            }

        err = item.get("error", {})
        if int(err.get("type", -1)) == 101:
            return {
                "ok": False,
                "bridge_ip": ip,
                "link_button_required": True,
                "error": err.get("description", "link button not pressed"),
                "hint": "Press the physical button on the Hue Bridge, then call hue_create_application_key again within 30 seconds.",
            }

        return {"ok": False, "bridge_ip": ip, "error": err or item}
    except Exception as e:
        return {"ok": False, "error": str(e)}


@mcp.tool()
def hue_validate_application_key(
    bridge_ip: str = "",
    bridge_url: str = "",
    application_key: str = "",
) -> dict:
    """Validate a Hue application key by reading bridge config."""
    try:
        ip = _bridge_ip(bridge_ip, bridge_url)
        key = _app_key(application_key)
        data = _http_json("GET", f"http://{ip}/api/{key}/config")
        if isinstance(data, list) and data and isinstance(data[0], dict) and data[0].get("error"):
            return {"ok": False, "bridge_ip": ip, "error": data[0]["error"]}
        return {
            "ok": True,
            "bridge_ip": ip,
            "name": data.get("name"),
            "bridge_id": data.get("bridgeid"),
            "model": data.get("modelid"),
        }
    except Exception as e:
        return {"ok": False, "error": str(e)}


@mcp.tool()
def hue_list_lights(
    bridge_ip: str = "",
    bridge_url: str = "",
    application_key: str = "",
) -> dict:
    """List all lights with basic state and the room they belong to."""
    try:
        ip = _bridge_ip(bridge_ip, bridge_url)
        key = _app_key(application_key)
        data = _http_json("GET", f"http://{ip}/api/{key}/lights")
        if isinstance(data, list) and data and isinstance(data[0], dict) and data[0].get("error"):
            return {"ok": False, "bridge_ip": ip, "error": data[0]["error"]}

        # Build light -> room mapping from groups
        light_room: dict[str, str] = {}
        try:
            groups_data = _http_json("GET", f"http://{ip}/api/{key}/groups")
            if isinstance(groups_data, dict):
                for group in groups_data.values():
                    if not isinstance(group, dict):
                        continue
                    if group.get("type") not in ("Room", "Zone"):
                        continue
                    room_name = group.get("name", "")
                    for lid in group.get("lights", []):
                        light_room[str(lid)] = room_name
        except Exception:
            pass

        lights: list[dict] = []
        for light_id, meta in (data or {}).items():
            state = meta.get("state", {}) if isinstance(meta, dict) else {}
            lights.append(
                {
                    "id": light_id,
                    "name": meta.get("name") if isinstance(meta, dict) else None,
                    "type": meta.get("type") if isinstance(meta, dict) else None,
                    "room": light_room.get(str(light_id)),
                    "on": state.get("on"),
                    "reachable": state.get("reachable"),
                    "bri": state.get("bri"),
                }
            )
        return {"ok": True, "bridge_ip": ip, "lights": lights}
    except Exception as e:
        return {"ok": False, "error": str(e)}


@mcp.tool()
def hue_set_light_state(
    light_id: str,
    on: bool | None = None,
    bri: int | None = None,
    hue: int | None = None,
    sat: int | None = None,
    transitiontime: int | None = None,
    bridge_ip: str = "",
    bridge_url: str = "",
    application_key: str = "",
) -> dict:
    """Set one light state. Send only fields you want to change."""
    try:
        ip = _bridge_ip(bridge_ip, bridge_url)
        key = _app_key(application_key)
        lid = str(light_id or "").strip()
        if not lid:
            return {"ok": False, "error": "light_id is required"}

        payload: dict[str, Any] = {}
        if on is not None:
            payload["on"] = bool(on)
        bri_value = _validate_range("bri", bri, 1, 254)
        if bri_value is not None:
            payload["bri"] = bri_value
        hue_value = _validate_range("hue", hue, 0, 65535)
        if hue_value is not None:
            payload["hue"] = hue_value
        sat_value = _validate_range("sat", sat, 0, 254)
        if sat_value is not None:
            payload["sat"] = sat_value
        transition_value = _validate_range("transitiontime", transitiontime, 0, 65535)
        if transition_value is not None:
            payload["transitiontime"] = transition_value

        if not payload:
            return {
                "ok": False,
                "error": "No state updates provided. Use at least one of: on, bri, hue, sat, transitiontime.",
            }

        data = _http_json("PUT", f"http://{ip}/api/{key}/lights/{lid}/state", payload=payload)
        errors = [item.get("error") for item in (data or []) if isinstance(item, dict) and item.get("error")]
        if errors:
            return {"ok": False, "bridge_ip": ip, "light_id": lid, "error": errors}

        return {"ok": True, "bridge_ip": ip, "light_id": lid, "updates": payload, "raw": data}
    except Exception as e:
        return {"ok": False, "error": str(e)}


@mcp.tool()
def hue_list_rooms(
    bridge_ip: str = "",
    bridge_url: str = "",
    application_key: str = "",
) -> dict:
    """List all rooms with their lights and current state (on/off, reachable)."""
    try:
        ip = _bridge_ip(bridge_ip, bridge_url)
        key = _app_key(application_key)

        groups_data = _http_json("GET", f"http://{ip}/api/{key}/groups")
        if isinstance(groups_data, list) and groups_data and isinstance(groups_data[0], dict) and groups_data[0].get("error"):
            return {"ok": False, "bridge_ip": ip, "error": groups_data[0]["error"]}

        lights_data = _http_json("GET", f"http://{ip}/api/{key}/lights")
        if isinstance(lights_data, list) and lights_data and isinstance(lights_data[0], dict) and lights_data[0].get("error"):
            return {"ok": False, "bridge_ip": ip, "error": lights_data[0]["error"]}

        rooms = []
        claimed_light_ids: set[str] = set()
        for group_id, group in (groups_data or {}).items():
            if not isinstance(group, dict):
                continue
            if group.get("type") not in ("Room", "Zone"):
                continue
            light_ids = group.get("lights", [])
            lights_in_room = []
            for lid in light_ids:
                claimed_light_ids.add(str(lid))
                lmeta = (lights_data or {}).get(str(lid), {})
                state = lmeta.get("state", {}) if isinstance(lmeta, dict) else {}
                lights_in_room.append({
                    "id": lid,
                    "name": lmeta.get("name") if isinstance(lmeta, dict) else lid,
                    "on": state.get("on"),
                    "reachable": state.get("reachable"),
                    "bri": state.get("bri"),
                })
            rooms.append({
                "group_id": group_id,
                "name": group.get("name"),
                "type": group.get("type"),
                "class": group.get("class"),
                "any_on": group.get("state", {}).get("any_on"),
                "all_on": group.get("state", {}).get("all_on"),
                "light_count": len(lights_in_room),
                "lights": lights_in_room,
            })

        # Lights that exist on the bridge but aren't assigned to any Room/Zone
        unassigned: list[dict] = []
        for lid, lmeta in (lights_data or {}).items():
            if str(lid) in claimed_light_ids:
                continue
            if not isinstance(lmeta, dict):
                continue
            state = lmeta.get("state", {})
            unassigned.append({
                "id": lid,
                "name": lmeta.get("name", lid),
                "on": state.get("on"),
                "reachable": state.get("reachable"),
                "bri": state.get("bri"),
            })

        return {"ok": True, "bridge_ip": ip, "rooms": rooms, "unassigned_lights": unassigned}
    except Exception as e:
        return {"ok": False, "error": str(e)}


@mcp.tool()
def hue_set_room_state(
    room_name: str = "",
    group_id: str = "",
    on: bool | None = None,
    bri: int | None = None,
    hue: int | None = None,
    sat: int | None = None,
    transitiontime: int | None = None,
    all_rooms: bool = False,
    bridge_ip: str = "",
    bridge_url: str = "",
    application_key: str = "",
) -> dict:
    """Control lights in a room or all rooms at once.

    Specify room by name (room_name) or group_id. Use all_rooms=true to affect every room.
    """
    try:
        ip = _bridge_ip(bridge_ip, bridge_url)
        key = _app_key(application_key)

        payload: dict[str, Any] = {}
        if on is not None:
            payload["on"] = bool(on)
        bri_value = _validate_range("bri", bri, 1, 254)
        if bri_value is not None:
            payload["bri"] = bri_value
        hue_value = _validate_range("hue", hue, 0, 65535)
        if hue_value is not None:
            payload["hue"] = hue_value
        sat_value = _validate_range("sat", sat, 0, 254)
        if sat_value is not None:
            payload["sat"] = sat_value
        transition_value = _validate_range("transitiontime", transitiontime, 0, 65535)
        if transition_value is not None:
            payload["transitiontime"] = transition_value

        if not payload:
            return {"ok": False, "error": "No state updates provided. Use at least one of: on, bri, hue, sat, transitiontime."}

        groups_data = _http_json("GET", f"http://{ip}/api/{key}/groups")
        if isinstance(groups_data, list) and groups_data and isinstance(groups_data[0], dict) and groups_data[0].get("error"):
            return {"ok": False, "bridge_ip": ip, "error": groups_data[0]["error"]}

        # Collect target group IDs
        target_ids: list[str] = []
        if all_rooms:
            target_ids = [gid for gid, g in (groups_data or {}).items() if isinstance(g, dict) and g.get("type") in ("Room", "Zone")]
        elif group_id:
            target_ids = [str(group_id).strip()]
        elif room_name:
            needle = room_name.strip().lower()
            for gid, g in (groups_data or {}).items():
                if isinstance(g, dict) and g.get("type") in ("Room", "Zone") and g.get("name", "").lower() == needle:
                    target_ids.append(gid)
                    break

        if not target_ids:
            available = [g.get("name") for g in (groups_data or {}).values() if isinstance(g, dict) and g.get("type") in ("Room", "Zone")]
            return {"ok": False, "error": f"Room not found. Available rooms: {available}"}

        results = []
        for gid in target_ids:
            gname = (groups_data or {}).get(gid, {}).get("name", gid)
            data = _http_json("PUT", f"http://{ip}/api/{key}/groups/{gid}/action", payload=payload)
            errors = [item.get("error") for item in (data or []) if isinstance(item, dict) and item.get("error")]
            results.append({"group_id": gid, "name": gname, "ok": not errors, "error": errors or None, "updates": payload})

        all_ok = all(r["ok"] for r in results)
        return {"ok": all_ok, "bridge_ip": ip, "results": results}
    except Exception as e:
        return {"ok": False, "error": str(e)}


if __name__ == "__main__":
    mcp.run()