"""Tests for the lifx component."""

from datetime import timedelta
import socket
from typing import Any
from unittest.mock import patch

import pytest

from homeassistant.components import lifx
from homeassistant.components.lifx import DOMAIN, discovery
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STARTED
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util

from . import (
    IP_ADDRESS,
    SERIAL,
    MockFailingLifxCommand,
    _mocked_bulb,
    _mocked_failing_bulb,
    _patch_config_flow_try_connect,
    _patch_device,
    _patch_discovery,
)

from tests.common import MockConfigEntry, async_fire_time_changed


async def test_configuring_lifx_causes_discovery(hass: HomeAssistant) -> None:
    """Test that specifying empty config does discovery."""
    start_calls = 0

    class MockLifxDiscovery:
        """Mock lifx discovery."""

        def __init__(self, *args: Any, **kwargs: Any) -> None:
            """Init discovery."""
            discovered = _mocked_bulb()
            self.lights = {discovered.mac_addr: discovered}

        def start(self):
            """Mock start."""
            nonlocal start_calls
            start_calls += 1

        def cleanup(self):
            """Mock cleanup."""

    with (
        _patch_config_flow_try_connect(),
        patch.object(discovery, "DEFAULT_TIMEOUT", 0),
        patch(
            "homeassistant.components.lifx.discovery.LifxDiscovery", MockLifxDiscovery
        ),
    ):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()
        assert start_calls == 0

        hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
        await hass.async_block_till_done()
        assert start_calls == 1

        async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
        await hass.async_block_till_done(wait_background_tasks=True)
        assert start_calls == 2

        async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=15))
        await hass.async_block_till_done(wait_background_tasks=True)
        assert start_calls == 3

        async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=30))
        await hass.async_block_till_done(wait_background_tasks=True)
        assert start_calls == 4


async def test_config_entry_reload(hass: HomeAssistant) -> None:
    """Test that a config entry can be reloaded."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    with _patch_discovery(), _patch_config_flow_try_connect(), _patch_device():
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()
        assert already_migrated_config_entry.state is ConfigEntryState.LOADED
        await hass.config_entries.async_unload(already_migrated_config_entry.entry_id)
        await hass.async_block_till_done()
        assert already_migrated_config_entry.state is ConfigEntryState.NOT_LOADED


async def test_config_entry_retry(hass: HomeAssistant) -> None:
    """Test that a config entry can be retried."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    with (
        _patch_discovery(no_device=True),
        _patch_config_flow_try_connect(no_device=True),
        _patch_device(no_device=True),
    ):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()
        assert already_migrated_config_entry.state is ConfigEntryState.SETUP_RETRY


async def test_get_version_fails(hass: HomeAssistant) -> None:
    """Test we handle get version failing."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_bulb()
    bulb.product = None
    bulb.host_firmware_version = None
    bulb.get_version = MockFailingLifxCommand(bulb)

    with _patch_discovery(device=bulb), _patch_device(device=bulb):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()
        assert already_migrated_config_entry.state is ConfigEntryState.SETUP_RETRY


async def test_dns_error_at_startup(hass: HomeAssistant) -> None:
    """Test we handle get version failing."""
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: IP_ADDRESS}, unique_id=SERIAL
    )
    already_migrated_config_entry.add_to_hass(hass)
    bulb = _mocked_failing_bulb()

    class MockLifxConnectonDnsError:
        """Mock lifx connection with a dns error."""

        def __init__(self, *args: Any, **kwargs: Any) -> None:
            """Init connection."""
            self.device = bulb

        async def async_setup(self):
            """Mock setup."""
            raise socket.gaierror

        def async_stop(self):
            """Mock teardown."""

    # Cannot connect due to dns error
    with (
        _patch_discovery(device=bulb),
        patch(
            "homeassistant.components.lifx.LIFXConnection",
            MockLifxConnectonDnsError,
        ),
    ):
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()
        assert already_migrated_config_entry.state is ConfigEntryState.SETUP_RETRY


async def test_config_entry_wrong_serial(
    hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
    """Test config entry enters setup retry when serial mismatches."""
    mismatched_serial = f"{SERIAL[:-1]}0"
    already_migrated_config_entry = MockConfigEntry(
        domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=mismatched_serial
    )
    already_migrated_config_entry.add_to_hass(hass)
    with _patch_discovery(), _patch_config_flow_try_connect(), _patch_device():
        await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
        await hass.async_block_till_done()
        assert already_migrated_config_entry.state is ConfigEntryState.SETUP_RETRY

    assert (
        "Unexpected device found at 127.0.0.1; expected"
        " aa:bb:cc:dd:ee:c0, found aa:bb:cc:dd:ee:cc" in caplog.text
    )
