"""The tests for the Updater component."""
import asyncio
from datetime import timedelta
from unittest.mock import Mock, patch

import pytest

from homeassistant.components import updater
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util

from tests.common import (
    MockDependency,
    async_fire_time_changed,
    mock_component,
    mock_coro,
)

NEW_VERSION = "10000.0"
MOCK_VERSION = "10.0"
MOCK_DEV_VERSION = "10.0.dev0"
MOCK_HUUID = "abcdefg"
MOCK_RESPONSE = {"version": "0.15", "release-notes": "https://home-assistant.io"}
MOCK_CONFIG = {updater.DOMAIN: {"reporting": True}}
RELEASE_NOTES = "test release notes"


@pytest.fixture(autouse=True)
def mock_distro():
    """Mock distro dep."""
    with MockDependency("distro"):
        yield


@pytest.fixture(name="mock_get_newest_version")
def mock_get_newest_version_fixture():
    """Fixture to mock get_newest_version."""
    with patch("homeassistant.components.updater.get_newest_version") as mock:
        yield mock


@pytest.fixture(name="mock_get_uuid")
def mock_get_uuid_fixture():
    """Fixture to mock get_uuid."""
    with patch("homeassistant.components.updater._load_uuid") as mock:
        yield mock


@pytest.fixture(name="mock_utcnow")
def mock_utcnow_fixture():
    """Fixture to mock utcnow."""
    with patch("homeassistant.components.updater.dt_util") as mock:
        yield mock.utcnow


async def test_new_version_shows_entity_startup(
    hass, mock_get_uuid, mock_get_newest_version
):
    """Test if binary sensor is unavailable at first."""
    mock_get_uuid.return_value = MOCK_HUUID
    mock_get_newest_version.return_value = mock_coro((NEW_VERSION, RELEASE_NOTES))

    res = await async_setup_component(hass, updater.DOMAIN, {updater.DOMAIN: {}})
    assert res, "Updater failed to set up"

    await hass.async_block_till_done()
    assert hass.states.is_state("binary_sensor.updater", "unavailable")
    assert "newest_version" not in hass.states.get("binary_sensor.updater").attributes
    assert "release_notes" not in hass.states.get("binary_sensor.updater").attributes


async def test_rename_entity(hass, mock_get_uuid, mock_get_newest_version, mock_utcnow):
    """Test if renaming the binary sensor works correctly."""
    mock_get_uuid.return_value = MOCK_HUUID
    mock_get_newest_version.return_value = mock_coro((NEW_VERSION, RELEASE_NOTES))

    now = dt_util.utcnow()
    later = now + timedelta(hours=1)
    mock_utcnow.return_value = now

    res = await async_setup_component(hass, updater.DOMAIN, {updater.DOMAIN: {}})
    assert res, "Updater failed to set up"

    await hass.async_block_till_done()
    assert hass.states.is_state("binary_sensor.updater", "unavailable")
    assert hass.states.get("binary_sensor.new_entity_id") is None

    entity_registry = await hass.helpers.entity_registry.async_get_registry()
    entity_registry.async_update_entity(
        "binary_sensor.updater", new_entity_id="binary_sensor.new_entity_id"
    )

    await hass.async_block_till_done()
    assert hass.states.is_state("binary_sensor.new_entity_id", "unavailable")
    assert hass.states.get("binary_sensor.updater") is None

    with patch("homeassistant.components.updater.current_version", MOCK_VERSION):
        async_fire_time_changed(hass, later)
        await hass.async_block_till_done()

    assert hass.states.is_state("binary_sensor.new_entity_id", "on")
    assert hass.states.get("binary_sensor.updater") is None


async def test_new_version_shows_entity_true(
    hass, mock_get_uuid, mock_get_newest_version, mock_utcnow
):
    """Test if sensor is true if new version is available."""
    mock_get_uuid.return_value = MOCK_HUUID
    mock_get_newest_version.return_value = mock_coro((NEW_VERSION, RELEASE_NOTES))

    now = dt_util.utcnow()
    later = now + timedelta(hours=1)
    mock_utcnow.return_value = now

    res = await async_setup_component(hass, updater.DOMAIN, {updater.DOMAIN: {}})
    assert res, "Updater failed to set up"

    await hass.async_block_till_done()
    with patch("homeassistant.components.updater.current_version", MOCK_VERSION):
        async_fire_time_changed(hass, later)
        await hass.async_block_till_done()

    assert hass.states.is_state("binary_sensor.updater", "on")
    assert (
        hass.states.get("binary_sensor.updater").attributes["newest_version"]
        == NEW_VERSION
    )
    assert (
        hass.states.get("binary_sensor.updater").attributes["release_notes"]
        == RELEASE_NOTES
    )


async def test_same_version_shows_entity_false(
    hass, mock_get_uuid, mock_get_newest_version, mock_utcnow
):
    """Test if sensor is false if no new version is available."""
    mock_get_uuid.return_value = MOCK_HUUID
    mock_get_newest_version.return_value = mock_coro((MOCK_VERSION, ""))

    now = dt_util.utcnow()
    later = now + timedelta(hours=1)
    mock_utcnow.return_value = now

    res = await async_setup_component(hass, updater.DOMAIN, {updater.DOMAIN: {}})
    assert res, "Updater failed to set up"

    await hass.async_block_till_done()
    with patch("homeassistant.components.updater.current_version", MOCK_VERSION):
        async_fire_time_changed(hass, later)
        await hass.async_block_till_done()

    assert hass.states.is_state("binary_sensor.updater", "off")
    assert (
        hass.states.get("binary_sensor.updater").attributes["newest_version"]
        == MOCK_VERSION
    )
    assert "release_notes" not in hass.states.get("binary_sensor.updater").attributes


