"""The tests for the Alert component."""
import unittest
# pylint: disable=protected-access
from copy import deepcopy

import homeassistant.components.alert as alert
import homeassistant.components.notify as notify
from homeassistant.components.alert import DOMAIN
from homeassistant.const import (
    ATTR_ENTITY_ID, CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE,
    SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_ON, STATE_OFF)
from homeassistant.core import callback
from homeassistant.setup import setup_component
from tests.common import get_test_home_assistant

NAME = "alert_test"
DONE_MESSAGE = "alert_gone"
NOTIFIER = 'test'
TEMPLATE = "{{ states.sensor.test.entity_id }}"
TEST_ENTITY = "sensor.test"
TITLE = "{{ states.sensor.test.entity_id }}"
TEST_TITLE = "sensor.test"
TEST_DATA = {
    'data': {
        'inline_keyboard': ['Close garage:/close_garage']
    }
}
TEST_CONFIG = \
    {alert.DOMAIN: {
        NAME: {
            CONF_NAME: NAME,
            alert.CONF_DONE_MESSAGE: DONE_MESSAGE,
            CONF_ENTITY_ID: TEST_ENTITY,
            CONF_STATE: STATE_ON,
            alert.CONF_REPEAT: 30,
            alert.CONF_SKIP_FIRST: False,
            alert.CONF_NOTIFIERS: [NOTIFIER],
            alert.CONF_TITLE: TITLE,
            alert.CONF_DATA: {}
        }
    }}
TEST_NOACK = [NAME, NAME, "sensor.test",
              STATE_ON, [30], False, None, None, NOTIFIER, False, None, None]
ENTITY_ID = alert.ENTITY_ID_FORMAT.format(NAME)


def turn_on(hass, entity_id):
    """Reset the alert.

    This is a legacy helper method. Do not use it for new tests.
    """
    hass.add_job(async_turn_on, hass, entity_id)


@callback
def async_turn_on(hass, entity_id):
    """Async reset the alert.

    This is a legacy helper method. Do not use it for new tests.
    """
    data = {ATTR_ENTITY_ID: entity_id}
    hass.async_create_task(
        hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))


def turn_off(hass, entity_id):
    """Acknowledge alert.

    This is a legacy helper method. Do not use it for new tests.
    """
    hass.add_job(async_turn_off, hass, entity_id)


@callback
def async_turn_off(hass, entity_id):
    """Async acknowledge the alert.

    This is a legacy helper method. Do not use it for new tests.
    """
    data = {ATTR_ENTITY_ID: entity_id}
    hass.async_create_task(
        hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data))


def toggle(hass, entity_id):
    """Toggle acknowledgment of alert.

    This is a legacy helper method. Do not use it for new tests.
    """
    hass.add_job(async_toggle, hass, entity_id)


@callback
def async_toggle(hass, entity_id):
    """Async toggle acknowledgment of alert.

    This is a legacy helper method. Do not use it for new tests.
    """
    data = {ATTR_ENTITY_ID: entity_id}
    hass.async_create_task(
        hass.services.async_call(DOMAIN, SERVICE_TOGGLE, data))


