import logging
from typing import Any, cast

from PyViCare.PyViCareDevice import Device
from PyViCare.PyViCareUtils import PyViCareNotSupportedFeatureError, handleNotSupported, handleAPICommandErrors

logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())


class RoomControl(Device):
    """Viessmann RoomControl virtual device.

    Aggregates room sensor data and heating programs.
    Used to enrich physical Zigbee devices with room data.
    """

    @handleNotSupported
    def getAvailableRooms(self) -> list[str]:
        return cast(list[str], self.service.getProperty("rooms")["properties"]["enabled"]["value"])

    def getRoomActorIds(self, room_id: str) -> list[str]:
        """Return list of actor device IDs for a room."""
        try:
            actors = self.service.getProperty(f"rooms.{room_id}")["properties"]["actors"]["value"]
            return [str(a["deviceId"]) for a in actors]
        except (PyViCareNotSupportedFeatureError, KeyError):
            return []

    def getRoomName(self, room_id: str) -> str | None:
        try:
            return str(self.service.getProperty(f"rooms.{room_id}")["properties"]["name"]["value"])
        except (PyViCareNotSupportedFeatureError, KeyError):
            return None

    def getRoomType(self, room_id: str) -> str | None:
        try:
            return str(self.service.getProperty(f"rooms.{room_id}")["properties"]["type"]["value"])
        except (PyViCareNotSupportedFeatureError, KeyError):
            return None

    # --- Sensors ---

    def getRoomTemperature(self, room_id: str) -> float:
        return float(self.service.getProperty(f"rooms.{room_id}.sensors.temperature")["properties"]["value"]["value"])

    def getRoomHumidity(self, room_id: str) -> float:
        return float(self.service.getProperty(f"rooms.{room_id}.sensors.humidity")["properties"]["value"]["value"])

    def getRoomCO2(self, room_id: str) -> int:
        return int(self.service.getProperty(f"rooms.{room_id}.sensors.co2")["properties"]["value"]["value"])

    def getRoomCondensationRisk(self, room_id: str) -> bool:
        return bool(self.service.getProperty(f"rooms.{room_id}.condensationRisk")["properties"]["value"]["value"])

    # --- Operating state ---

    def getRoomOperatingStateLevel(self, room_id: str) -> str:
        return str(self.service.getProperty(f"rooms.{room_id}.operating.state")["properties"]["level"]["value"])

    def getRoomOperatingStateDemand(self, room_id: str) -> str:
        return str(self.service.getProperty(f"rooms.{room_id}.operating.state")["properties"]["demand"]["value"])

    def getRoomOperatingStateReason(self, room_id: str) -> str:
        return str(self.service.getProperty(f"rooms.{room_id}.operating.state")["properties"]["reason"]["value"])

    # --- Heating programs ---

    def getRoomNormalHeatingTemperature(self, room_id: str) -> float:
        return float(self.service.getProperty(f"rooms.{room_id}.operating.programs.normalHeating")["properties"]["temperature"]["value"])

    @handleAPICommandErrors
    def setRoomNormalHeatingTemperature(self, room_id: str, temperature: float) -> None:
        self.service.setProperty(f"rooms.{room_id}.operating.programs.normalHeating", "setTemperature",
                                 {"targetTemperature": temperature})

    def getRoomReducedHeatingTemperature(self, room_id: str) -> float:
        return float(self.service.getProperty(f"rooms.{room_id}.operating.programs.reducedHeating")["properties"]["temperature"]["value"])

    @handleAPICommandErrors
    def setRoomReducedHeatingTemperature(self, room_id: str, temperature: float) -> None:
        self.service.setProperty(f"rooms.{room_id}.operating.programs.reducedHeating", "setTemperature",
                                 {"targetTemperature": temperature})

    def getRoomComfortHeatingTemperature(self, room_id: str) -> float:
        return float(self.service.getProperty(f"rooms.{room_id}.operating.programs.comfortHeating")["properties"]["temperature"]["value"])

    @handleAPICommandErrors
    def setRoomComfortHeatingTemperature(self, room_id: str, temperature: float) -> None:
        self.service.setProperty(f"rooms.{room_id}.operating.programs.comfortHeating", "setTemperature",
                                 {"targetTemperature": temperature})

    # --- Schedule ---

    def getRoomSchedule(self, room_id: str) -> dict[str, Any]:
        props = self.service.getProperty(f"rooms.{room_id}.schedule")["properties"]
        return {
            "active": props["active"]["value"],
            "mon": props["entries"]["value"]["mon"],
            "tue": props["entries"]["value"]["tue"],
            "wed": props["entries"]["value"]["wed"],
            "thu": props["entries"]["value"]["thu"],
            "fri": props["entries"]["value"]["fri"],
            "sat": props["entries"]["value"]["sat"],
            "sun": props["entries"]["value"]["sun"],
        }

    # --- Quick modes ---

    def getRoomManualTillNextScheduleActive(self, room_id: str) -> bool:
        return bool(self.service.getProperty(
            f"rooms.{room_id}.quickmodes.manualTillNextSchedule")["properties"]["active"]["value"])

    @handleAPICommandErrors
    def activateRoomManualTillNextSchedule(self, room_id: str, temperature: float) -> None:
        self.service.setProperty(f"rooms.{room_id}.quickmodes.manualTillNextSchedule", "activate",
                                 {"temperature": temperature})

    @handleAPICommandErrors
    def deactivateRoomManualTillNextSchedule(self, room_id: str) -> None:
        self.service.setProperty(f"rooms.{room_id}.quickmodes.manualTillNextSchedule", "deactivate", {})

    # --- Mapping ---

    def buildActorRoomMap(self) -> dict[str, str]:
        """Build a mapping of actor device ID -> room ID."""
        actor_map: dict[str, str] = {}
        try:
            rooms = self.getAvailableRooms()
        except PyViCareNotSupportedFeatureError:
            return actor_map

        for room_id in rooms:
            for actor_id in self.getRoomActorIds(room_id):
                actor_map[actor_id] = room_id
        return actor_map