async def test_disable_reporting(
    hass, mock_get_uuid, mock_get_newest_version, mock_utcnow
):
    """Test we do not gather analytics when disable reporting is active."""
    mock_get_uuid.return_value = MOCK_HUUID
    mock_get_newest_version.return_value = mock_coro((MOCK_VERSION, ""))

    now = dt_util.utcnow()
    later = now + timedelta(hours=1)
    mock_utcnow.return_value = now

    res = await async_setup_component(
        hass, updater.DOMAIN, {updater.DOMAIN: {"reporting": False}}
    )
    assert res, "Updater failed to set up"

    await hass.async_block_till_done()
    with patch("homeassistant.components.updater.current_version", MOCK_VERSION):
        async_fire_time_changed(hass, later)
        await hass.async_block_till_done()

    assert hass.states.is_state("binary_sensor.updater", "off")
    res = await updater.get_newest_version(hass, MOCK_HUUID, MOCK_CONFIG)
    call = mock_get_newest_version.mock_calls[0][1]
    assert call[0] is hass
    assert call[1] is None


async def test_get_newest_version_no_analytics_when_no_huuid(hass, aioclient_mock):
    """Test we do not gather analytics when no huuid is passed in."""
    aioclient_mock.post(updater.UPDATER_URL, json=MOCK_RESPONSE)

    with patch(
        "homeassistant.helpers.system_info.async_get_system_info", side_effect=Exception
    ):
        res = await updater.get_newest_version(hass, None, False)
        assert res == (MOCK_RESPONSE["version"], MOCK_RESPONSE["release-notes"])


async def test_get_newest_version_analytics_when_huuid(hass, aioclient_mock):
    """Test we gather analytics when huuid is passed in."""
    aioclient_mock.post(updater.UPDATER_URL, json=MOCK_RESPONSE)

    with patch(
        "homeassistant.helpers.system_info.async_get_system_info",
        Mock(return_value=mock_coro({"fake": "bla"})),
    ):
        res = await updater.get_newest_version(hass, MOCK_HUUID, False)
        assert res == (MOCK_RESPONSE["version"], MOCK_RESPONSE["release-notes"])


async def test_error_fetching_new_version_timeout(hass):
    """Test we handle timeout error while fetching new version."""
    with patch(
        "homeassistant.helpers.system_info.async_get_system_info",
        Mock(return_value=mock_coro({"fake": "bla"})),
    ), patch("async_timeout.timeout", side_effect=asyncio.TimeoutError):
        res = await updater.get_newest_version(hass, MOCK_HUUID, False)
        assert res is None


async def test_error_fetching_new_version_bad_json(hass, aioclient_mock):
    """Test we handle json error while fetching new version."""
    aioclient_mock.post(updater.UPDATER_URL, text="not json")

    with patch(
        "homeassistant.helpers.system_info.async_get_system_info",
        Mock(return_value=mock_coro({"fake": "bla"})),
    ):
        res = await updater.get_newest_version(hass, MOCK_HUUID, False)
        assert res is None


async def test_error_fetching_new_version_invalid_response(hass, aioclient_mock):
    """Test we handle response error while fetching new version."""
    aioclient_mock.post(
        updater.UPDATER_URL,
        json={
            "version": "0.15"
            # 'release-notes' is missing
        },
    )

    with patch(
        "homeassistant.helpers.system_info.async_get_system_info",
        Mock(return_value=mock_coro({"fake": "bla"})),
    ):
        res = await updater.get_newest_version(hass, MOCK_HUUID, False)
        assert res is None


async def test_new_version_shows_entity_after_hour_hassio(
    hass, mock_get_uuid, mock_get_newest_version, mock_utcnow
):
    """Test if binary sensor gets updated if new version is available / hass.io."""
    mock_get_uuid.return_value = MOCK_HUUID
    mock_get_newest_version.return_value = mock_coro((NEW_VERSION, RELEASE_NOTES))
    mock_component(hass, "hassio")
    hass.data["hassio_hass_version"] = "999.0"

    now = dt_util.utcnow()
    later = now + timedelta(hours=1)
    mock_utcnow.return_value = now

    res = await async_setup_component(hass, updater.DOMAIN, {updater.DOMAIN: {}})
    assert res, "Updater failed to set up"

    await hass.async_block_till_done()
    with patch("homeassistant.components.updater.current_version", MOCK_VERSION):
        async_fire_time_changed(hass, later)
        await hass.async_block_till_done()

    assert hass.states.is_state("binary_sensor.updater", "on")
    assert (
        hass.states.get("binary_sensor.updater").attributes["newest_version"] == "999.0"
    )
    assert (
        hass.states.get("binary_sensor.updater").attributes["release_notes"]
        == RELEASE_NOTES
    )
