"""
tests.helper
~~~~~~~~~~~~~

Helper method for writing tests.
"""
import os
from datetime import timedelta
from unittest import mock

from homeassistant import core as ha, loader
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.const import (
    STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED,
    EVENT_STATE_CHANGED, EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE,
    ATTR_DISCOVERED)
from homeassistant.components import sun, mqtt


def get_test_config_dir():
    """ Returns a path to a test config dir. """
    return os.path.join(os.path.dirname(__file__), "config")


def get_test_home_assistant(num_threads=None):
    """ Returns a Home Assistant object pointing at test config dir. """
    if num_threads:
        orig_num_threads = ha.MIN_WORKER_THREAD
        ha.MIN_WORKER_THREAD = num_threads

    hass = ha.HomeAssistant()

    if num_threads:
        ha.MIN_WORKER_THREAD = orig_num_threads

    hass.config.config_dir = get_test_config_dir()
    hass.config.latitude = 32.87336
    hass.config.longitude = -117.22743

    if 'custom_components.test' not in loader.AVAILABLE_COMPONENTS:
        loader.prepare(hass)

    return hass


def mock_service(hass, domain, service):
    """
    Sets up a fake service.
    Returns a list that logs all calls to fake service.
    """
    calls = []

    hass.services.register(
        domain, service, lambda call: calls.append(call))

    return calls


def fire_mqtt_message(hass, topic, payload, qos=0):
    hass.bus.fire(mqtt.EVENT_MQTT_MESSAGE_RECEIVED, {
        mqtt.ATTR_TOPIC: topic,
        mqtt.ATTR_PAYLOAD: payload,
        mqtt.ATTR_QOS: qos,
    })


def fire_time_changed(hass, time):
    hass.bus.fire(EVENT_TIME_CHANGED, {'now': time})


def fire_service_discovered(hass, service, info):
    hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
        ATTR_SERVICE: service,
        ATTR_DISCOVERED: info
    })


def ensure_sun_risen(hass):
    """ Trigger sun to rise if below horizon. """
    if sun.is_on(hass):
        return
    fire_time_changed(hass, sun.next_rising_utc(hass) + timedelta(seconds=10))


def ensure_sun_set(hass):
    """ Trigger sun to set if above horizon. """
    if not sun.is_on(hass):
        return
    fire_time_changed(hass, sun.next_setting_utc(hass) + timedelta(seconds=10))


def mock_state_change_event(hass, new_state, old_state=None):
    event_data = {
        'entity_id': new_state.entity_id,
        'new_state': new_state,
    }

    if old_state:
        event_data['old_state'] = old_state

    hass.bus.fire(EVENT_STATE_CHANGED, event_data)


def mock_http_component(hass):
    hass.http = MockHTTP()
    hass.config.components.append('http')


@mock.patch('homeassistant.components.mqtt.MQTT')
def mock_mqtt_component(hass, mock_mqtt):
    mqtt.setup(hass, {
        mqtt.DOMAIN: {
            mqtt.CONF_BROKER: 'mock-broker',
        }
    })
    hass.config.components.append(mqtt.DOMAIN)
    return mock_mqtt


class MockHTTP(object):
    """ Mocks the HTTP module. """

    def register_path(self, method, url, callback, require_auth=True):
        pass


class MockModule(object):
    """ Provides a fake module. """

    def __init__(self, domain=None, dependencies=[], setup=None):
        self.DOMAIN = domain
        self.DEPENDENCIES = dependencies
        # Setup a mock setup if none given.
        if setup is None:
            self.setup = lambda hass, config: False
        else:
            self.setup = setup


class MockPlatform(object):
    """ Provides a fake platform. """

    def __init__(self, setup_platform=None, dependencies=[]):
        self.DEPENDENCIES = dependencies
        self._setup_platform = setup_platform

    def setup_platform(self, hass, config, add_devices, discovery_info=None):
        if self._setup_platform is not None:
            self._setup_platform(hass, config, add_devices, discovery_info)


class MockToggleDevice(ToggleEntity):
    """ Provides a mock toggle device. """
    def __init__(self, name, state):
        self._name = name or DEVICE_DEFAULT_NAME
        self._state = state
        self.calls = []

    @property
    def name(self):
        """ Returns the name of the device if any. """
        self.calls.append(('name', {}))
        return self._name

    @property
    def state(self):
        """ Returns the name of the device if any. """
        self.calls.append(('state', {}))
        return self._state

    @property
    def is_on(self):
        """ True if device is on. """
        self.calls.append(('is_on', {}))
        return self._state == STATE_ON

    def turn_on(self, **kwargs):
        """ Turn the device on. """
        self.calls.append(('turn_on', kwargs))
        self._state = STATE_ON

    def turn_off(self, **kwargs):
        """ Turn the device off. """
        self.calls.append(('turn_off', kwargs))
        self._state = STATE_OFF

    def last_call(self, method=None):
        if not self.calls:
            return None
        elif method is None:
            return self.calls[-1]
        else:
            try:
                return next(call for call in reversed(self.calls)
                            if call[0] == method)
            except StopIteration:
                return None
