"""Support for HomematicIP Cloud binary sensor."""
from __future__ import annotations

from typing import Any

from homematicip.aio.device import (
    AsyncAccelerationSensor,
    AsyncContactInterface,
    AsyncDevice,
    AsyncFullFlushContactInterface,
    AsyncFullFlushContactInterface6,
    AsyncMotionDetectorIndoor,
    AsyncMotionDetectorOutdoor,
    AsyncMotionDetectorPushButton,
    AsyncPluggableMainsFailureSurveillance,
    AsyncPresenceDetectorIndoor,
    AsyncRainSensor,
    AsyncRotaryHandleSensor,
    AsyncShutterContact,
    AsyncShutterContactMagnetic,
    AsyncSmokeDetector,
    AsyncTiltVibrationSensor,
    AsyncWaterSensor,
    AsyncWeatherSensor,
    AsyncWeatherSensorPlus,
    AsyncWeatherSensorPro,
    AsyncWiredInput32,
)
from homematicip.aio.group import AsyncSecurityGroup, AsyncSecurityZoneGroup
from homematicip.base.enums import SmokeDetectorAlarmType, WindowState

from homeassistant.components.binary_sensor import (
    BinarySensorDeviceClass,
    BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import DOMAIN as HMIPC_DOMAIN, HomematicipGenericEntity
from .hap import HomematicipHAP

ATTR_ACCELERATION_SENSOR_MODE = "acceleration_sensor_mode"
ATTR_ACCELERATION_SENSOR_NEUTRAL_POSITION = "acceleration_sensor_neutral_position"
ATTR_ACCELERATION_SENSOR_SENSITIVITY = "acceleration_sensor_sensitivity"
ATTR_ACCELERATION_SENSOR_TRIGGER_ANGLE = "acceleration_sensor_trigger_angle"
ATTR_INTRUSION_ALARM = "intrusion_alarm"
ATTR_MOISTURE_DETECTED = "moisture_detected"
ATTR_MOTION_DETECTED = "motion_detected"
ATTR_POWER_MAINS_FAILURE = "power_mains_failure"
ATTR_PRESENCE_DETECTED = "presence_detected"
ATTR_SMOKE_DETECTOR_ALARM = "smoke_detector_alarm"
ATTR_TODAY_SUNSHINE_DURATION = "today_sunshine_duration_in_minutes"
ATTR_WATER_LEVEL_DETECTED = "water_level_detected"
ATTR_WINDOW_STATE = "window_state"

GROUP_ATTRIBUTES = {
    "moistureDetected": ATTR_MOISTURE_DETECTED,
    "motionDetected": ATTR_MOTION_DETECTED,
    "powerMainsFailure": ATTR_POWER_MAINS_FAILURE,
    "presenceDetected": ATTR_PRESENCE_DETECTED,
    "waterlevelDetected": ATTR_WATER_LEVEL_DETECTED,
}

SAM_DEVICE_ATTRIBUTES = {
    "accelerationSensorNeutralPosition": ATTR_ACCELERATION_SENSOR_NEUTRAL_POSITION,
    "accelerationSensorMode": ATTR_ACCELERATION_SENSOR_MODE,
    "accelerationSensorSensitivity": ATTR_ACCELERATION_SENSOR_SENSITIVITY,
    "accelerationSensorTriggerAngle": ATTR_ACCELERATION_SENSOR_TRIGGER_ANGLE,
}


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up the HomematicIP Cloud binary sensor from a config entry."""
    hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id]
    entities: list[HomematicipGenericEntity] = [HomematicipCloudConnectionSensor(hap)]
    for device in hap.home.devices:
        if isinstance(device, AsyncAccelerationSensor):
            entities.append(HomematicipAccelerationSensor(hap, device))
        if isinstance(device, AsyncTiltVibrationSensor):
            entities.append(HomematicipTiltVibrationSensor(hap, device))
        if isinstance(device, AsyncWiredInput32):
            for channel in range(1, 33):
                entities.append(
                    HomematicipMultiContactInterface(hap, device, channel=channel)
                )
        elif isinstance(device, AsyncFullFlushContactInterface6):
            for channel in range(1, 7):
                entities.append(
                    HomematicipMultiContactInterface(hap, device, channel=channel)
                )
        elif isinstance(
            device, (AsyncContactInterface, AsyncFullFlushContactInterface)
        ):
            entities.append(HomematicipContactInterface(hap, device))
        if isinstance(
            device,
            (AsyncShutterContact, AsyncShutterContactMagnetic),
        ):
            entities.append(HomematicipShutterContact(hap, device))
        if isinstance(device, AsyncRotaryHandleSensor):
            entities.append(HomematicipShutterContact(hap, device, True))
        if isinstance(
            device,
            (
                AsyncMotionDetectorIndoor,
                AsyncMotionDetectorOutdoor,
                AsyncMotionDetectorPushButton,
            ),
        ):
            entities.append(HomematicipMotionDetector(hap, device))
        if isinstance(device, AsyncPluggableMainsFailureSurveillance):
            entities.append(
                HomematicipPluggableMainsFailureSurveillanceSensor(hap, device)
            )
        if isinstance(device, AsyncPresenceDetectorIndoor):
            entities.append(HomematicipPresenceDetector(hap, device))
        if isinstance(device, AsyncSmokeDetector):
            entities.append(HomematicipSmokeDetector(hap, device))
        if isinstance(device, AsyncWaterSensor):
            entities.append(HomematicipWaterDetector(hap, device))
        if isinstance(
            device, (AsyncRainSensor, AsyncWeatherSensorPlus, AsyncWeatherSensorPro)
        ):
            entities.append(HomematicipRainSensor(hap, device))
        if isinstance(
            device, (AsyncWeatherSensor, AsyncWeatherSensorPlus, AsyncWeatherSensorPro)
        ):
            entities.append(HomematicipStormSensor(hap, device))
            entities.append(HomematicipSunshineSensor(hap, device))
        if isinstance(device, AsyncDevice) and device.lowBat is not None:
            entities.append(HomematicipBatterySensor(hap, device))

    for group in hap.home.groups:
        if isinstance(group, AsyncSecurityGroup):
            entities.append(HomematicipSecuritySensorGroup(hap, device=group))
        elif isinstance(group, AsyncSecurityZoneGroup):
            entities.append(HomematicipSecurityZoneSensorGroup(hap, device=group))

    if entities:
        async_add_entities(entities)


class HomematicipCloudConnectionSensor(HomematicipGenericEntity, BinarySensorEntity):
    """Representation of the HomematicIP cloud connection sensor."""

    def __init__(self, hap: HomematicipHAP) -> None:
        """Initialize the cloud connection sensor."""
        super().__init__(hap, hap.home)

    @property
    def name(self) -> str:
        """Return the name cloud connection entity."""

        name = "Cloud Connection"
        # Add a prefix to the name if the homematic ip home has a name.
        return name if not self._home.name else f"{self._home.name} {name}"

    @property
    def device_info(self) -> DeviceInfo:
        """Return device specific attributes."""
        # Adds a sensor to the existing HAP device
        return DeviceInfo(
            identifiers={
                # Serial numbers of Homematic IP device
                (HMIPC_DOMAIN, self._home.id)
            }
        )

    @property
    def icon(self) -> str:
        """Return the icon of the access point entity."""
        return (
            "mdi:access-point-network"
            if self._home.connected
            else "mdi:access-point-network-off"
        )

    @property
    def is_on(self) -> bool:
        """Return true if hap is connected to cloud."""
        return self._home.connected

    @property
    def available(self) -> bool:
        """Sensor is always available."""
        return True


class HomematicipBaseActionSensor(HomematicipGenericEntity, BinarySensorEntity):
    """Representation of the HomematicIP base action sensor."""

    @property
    def device_class(self) -> str:
        """Return the class of this sensor."""
        return BinarySensorDeviceClass.MOVING

    @property
    def is_on(self) -> bool:
        """Return true if acceleration is detected."""
        return self._device.accelerationSensorTriggered

    @property
    def extra_state_attributes(self) -> dict[str, Any]:
        """Return the state attributes of the acceleration sensor."""
        state_attr = super().extra_state_attributes

        for attr, attr_key in SAM_DEVICE_ATTRIBUTES.items():
            if attr_value := getattr(self._device, attr, None):
                state_attr[attr_key] = attr_value

        return state_attr


class HomematicipAccelerationSensor(HomematicipBaseActionSensor):
    """Representation of the HomematicIP acceleration sensor."""


class HomematicipTiltVibrationSensor(HomematicipBaseActionSensor):
    """Representation of the HomematicIP tilt vibration sensor."""


class HomematicipMultiContactInterface(HomematicipGenericEntity, BinarySensorEntity):
    """Representation of the HomematicIP multi room/area contact interface."""

    def __init__(
        self,
        hap: HomematicipHAP,
        device,
        channel=1,
        is_multi_channel=True,
    ) -> None:
        """Initialize the multi contact entity."""
        super().__init__(
            hap, device, channel=channel, is_multi_channel=is_multi_channel
        )

    @property
    def device_class(self) -> str:
        """Return the class of this sensor."""
        return BinarySensorDeviceClass.OPENING

    @property
    def is_on(self) -> bool | None:
        """Return true if the contact interface is on/open."""
        if self._device.functionalChannels[self._channel].windowState is None:
            return None
        return (
            self._device.functionalChannels[self._channel].windowState
            != WindowState.CLOSED
        )


class HomematicipContactInterface(HomematicipMultiContactInterface, BinarySensorEntity):
    """Representation of the HomematicIP contact interface."""

    def __init__(self, hap: HomematicipHAP, device) -> None:
        """Initialize the multi contact entity."""
        super().__init__(hap, device, is_multi_channel=False)


class HomematicipShutterContact(HomematicipMultiContactInterface, BinarySensorEntity):
    """Representation of the HomematicIP shutter contact."""

    def __init__(
        self, hap: HomematicipHAP, device, has_additional_state: bool = False
    ) -> None:
        """Initialize the shutter contact."""
        super().__init__(hap, device, is_multi_channel=False)
        self.has_additional_state = has_additional_state

    @property
    def device_class(self) -> str:
        """Return the class of this sensor."""
        return BinarySensorDeviceClass.DOOR

    @property
    def extra_state_attributes(self) -> dict[str, Any]:
        """Return the state attributes of the Shutter Contact."""
        state_attr = super().extra_state_attributes

        if self.has_additional_state:
            window_state = getattr(self._device, "windowState", None)
            if window_state and window_state != WindowState.CLOSED:
                state_attr[ATTR_WINDOW_STATE] = window_state

        return state_attr


class HomematicipMotionDetector(HomematicipGenericEntity, BinarySensorEntity):
    """Representation of the HomematicIP motion detector."""

    @property
    def device_class(self) -> str:
        """Return the class of this sensor."""
        return BinarySensorDeviceClass.MOTION

    @property
    def is_on(self) -> bool:
        """Return true if motion is detected."""
        return self._device.motionDetected


class HomematicipPresenceDetector(HomematicipGenericEntity, BinarySensorEntity):
    """Representation of the HomematicIP presence detector."""

    @property
    def device_class(self) -> str:
        """Return the class of this sensor."""
        return BinarySensorDeviceClass.PRESENCE

    @property
    def is_on(self) -> bool:
        """Return true if presence is detected."""
        return self._device.presenceDetected


class HomematicipSmokeDetector(HomematicipGenericEntity, BinarySensorEntity):
    """Representation of the HomematicIP smoke detector."""

    @property
    def device_class(self) -> str:
        """Return the class of this sensor."""
        return BinarySensorDeviceClass.SMOKE

    @property
    def is_on(self) -> bool:
        """Return true if smoke is detected."""
        if self._device.smokeDetectorAlarmType:
            return (
                self._device.smokeDetectorAlarmType
                == SmokeDetectorAlarmType.PRIMARY_ALARM
            )
        return False


class HomematicipWaterDetector(HomematicipGenericEntity, BinarySensorEntity):
    """Representation of the HomematicIP water detector."""

    @property
    def device_class(self) -> str:
        """Return the class of this sensor."""
        return BinarySensorDeviceClass.MOISTURE

    @property
    def is_on(self) -> bool:
        """Return true, if moisture or waterlevel is detected."""
        return self._device.moistureDetected or self._device.waterlevelDetected


class HomematicipStormSensor(HomematicipGenericEntity, BinarySensorEntity):
    """Representation of the HomematicIP storm sensor."""

    def __init__(self, hap: HomematicipHAP, device) -> None:
        """Initialize storm sensor."""
        super().__init__(hap, device, "Storm")

    @property
    def icon(self) -> str:
        """Return the icon."""
        return "mdi:weather-windy" if self.is_on else "mdi:pinwheel-outline"

    @property
    def is_on(self) -> bool:
        """Return true, if storm is detected."""
        return self._device.storm


class HomematicipRainSensor(HomematicipGenericEntity, BinarySensorEntity):
    """Representation of the HomematicIP rain sensor."""

    def __init__(self, hap: HomematicipHAP, device) -> None:
        """Initialize rain sensor."""
        super().__init__(hap, device, "Raining")

    @property
    def device_class(self) -> str:
        """Return the class of this sensor."""
        return BinarySensorDeviceClass.MOISTURE

    @property
    def is_on(self) -> bool:
        """Return true, if it is raining."""
        return self._device.raining


class HomematicipSunshineSensor(HomematicipGenericEntity, BinarySensorEntity):
    """Representation of the HomematicIP sunshine sensor."""

    def __init__(self, hap: HomematicipHAP, device) -> None:
        """Initialize sunshine sensor."""
        super().__init__(hap, device, post="Sunshine")

    @property
    def device_class(self) -> str:
        """Return the class of this sensor."""
        return BinarySensorDeviceClass.LIGHT

    @property
    def is_on(self) -> bool:
        """Return true if sun is shining."""
        return self._device.sunshine

    @property
    def extra_state_attributes(self) -> dict[str, Any]:
        """Return the state attributes of the illuminance sensor."""
        state_attr = super().extra_state_attributes

        today_sunshine_duration = getattr(self._device, "todaySunshineDuration", None)
        if today_sunshine_duration:
            state_attr[ATTR_TODAY_SUNSHINE_DURATION] = today_sunshine_duration

        return state_attr


class HomematicipBatterySensor(HomematicipGenericEntity, BinarySensorEntity):
    """Representation of the HomematicIP low battery sensor."""

    def __init__(self, hap: HomematicipHAP, device) -> None:
        """Initialize battery sensor."""
        super().__init__(hap, device, post="Battery")

    @property
    def device_class(self) -> str:
        """Return the class of this sensor."""
        return BinarySensorDeviceClass.BATTERY

    @property
    def is_on(self) -> bool:
        """Return true if battery is low."""
        return self._device.lowBat


class HomematicipPluggableMainsFailureSurveillanceSensor(
    HomematicipGenericEntity, BinarySensorEntity
):
    """Representation of the HomematicIP pluggable mains failure surveillance sensor."""

    def __init__(self, hap: HomematicipHAP, device) -> None:
        """Initialize pluggable mains failure surveillance sensor."""
        super().__init__(hap, device)

    @property
    def device_class(self) -> str:
        """Return the class of this sensor."""
        return BinarySensorDeviceClass.POWER

    @property
    def is_on(self) -> bool:
        """Return true if power mains fails."""
        return not self._device.powerMainsFailure


class HomematicipSecurityZoneSensorGroup(HomematicipGenericEntity, BinarySensorEntity):
    """Representation of the HomematicIP security zone sensor group."""

    def __init__(self, hap: HomematicipHAP, device, post: str = "SecurityZone") -> None:
        """Initialize security zone group."""
        device.modelType = f"HmIP-{post}"
        super().__init__(hap, device, post=post)

    @property
    def device_class(self) -> str:
        """Return the class of this sensor."""
        return BinarySensorDeviceClass.SAFETY

    @property
    def available(self) -> bool:
        """Security-Group available."""
        # A security-group must be available, and should not be affected by
        # the individual availability of group members.
        return True

    @property
    def extra_state_attributes(self) -> dict[str, Any]:
        """Return the state attributes of the security zone group."""
        state_attr = super().extra_state_attributes

        for attr, attr_key in GROUP_ATTRIBUTES.items():
            if attr_value := getattr(self._device, attr, None):
                state_attr[attr_key] = attr_value

        window_state = getattr(self._device, "windowState", None)
        if window_state and window_state != WindowState.CLOSED:
            state_attr[ATTR_WINDOW_STATE] = str(window_state)

        return state_attr

    @property
    def is_on(self) -> bool:
        """Return true if security issue detected."""
        if (
            self._device.motionDetected
            or self._device.presenceDetected
            or self._device.unreach
            or self._device.sabotage
        ):
            return True

        if (
            self._device.windowState is not None
            and self._device.windowState != WindowState.CLOSED
        ):
            return True
        return False


class HomematicipSecuritySensorGroup(
    HomematicipSecurityZoneSensorGroup, BinarySensorEntity
):
    """Representation of the HomematicIP security group."""

    def __init__(self, hap: HomematicipHAP, device) -> None:
        """Initialize security group."""
        super().__init__(hap, device, post="Sensors")

    @property
    def extra_state_attributes(self) -> dict[str, Any]:
        """Return the state attributes of the security group."""
        state_attr = super().extra_state_attributes

        smoke_detector_at = getattr(self._device, "smokeDetectorAlarmType", None)
        if smoke_detector_at:
            if smoke_detector_at == SmokeDetectorAlarmType.PRIMARY_ALARM:
                state_attr[ATTR_SMOKE_DETECTOR_ALARM] = str(smoke_detector_at)
            if smoke_detector_at == SmokeDetectorAlarmType.INTRUSION_ALARM:
                state_attr[ATTR_INTRUSION_ALARM] = str(smoke_detector_at)
        return state_attr

    @property
    def is_on(self) -> bool:
        """Return true if safety issue detected."""
        if super().is_on:
            # parent is on
            return True

        if (
            self._device.powerMainsFailure
            or self._device.moistureDetected
            or self._device.waterlevelDetected
            or self._device.lowBat
            or self._device.dutyCycle
        ):
            return True

        if (
            self._device.smokeDetectorAlarmType is not None
            and self._device.smokeDetectorAlarmType != SmokeDetectorAlarmType.IDLE_OFF
        ):
            return True

        return False