# pylint: disable=invalid-name
class TestAlert(unittest.TestCase):
    """Test the alert module."""

    def setUp(self):
        """Set up things to be run when tests are started."""
        self.hass = get_test_home_assistant()
        self._setup_notify()

    def tearDown(self):
        """Stop everything that was started."""
        self.hass.stop()

    def _setup_notify(self):
        events = []

        @callback
        def record_event(event):
            """Add recorded event to set."""
            events.append(event)

        self.hass.services.register(
            notify.DOMAIN, NOTIFIER, record_event)

        return events

    def test_is_on(self):
        """Test is_on method."""
        self.hass.states.set(ENTITY_ID, STATE_ON)
        self.hass.block_till_done()
        assert alert.is_on(self.hass, ENTITY_ID)
        self.hass.states.set(ENTITY_ID, STATE_OFF)
        self.hass.block_till_done()
        assert not alert.is_on(self.hass, ENTITY_ID)

    def test_setup(self):
        """Test setup method."""
        assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
        assert STATE_IDLE == self.hass.states.get(ENTITY_ID).state

    def test_fire(self):
        """Test the alert firing."""
        assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
        self.hass.states.set("sensor.test", STATE_ON)
        self.hass.block_till_done()
        assert STATE_ON == self.hass.states.get(ENTITY_ID).state

    def test_silence(self):
        """Test silencing the alert."""
        assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
        self.hass.states.set("sensor.test", STATE_ON)
        self.hass.block_till_done()
        turn_off(self.hass, ENTITY_ID)
        self.hass.block_till_done()
        assert STATE_OFF == self.hass.states.get(ENTITY_ID).state

        # alert should not be silenced on next fire
        self.hass.states.set("sensor.test", STATE_OFF)
        self.hass.block_till_done()
        assert STATE_IDLE == self.hass.states.get(ENTITY_ID).state
        self.hass.states.set("sensor.test", STATE_ON)
        self.hass.block_till_done()
        assert STATE_ON == self.hass.states.get(ENTITY_ID).state

    def test_reset(self):
        """Test resetting the alert."""
        assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
        self.hass.states.set("sensor.test", STATE_ON)
        self.hass.block_till_done()
        turn_off(self.hass, ENTITY_ID)
        self.hass.block_till_done()
        assert STATE_OFF == self.hass.states.get(ENTITY_ID).state
        turn_on(self.hass, ENTITY_ID)
        self.hass.block_till_done()
        assert STATE_ON == self.hass.states.get(ENTITY_ID).state

    def test_toggle(self):
        """Test toggling alert."""
        assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
        self.hass.states.set("sensor.test", STATE_ON)
        self.hass.block_till_done()
        assert STATE_ON == self.hass.states.get(ENTITY_ID).state
        toggle(self.hass, ENTITY_ID)
        self.hass.block_till_done()
        assert STATE_OFF == self.hass.states.get(ENTITY_ID).state
        toggle(self.hass, ENTITY_ID)
        self.hass.block_till_done()
        assert STATE_ON == self.hass.states.get(ENTITY_ID).state

    def test_hidden(self):
        """Test entity hiding."""
        assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
        hidden = self.hass.states.get(ENTITY_ID).attributes.get('hidden')
        assert hidden

        self.hass.states.set("sensor.test", STATE_ON)
        self.hass.block_till_done()
        hidden = self.hass.states.get(ENTITY_ID).attributes.get('hidden')
        assert not hidden

        turn_off(self.hass, ENTITY_ID)
        hidden = self.hass.states.get(ENTITY_ID).attributes.get('hidden')
        assert not hidden

    def test_notification_no_done_message(self):
        """Test notifications."""
        events = []
        config = deepcopy(TEST_CONFIG)
        del (config[alert.DOMAIN][NAME][alert.CONF_DONE_MESSAGE])

        @callback
        def record_event(event):
            """Add recorded event to set."""
            events.append(event)

        self.hass.services.register(
            notify.DOMAIN, NOTIFIER, record_event)

        assert setup_component(self.hass, alert.DOMAIN, config)
        assert 0 == len(events)

        self.hass.states.set("sensor.test", STATE_ON)
        self.hass.block_till_done()
        assert 1 == len(events)

        self.hass.states.set("sensor.test", STATE_OFF)
        self.hass.block_till_done()
        assert 1 == len(events)

    def test_notification(self):
        """Test notifications."""
        events = []

        @callback
        def record_event(event):
            """Add recorded event to set."""
            events.append(event)

        self.hass.services.register(
            notify.DOMAIN, NOTIFIER, record_event)

        assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
        assert 0 == len(events)

        self.hass.states.set("sensor.test", STATE_ON)
        self.hass.block_till_done()
        assert 1 == len(events)

        self.hass.states.set("sensor.test", STATE_OFF)
        self.hass.block_till_done()
        assert 2 == len(events)

    def test_sending_non_templated_notification(self):
        """Test notifications."""
        events = self._setup_notify()

        assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)

        self.hass.states.set(TEST_ENTITY, STATE_ON)
        self.hass.block_till_done()
        self.assertEqual(1, len(events))
        last_event = events[-1]
        self.assertEqual(last_event.data[notify.ATTR_MESSAGE], NAME)

    def test_sending_templated_notification(self):
        """Test templated notification."""
        events = self._setup_notify()

        config = deepcopy(TEST_CONFIG)
        config[alert.DOMAIN][NAME][alert.CONF_ALERT_MESSAGE] = TEMPLATE
        assert setup_component(self.hass, alert.DOMAIN, config)

        self.hass.states.set(TEST_ENTITY, STATE_ON)
        self.hass.block_till_done()
        self.assertEqual(1, len(events))
        last_event = events[-1]
        self.assertEqual(last_event.data[notify.ATTR_MESSAGE], TEST_ENTITY)

    def test_sending_templated_done_notification(self):
        """Test templated notification."""
        events = self._setup_notify()

        config = deepcopy(TEST_CONFIG)
        config[alert.DOMAIN][NAME][alert.CONF_DONE_MESSAGE] = TEMPLATE
        assert setup_component(self.hass, alert.DOMAIN, config)

        self.hass.states.set(TEST_ENTITY, STATE_ON)
        self.hass.block_till_done()
        self.hass.states.set(TEST_ENTITY, STATE_OFF)
        self.hass.block_till_done()
        self.assertEqual(2, len(events))
        last_event = events[-1]
        self.assertEqual(last_event.data[notify.ATTR_MESSAGE], TEST_ENTITY)

    def test_sending_titled_notification(self):
        """Test notifications."""
        events = self._setup_notify()

        config = deepcopy(TEST_CONFIG)
        config[alert.DOMAIN][NAME][alert.CONF_TITLE] = TITLE
        assert setup_component(self.hass, alert.DOMAIN, config)

        self.hass.states.set(TEST_ENTITY, STATE_ON)
        self.hass.block_till_done()
        self.assertEqual(1, len(events))
        last_event = events[-1]
        self.assertEqual(last_event.data[notify.ATTR_TITLE], TEST_TITLE)

    def test_sending_data_notification(self):
        """Test notifications."""
        events = self._setup_notify()

        config = deepcopy(TEST_CONFIG)
        config[alert.DOMAIN][NAME][alert.CONF_DATA] = TEST_DATA
        assert setup_component(self.hass, alert.DOMAIN, config)

        self.hass.states.set(TEST_ENTITY, STATE_ON)
        self.hass.block_till_done()
        self.assertEqual(1, len(events))
        last_event = events[-1]
        self.assertEqual(last_event.data[notify.ATTR_DATA], TEST_DATA)

    def test_skipfirst(self):
        """Test skipping first notification."""
        config = deepcopy(TEST_CONFIG)
        config[alert.DOMAIN][NAME][alert.CONF_SKIP_FIRST] = True
        events = []

        @callback
        def record_event(event):
            """Add recorded event to set."""
            events.append(event)

        self.hass.services.register(
            notify.DOMAIN, NOTIFIER, record_event)

        assert setup_component(self.hass, alert.DOMAIN, config)
        assert 0 == len(events)

        self.hass.states.set("sensor.test", STATE_ON)
        self.hass.block_till_done()
        assert 0 == len(events)

    def test_noack(self):
        """Test no ack feature."""
        entity = alert.Alert(self.hass, *TEST_NOACK)
        self.hass.add_job(entity.begin_alerting)
        self.hass.block_till_done()

        assert entity.hidden is True

    def test_done_message_state_tracker_reset_on_cancel(self):
        """Test that the done message is reset when canceled."""
        entity = alert.Alert(self.hass, *TEST_NOACK)
        entity._cancel = lambda *args: None
        assert entity._send_done_message is False
        entity._send_done_message = True
        self.hass.add_job(entity.end_alerting)
        self.hass.block_till_done()
        assert entity._send_done_message is False