"""Test UPnP/IGD setup process."""

from ipaddress import ip_address
from unittest.mock import patch, MagicMock

from homeassistant.setup import async_setup_component
from homeassistant.components import upnp
from homeassistant.components.upnp.device import Device
from homeassistant.const import EVENT_HOMEASSISTANT_STOP

from tests.common import MockConfigEntry
from tests.common import MockDependency
from tests.common import mock_coro


class MockDevice(Device):
    """Mock device for Device."""

    def __init__(self, udn):
        """Initializer."""
        super().__init__(MagicMock())
        self._udn = udn
        self.added_port_mappings = []
        self.removed_port_mappings = []

    @classmethod
    async def async_create_device(cls, hass, ssdp_description):
        """Return self."""
        return cls('UDN')

    @property
    def udn(self):
        """Get the UDN."""
        return self._udn

    async def _async_add_port_mapping(self,
                                      external_port,
                                      local_ip,
                                      internal_port):
        """Add a port mapping."""
        entry = [external_port, local_ip, internal_port]
        self.added_port_mappings.append(entry)

    async def _async_delete_port_mapping(self, external_port):
        """Remove a port mapping."""
        entry = external_port
        self.removed_port_mappings.append(entry)


async def test_async_setup_entry_default(hass):
    """Test async_setup_entry."""
    udn = 'uuid:device_1'
    entry = MockConfigEntry(domain=upnp.DOMAIN)

    config = {
        'http': {},
        'discovery': {},
        # no upnp
    }
    with MockDependency('netdisco.discovery'), \
        patch('homeassistant.components.upnp.get_local_ip',
              return_value='192.168.1.10'):
        await async_setup_component(hass, 'http', config)
        await async_setup_component(hass, 'upnp', config)
        await hass.async_block_till_done()

    # mock homeassistant.components.upnp.device.Device
    mock_device = MockDevice(udn)
    discovery_infos = [{
        'udn': udn,
        'ssdp_description': 'http://192.168.1.1/desc.xml',
    }]
    with patch.object(Device, 'async_create_device') as create_device, \
         patch.object(Device, 'async_discover') as async_discover:  # noqa:E125

        create_device.return_value = mock_coro(return_value=mock_device)
        async_discover.return_value = mock_coro(return_value=discovery_infos)

        assert await upnp.async_setup_entry(hass, entry) is True

        # ensure device is stored/used
        assert hass.data[upnp.DOMAIN]['devices'][udn] == mock_device

        hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
        await hass.async_block_till_done()

    # ensure no port-mappings created or removed
    assert not mock_device.added_port_mappings
    assert not mock_device.removed_port_mappings


async def test_async_setup_entry_port_mapping(hass):
    """Test async_setup_entry."""
    # pylint: disable=invalid-name
    udn = 'uuid:device_1'
    entry = MockConfigEntry(domain=upnp.DOMAIN)

    config = {
        'http': {},
        'discovery': {},
        'upnp': {
            'port_mapping': True,
            'ports': {'hass': 'hass'},
        },
    }
    with MockDependency('netdisco.discovery'), \
        patch('homeassistant.components.upnp.get_local_ip',
              return_value='192.168.1.10'):
        await async_setup_component(hass, 'http', config)
        await async_setup_component(hass, 'upnp', config)
        await hass.async_block_till_done()

    mock_device = MockDevice(udn)
    discovery_infos = [{
        'udn': udn,
        'ssdp_description': 'http://192.168.1.1/desc.xml',
    }]
    with patch.object(Device, 'async_create_device') as create_device, \
         patch.object(Device, 'async_discover') as async_discover:  # noqa:E125

        create_device.return_value = mock_coro(return_value=mock_device)
        async_discover.return_value = mock_coro(return_value=discovery_infos)

        assert await upnp.async_setup_entry(hass, entry) is True

        # ensure device is stored/used
        assert hass.data[upnp.DOMAIN]['devices'][udn] == mock_device

        # ensure add-port-mapping-methods called
        assert mock_device.added_port_mappings == [
            [8123, ip_address('192.168.1.10'), 8123]
        ]

        hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
        await hass.async_block_till_done()

    # ensure delete-port-mapping-methods called
    assert mock_device.removed_port_mappings == [8123]