"""Test discovery helpers."""
from unittest.mock import patch

from homeassistant import setup
from homeassistant.core import callback
from homeassistant.helpers import discovery
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.util.async_ import run_callback_threadsafe

from tests.common import (
    MockModule,
    MockPlatform,
    get_test_home_assistant,
    mock_coro,
    mock_entity_platform,
    mock_integration,
)


class TestHelpersDiscovery:
    """Tests for discovery helper methods."""

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

    def teardown_method(self, method):
        """Stop everything that was started."""
        self.hass.stop()

    @patch("homeassistant.setup.async_setup_component", return_value=mock_coro())
    def test_listen(self, mock_setup_component):
        """Test discovery listen/discover combo."""
        helpers = self.hass.helpers
        calls_single = []

        @callback
        def callback_single(service, info):
            """Service discovered callback."""
            calls_single.append((service, info))

        self.hass.add_job(
            helpers.discovery.async_listen, "test service", callback_single
        )

        self.hass.add_job(
            helpers.discovery.async_discover,
            "test service",
            "discovery info",
            "test_component",
            {},
        )
        self.hass.block_till_done()

        assert mock_setup_component.called
        assert mock_setup_component.call_args[0] == (self.hass, "test_component", {})
        assert len(calls_single) == 1
        assert calls_single[0] == ("test service", "discovery info")

    @patch("homeassistant.setup.async_setup_component", return_value=mock_coro(True))
    def test_platform(self, mock_setup_component):
        """Test discover platform method."""
        calls = []

        @callback
        def platform_callback(platform, info):
            """Platform callback method."""
            calls.append((platform, info))

        run_callback_threadsafe(
            self.hass.loop,
            discovery.async_listen_platform,
            self.hass,
            "test_component",
            platform_callback,
        ).result()

        discovery.load_platform(
            self.hass,
            "test_component",
            "test_platform",
            "discovery info",
            {"test_component": {}},
        )
        self.hass.block_till_done()
        assert mock_setup_component.called
        assert mock_setup_component.call_args[0] == (
            self.hass,
            "test_component",
            {"test_component": {}},
        )
        self.hass.block_till_done()

        discovery.load_platform(
            self.hass,
            "test_component_2",
            "test_platform",
            "discovery info",
            {"test_component": {}},
        )
        self.hass.block_till_done()

        assert len(calls) == 1
        assert calls[0] == ("test_platform", "discovery info")

        dispatcher_send(
            self.hass,
            discovery.SIGNAL_PLATFORM_DISCOVERED,
            {"service": discovery.EVENT_LOAD_PLATFORM.format("test_component")},
        )
        self.hass.block_till_done()

        assert len(calls) == 1

    def test_circular_import(self):
        """Test we don't break doing circular import.

        This test will have test_component discover the switch.test_circular
        component while setting up.

        The supplied config will load test_component and will load
        switch.test_circular.

        That means that after startup, we will have test_component and switch
        setup. The test_circular platform has been loaded twice.
        """
        component_calls = []
        platform_calls = []

        def component_setup(hass, config):
            """Set up mock component."""
            discovery.load_platform(hass, "switch", "test_circular", "disc", config)
            component_calls.append(1)
            return True

        def setup_platform(hass, config, add_entities_callback, discovery_info=None):
            """Set up mock platform."""
            platform_calls.append("disc" if discovery_info else "component")

        mock_integration(self.hass, MockModule("test_component", setup=component_setup))

        # dependencies are only set in component level
        # since we are using manifest to hold them
        mock_integration(
            self.hass, MockModule("test_circular", dependencies=["test_component"])
        )
        mock_entity_platform(
            self.hass, "switch.test_circular", MockPlatform(setup_platform)
        )

        setup.setup_component(
            self.hass,
            "test_component",
            {"test_component": None, "switch": [{"platform": "test_circular"}]},
        )

        self.hass.block_till_done()

        # test_component will only be setup once
        assert len(component_calls) == 1
        # The platform will be setup once via the config in `setup_component`
        # and once via the discovery inside test_component.
        assert len(platform_calls) == 2
        assert "test_component" in self.hass.config.components
        assert "switch" in self.hass.config.components

    @patch("homeassistant.helpers.signal.async_register_signal_handling")
    def test_1st_discovers_2nd_component(self, mock_signal):
        """Test that we don't break if one component discovers the other.

        If the first component fires a discovery event to set up the
        second component while the second component is about to be set up,
        it should not set up the second component twice.
        """
        component_calls = []

        async def component1_setup(hass, config):
            """Set up mock component."""
            print("component1 setup")
            await discovery.async_discover(
                hass, "test_component2", {}, "test_component2", {}
            )
            return True

        def component2_setup(hass, config):
            """Set up mock component."""
            component_calls.append(1)
            return True

        mock_integration(
            self.hass, MockModule("test_component1", async_setup=component1_setup)
        )

        mock_integration(
            self.hass, MockModule("test_component2", setup=component2_setup)
        )

        @callback
        def do_setup():
            """Set up 2 components."""
            self.hass.async_add_job(
                setup.async_setup_component(self.hass, "test_component1", {})
            )
            self.hass.async_add_job(
                setup.async_setup_component(self.hass, "test_component2", {})
            )

        self.hass.add_job(do_setup)
        self.hass.block_till_done()

        # test_component will only be setup once
        assert len(component_calls) == 1