"""Integration with the Rachio Iro sprinkler system controller."""
from abc import abstractmethod
from datetime import timedelta
import logging

from homeassistant.components.switch import SwitchDevice
from homeassistant.helpers.dispatcher import dispatcher_connect

from . import (
    CONF_MANUAL_RUN_MINS,
    DOMAIN as DOMAIN_RACHIO,
    KEY_DEVICE_ID,
    KEY_ENABLED,
    KEY_ID,
    KEY_NAME,
    KEY_ON,
    KEY_SUBTYPE,
    KEY_SUMMARY,
    KEY_ZONE_ID,
    KEY_ZONE_NUMBER,
    SIGNAL_RACHIO_CONTROLLER_UPDATE,
    SIGNAL_RACHIO_ZONE_UPDATE,
    SUBTYPE_SLEEP_MODE_OFF,
    SUBTYPE_SLEEP_MODE_ON,
    SUBTYPE_ZONE_COMPLETED,
    SUBTYPE_ZONE_STARTED,
    SUBTYPE_ZONE_STOPPED,
)

_LOGGER = logging.getLogger(__name__)

ATTR_ZONE_SUMMARY = "Summary"
ATTR_ZONE_NUMBER = "Zone number"


def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Rachio switches."""
    manual_run_time = timedelta(
        minutes=hass.data[DOMAIN_RACHIO].config.get(CONF_MANUAL_RUN_MINS)
    )
    _LOGGER.info("Rachio run time is %s", str(manual_run_time))

    # Add all zones from all controllers as switches
    devices = []
    for controller in hass.data[DOMAIN_RACHIO].controllers:
        devices.append(RachioStandbySwitch(hass, controller))

        for zone in controller.list_zones():
            devices.append(RachioZone(hass, controller, zone, manual_run_time))

    add_entities(devices)
    _LOGGER.info("%d Rachio switch(es) added", len(devices))


class RachioSwitch(SwitchDevice):
    """Represent a Rachio state that can be toggled."""

    def __init__(self, controller, poll=True):
        """Initialize a new Rachio switch."""
        self._controller = controller

        if poll:
            self._state = self._poll_update()
        else:
            self._state = None

    @property
    def should_poll(self) -> bool:
        """Declare that this entity pushes its state to HA."""
        return False

    @property
    def name(self) -> str:
        """Get a name for this switch."""
        return "Switch on {}".format(self._controller.name)

    @property
    def is_on(self) -> bool:
        """Return whether the switch is currently on."""
        return self._state

    @abstractmethod
    def _poll_update(self, data=None) -> bool:
        """Poll the API."""
        pass

    def _handle_any_update(self, *args, **kwargs) -> None:
        """Determine whether an update event applies to this device."""
        if args[0][KEY_DEVICE_ID] != self._controller.controller_id:
            # For another device
            return

        # For this device
        self._handle_update(args, kwargs)

    @abstractmethod
    def _handle_update(self, *args, **kwargs) -> None:
        """Handle incoming webhook data."""
        pass


class RachioStandbySwitch(RachioSwitch):
    """Representation of a standby status/button."""

    def __init__(self, hass, controller):
        """Instantiate a new Rachio standby mode switch."""
        dispatcher_connect(
            hass, SIGNAL_RACHIO_CONTROLLER_UPDATE, self._handle_any_update
        )
        super().__init__(controller, poll=False)
        self._poll_update(controller.init_data)

    @property
    def name(self) -> str:
        """Return the name of the standby switch."""
        return "{} in standby mode".format(self._controller.name)

    @property
    def unique_id(self) -> str:
        """Return a unique id by combinining controller id and purpose."""
        return "{}-standby".format(self._controller.controller_id)

    @property
    def icon(self) -> str:
        """Return an icon for the standby switch."""
        return "mdi:power"

    def _poll_update(self, data=None) -> bool:
        """Request the state from the API."""
        if data is None:
            data = self._controller.rachio.device.get(self._controller.controller_id)[1]

        return not data[KEY_ON]

    def _handle_update(self, *args, **kwargs) -> None:
        """Update the state using webhook data."""
        if args[0][KEY_SUBTYPE] == SUBTYPE_SLEEP_MODE_ON:
            self._state = True
        elif args[0][KEY_SUBTYPE] == SUBTYPE_SLEEP_MODE_OFF:
            self._state = False

        self.schedule_update_ha_state()

    def turn_on(self, **kwargs) -> None:
        """Put the controller in standby mode."""
        self._controller.rachio.device.off(self._controller.controller_id)

    def turn_off(self, **kwargs) -> None:
        """Resume controller functionality."""
        self._controller.rachio.device.on(self._controller.controller_id)


class RachioZone(RachioSwitch):
    """Representation of one zone of sprinklers connected to the Rachio Iro."""

    def __init__(self, hass, controller, data, manual_run_time):
        """Initialize a new Rachio Zone."""
        self._id = data[KEY_ID]
        self._zone_name = data[KEY_NAME]
        self._zone_number = data[KEY_ZONE_NUMBER]
        self._zone_enabled = data[KEY_ENABLED]
        self._manual_run_time = manual_run_time
        self._summary = str()
        super().__init__(controller)

        # Listen for all zone updates
        dispatcher_connect(hass, SIGNAL_RACHIO_ZONE_UPDATE, self._handle_update)

    def __str__(self):
        """Display the zone as a string."""
        return 'Rachio Zone "{}" on {}'.format(self.name, str(self._controller))

    @property
    def zone_id(self) -> str:
        """How the Rachio API refers to the zone."""
        return self._id

    @property
    def name(self) -> str:
        """Return the friendly name of the zone."""
        return self._zone_name

    @property
    def unique_id(self) -> str:
        """Return a unique id by combinining controller id and zone number."""
        return "{}-zone-{}".format(self._controller.controller_id, self.zone_id)

    @property
    def icon(self) -> str:
        """Return the icon to display."""
        return "mdi:water"

    @property
    def zone_is_enabled(self) -> bool:
        """Return whether the zone is allowed to run."""
        return self._zone_enabled

    @property
    def state_attributes(self) -> dict:
        """Return the optional state attributes."""
        return {ATTR_ZONE_NUMBER: self._zone_number, ATTR_ZONE_SUMMARY: self._summary}

    def turn_on(self, **kwargs) -> None:
        """Start watering this zone."""
        # Stop other zones first
        self.turn_off()

        # Start this zone
        self._controller.rachio.zone.start(self.zone_id, self._manual_run_time.seconds)
        _LOGGER.debug("Watering %s on %s", self.name, self._controller.name)

    def turn_off(self, **kwargs) -> None:
        """Stop watering all zones."""
        self._controller.stop_watering()

    def _poll_update(self, data=None) -> bool:
        """Poll the API to check whether the zone is running."""
        schedule = self._controller.current_schedule
        return self.zone_id == schedule.get(KEY_ZONE_ID)

    def _handle_update(self, *args, **kwargs) -> None:
        """Handle incoming webhook zone data."""
        if args[0][KEY_ZONE_ID] != self.zone_id:
            return

        self._summary = kwargs.get(KEY_SUMMARY, str())

        if args[0][KEY_SUBTYPE] == SUBTYPE_ZONE_STARTED:
            self._state = True
        elif args[0][KEY_SUBTYPE] in [SUBTYPE_ZONE_STOPPED, SUBTYPE_ZONE_COMPLETED]:
            self._state = False

        self.schedule_update_ha_state()
