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

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

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

from tests.common import MockConfigEntry, MockDependency, mock_coro


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

    def __init__(self, udn):
        """Initialize mock device."""
        device = MagicMock()
        device.manufacturer = "mock-manuf"
        device.name = "mock-name"
        super().__init__(device)
        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"
    ), patch.object(Device, "async_create_device") as create_device, patch.object(
        Device, "async_create_device"
    ) as create_device, patch.object(
        Device, "async_discover", return_value=mock_coro([])
    ) as async_discover:
        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"}
        ]

        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"
    ), patch.object(Device, "async_create_device") as create_device, patch.object(
        Device, "async_discover", return_value=mock_coro([])
    ) as async_discover:
        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"}
        ]

        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]
