"""The tests for the hassio update entities."""

from dataclasses import replace
from datetime import datetime, timedelta
import os
from typing import Any
from unittest.mock import AsyncMock, MagicMock, Mock, patch
from uuid import uuid4

from aiohasupervisor import (
    SupervisorBadRequestError,
    SupervisorError,
    SupervisorNotFoundError,
)
from aiohasupervisor.models import (
    AddonState,
    HomeAssistantUpdateOptions,
    InstalledAddonComplete,
    Job,
    JobsInfo,
    OSUpdate,
    StoreAddonUpdate,
)
import pytest

from homeassistant.components.backup import BackupManagerError, ManagerBackup

# pylint: disable-next=home-assistant-component-root-import
from homeassistant.components.backup.manager import AgentBackupStatus
from homeassistant.components.hassio import DOMAIN
from homeassistant.components.hassio.const import REQUEST_REFRESH_DELAY
from homeassistant.const import __version__ as HAVERSION
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util

from tests.common import MockConfigEntry, async_fire_time_changed
from tests.typing import WebSocketGenerator

MOCK_ENVIRON = {"SUPERVISOR": "127.0.0.1", "SUPERVISOR_TOKEN": "abcdefgh"}


@pytest.fixture(autouse=True)
def mock_all(
    addon_installed: AsyncMock,
    store_info: AsyncMock,
    addon_stats: AsyncMock,
    addon_changelog: AsyncMock,
    resolution_info: AsyncMock,
    jobs_info: AsyncMock,
    host_info: AsyncMock,
    supervisor_root_info: AsyncMock,
    homeassistant_info: AsyncMock,
    supervisor_info: AsyncMock,
    addons_list: AsyncMock,
    network_info: AsyncMock,
    os_info: AsyncMock,
    homeassistant_stats: AsyncMock,
    supervisor_stats: AsyncMock,
    ingress_panels: AsyncMock,
) -> None:
    """Mock all setup requests."""
    homeassistant_info.return_value = replace(
        homeassistant_info.return_value,
        version="1.0.0dev221",
        version_latest="1.0.0dev222",
        update_available=True,
    )
    os_info.return_value = replace(
        os_info.return_value,
        version="1.0.0dev2221",
        version_latest="1.0.0dev2222",
        update_available=True,
    )
    supervisor_info.return_value = replace(
        supervisor_info.return_value,
        version_latest="1.0.1dev222",
        update_available=True,
    )

    def mock_addon_info(slug: str):
        addon = Mock(
            spec=InstalledAddonComplete,
            to_dict=addon_installed.return_value.to_dict,
            **addon_installed.return_value.to_dict(),
        )
        if slug == "test":
            addon.name = "test"
            addon.slug = "test"
            addon.version = "2.0.0"
            addon.version_latest = "2.0.1"
            addon.update_available = True
            addon.state = AddonState.STARTED
            addon.url = "https://github.com/home-assistant/addons/test"
            addon.auto_update = True
        else:
            addon.name = "test2"
            addon.slug = "test2"
            addon.version = "3.1.0"
            addon.version_latest = "3.1.0"
            addon.update_available = False
            addon.state = AddonState.STOPPED
            addon.url = "https://github.com"
            addon.auto_update = False

        return addon

    addon_installed.side_effect = mock_addon_info


@pytest.mark.parametrize(
    ("entity_id", "expected_state", "auto_update"),
    [
        ("update.home_assistant_operating_system_update", "on", False),
        ("update.home_assistant_supervisor_update", "on", True),
        ("update.home_assistant_core_update", "on", False),
        ("update.test_update", "on", True),
        ("update.test2_update", "off", False),
    ],
)
async def test_update_entities(
    hass: HomeAssistant,
    entity_id,
    expected_state,
    auto_update,
    addon_installed: AsyncMock,
) -> None:
    """Test update entities."""
    addon_installed.return_value.auto_update = auto_update
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    # Verify that the entity have the expected state.
    state = hass.states.get(entity_id)
    assert state.state == expected_state

    # Verify that the auto_update attribute is correct
    assert state.attributes["auto_update"] is auto_update


async def test_update_addon(hass: HomeAssistant, update_addon: AsyncMock) -> None:
    """Test updating addon update entity."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    with patch(
        "homeassistant.components.backup.manager.BackupManager.async_create_backup",
    ) as mock_create_backup:
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.test_update"},
            blocking=True,
        )
    mock_create_backup.assert_not_called()
    update_addon.assert_called_once_with("test", StoreAddonUpdate(backup=False))


async def test_update_addon_progress(
    hass: HomeAssistant, hass_supervisor_ws_client: WebSocketGenerator
) -> None:
    """Test progress reporting for addon update."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    client = await hass_supervisor_ws_client()
    message_id = 0
    job_uuid = uuid4().hex

    def make_job_message(progress: float, done: bool | None):
        nonlocal message_id
        message_id += 1
        return {
            "id": message_id,
            "type": "supervisor/event",
            "data": {
                "event": "job",
                "data": {
                    "uuid": job_uuid,
                    "created": "2025-09-29T00:00:00.000000+00:00",
                    "name": "addon_manager_update",
                    "reference": "test",
                    "progress": progress,
                    "done": done,
                    "stage": None,
                    "extra": {"total": 1234567890} if progress > 0 else None,
                    "errors": [],
                },
            },
        }

    await client.send_json(make_job_message(progress=0, done=None))
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    assert hass.states.get("update.test_update").attributes.get("in_progress") is False
    assert (
        hass.states.get("update.test_update").attributes.get("update_percentage")
        is None
    )

    await client.send_json(make_job_message(progress=5, done=False))
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    assert hass.states.get("update.test_update").attributes.get("in_progress") is True
    assert (
        hass.states.get("update.test_update").attributes.get("update_percentage") == 5
    )

    await client.send_json(make_job_message(progress=50, done=False))
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    assert hass.states.get("update.test_update").attributes.get("in_progress") is True
    assert (
        hass.states.get("update.test_update").attributes.get("update_percentage") == 50
    )

    await client.send_json(make_job_message(progress=100, done=True))
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    assert hass.states.get("update.test_update").attributes.get("in_progress") is False
    assert (
        hass.states.get("update.test_update").attributes.get("update_percentage")
        is None
    )


async def test_addon_update_progress_startup(
    hass: HomeAssistant, jobs_info: AsyncMock
) -> None:
    """Test addon update in progress during home assistant startup."""
    jobs_info.return_value = JobsInfo(
        ignore_conditions=[],
        jobs=[
            Job(
                name="addon_manager_update",
                reference="test",
                uuid=uuid4().hex,
                progress=50,
                stage=None,
                done=False,
                errors=[],
                created=datetime.now(),
                child_jobs=[],
                extra={"total": 1234567890},
            )
        ],
    )

    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    assert hass.states.get("update.test_update").attributes.get("in_progress") is True
    assert (
        hass.states.get("update.test_update").attributes.get("update_percentage") == 50
    )


async def setup_backup_integration(hass: HomeAssistant) -> None:
    """Set up the backup integration."""
    assert await async_setup_component(hass, "backup", {})
    await hass.async_block_till_done()


@pytest.mark.parametrize(
    ("commands", "default_mount", "expected_kwargs"),
    [
        (
            [],
            None,
            {
                "agent_ids": ["hassio.local"],
                "extra_metadata": {"supervisor.addon_update": "test"},
                "include_addons": ["test"],
                "include_all_addons": False,
                "include_database": False,
                "include_folders": None,
                "include_homeassistant": False,
                "name": "test 2.0.0",
                "password": None,
            },
        ),
        (
            [],
            "my_nas",
            {
                "agent_ids": ["hassio.my_nas"],
                "extra_metadata": {"supervisor.addon_update": "test"},
                "include_addons": ["test"],
                "include_all_addons": False,
                "include_database": False,
                "include_folders": None,
                "include_homeassistant": False,
                "name": "test 2.0.0",
                "password": None,
            },
        ),
        (
            [
                {
                    "type": "backup/config/update",
                    "create_backup": {
                        "agent_ids": ["test-agent"],
                        "include_addons": ["my-addon"],
                        "include_all_addons": True,
                        "include_database": False,
                        "include_folders": ["share"],
                        "name": "cool_backup",
                        "password": "hunter2",
                    },
                },
            ],
            None,
            {
                "agent_ids": ["hassio.local"],
                "extra_metadata": {"supervisor.addon_update": "test"},
                "include_addons": ["test"],
                "include_all_addons": False,
                "include_database": False,
                "include_folders": None,
                "include_homeassistant": False,
                "name": "test 2.0.0",
                "password": "hunter2",
            },
        ),
    ],
)
async def test_update_addon_with_backup(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    supervisor_client: AsyncMock,
    update_addon: AsyncMock,
    commands: list[dict[str, Any]],
    default_mount: str | None,
    expected_kwargs: dict[str, Any],
) -> None:
    """Test updating addon update entity."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await setup_backup_integration(hass)

    client = await hass_ws_client(hass)
    for command in commands:
        await client.send_json_auto_id(command)
        result = await client.receive_json()
        assert result["success"]

    supervisor_client.mounts.info.return_value.default_backup_mount = default_mount
    with patch(
        "homeassistant.components.backup.manager.BackupManager.async_create_backup",
    ) as mock_create_backup:
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.test_update", "backup": True},
            blocking=True,
        )
    mock_create_backup.assert_called_once_with(**expected_kwargs)
    update_addon.assert_called_once_with("test", StoreAddonUpdate(backup=False))


@pytest.mark.parametrize(
    ("backups", "removed_backups"),
    [
        (
            {},
            [],
        ),
        (
            {
                "backup-1": MagicMock(
                    agents={"hassio.local": MagicMock(spec=AgentBackupStatus)},
                    date="2024-11-10T04:45:00+01:00",
                    with_automatic_settings=True,
                    spec=ManagerBackup,
                ),
                "backup-2": MagicMock(
                    agents={"hassio.local": MagicMock(spec=AgentBackupStatus)},
                    date="2024-11-11T04:45:00+01:00",
                    with_automatic_settings=False,
                    spec=ManagerBackup,
                ),
                "backup-3": MagicMock(
                    agents={"hassio.local": MagicMock(spec=AgentBackupStatus)},
                    date="2024-11-11T04:45:00+01:00",
                    extra_metadata={"supervisor.addon_update": "other"},
                    with_automatic_settings=True,
                    spec=ManagerBackup,
                ),
                "backup-4": MagicMock(
                    agents={"hassio.local": MagicMock(spec=AgentBackupStatus)},
                    date="2024-11-11T04:45:00+01:00",
                    extra_metadata={"supervisor.addon_update": "other"},
                    with_automatic_settings=True,
                    spec=ManagerBackup,
                ),
                "backup-5": MagicMock(
                    agents={"hassio.local": MagicMock(spec=AgentBackupStatus)},
                    date="2024-11-11T04:45:00+01:00",
                    extra_metadata={"supervisor.addon_update": "test"},
                    with_automatic_settings=True,
                    spec=ManagerBackup,
                ),
                "backup-6": MagicMock(
                    agents={"hassio.local": MagicMock(spec=AgentBackupStatus)},
                    date="2024-11-12T04:45:00+01:00",
                    extra_metadata={"supervisor.addon_update": "test"},
                    with_automatic_settings=True,
                    spec=ManagerBackup,
                ),
            },
            ["backup-5"],
        ),
    ],
)
async def test_update_addon_with_backup_removes_old_backups(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    update_addon: AsyncMock,
    backups: dict[str, ManagerBackup],
    removed_backups: list[str],
) -> None:
    """Test updating addon update entity."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await setup_backup_integration(hass)

    supervisor_client.mounts.info.return_value.default_backup_mount = None
    with (
        patch(
            "homeassistant.components.backup.manager.BackupManager.async_create_backup",
        ) as mock_create_backup,
        patch(
            "homeassistant.components.backup.manager.BackupManager.async_delete_backup",
            autospec=True,
            return_value={},
        ) as async_delete_backup,
        patch(
            "homeassistant.components.backup.manager.BackupManager.async_get_backups",
            return_value=(backups, {}),
        ),
    ):
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.test_update", "backup": True},
            blocking=True,
        )
    mock_create_backup.assert_called_once_with(
        agent_ids=["hassio.local"],
        extra_metadata={"supervisor.addon_update": "test"},
        include_addons=["test"],
        include_all_addons=False,
        include_database=False,
        include_folders=None,
        include_homeassistant=False,
        name="test 2.0.0",
        password=None,
    )
    assert len(async_delete_backup.mock_calls) == len(removed_backups)
    for call in async_delete_backup.mock_calls:
        assert call.args[1] in removed_backups
    update_addon.assert_called_once_with("test", StoreAddonUpdate(backup=False))


async def test_update_os(hass: HomeAssistant, supervisor_client: AsyncMock) -> None:
    """Test updating OS update entity."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    supervisor_client.os.update.return_value = None
    with patch(
        "homeassistant.components.backup.manager.BackupManager.async_create_backup",
    ) as mock_create_backup:
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.home_assistant_operating_system_update"},
            blocking=True,
        )
    mock_create_backup.assert_not_called()
    supervisor_client.os.update.assert_called_once_with(OSUpdate(version=None))


@pytest.mark.parametrize(
    ("commands", "default_mount", "expected_kwargs"),
    [
        (
            [],
            None,
            {
                "agent_ids": ["hassio.local"],
                "include_addons": None,
                "include_all_addons": False,
                "include_database": True,
                "include_folders": None,
                "include_homeassistant": True,
                "name": f"Home Assistant Core {HAVERSION}",
                "password": None,
            },
        ),
        (
            [],
            "my_nas",
            {
                "agent_ids": ["hassio.my_nas"],
                "include_addons": None,
                "include_all_addons": False,
                "include_database": True,
                "include_folders": None,
                "include_homeassistant": True,
                "name": f"Home Assistant Core {HAVERSION}",
                "password": None,
            },
        ),
        (
            [
                {
                    "type": "backup/config/update",
                    "create_backup": {
                        "agent_ids": ["test-agent"],
                        "include_addons": ["my-addon"],
                        "include_all_addons": True,
                        "include_database": False,
                        "include_folders": ["share"],
                        "name": "cool_backup",
                        "password": "hunter2",
                    },
                },
            ],
            None,
            {
                "agent_ids": ["test-agent"],
                "include_addons": ["my-addon"],
                "include_all_addons": True,
                "include_database": False,
                "include_folders": ["share"],
                "include_homeassistant": True,
                "name": "cool_backup",
                "password": "hunter2",
                "with_automatic_settings": True,
            },
        ),
    ],
)
async def test_update_os_with_backup(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    supervisor_client: AsyncMock,
    commands: list[dict[str, Any]],
    default_mount: str | None,
    expected_kwargs: dict[str, Any],
) -> None:
    """Test updating OS update entity."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await setup_backup_integration(hass)

    client = await hass_ws_client(hass)
    for command in commands:
        await client.send_json_auto_id(command)
        result = await client.receive_json()
        assert result["success"]

    supervisor_client.os.update.return_value = None
    supervisor_client.mounts.info.return_value.default_backup_mount = default_mount
    with patch(
        "homeassistant.components.backup.manager.BackupManager.async_create_backup",
    ) as mock_create_backup:
        await hass.services.async_call(
            "update",
            "install",
            {
                "entity_id": "update.home_assistant_operating_system_update",
                "backup": True,
            },
            blocking=True,
        )
    mock_create_backup.assert_called_once_with(**expected_kwargs)
    supervisor_client.os.update.assert_called_once_with(OSUpdate(version=None))


async def test_update_core(hass: HomeAssistant, supervisor_client: AsyncMock) -> None:
    """Test updating core update entity."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    supervisor_client.homeassistant.update.return_value = None
    with patch(
        "homeassistant.components.backup.manager.BackupManager.async_create_backup",
    ) as mock_create_backup:
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.home_assistant_core_update"},
            blocking=True,
        )
    mock_create_backup.assert_not_called()
    supervisor_client.homeassistant.update.assert_called_once_with(
        HomeAssistantUpdateOptions(version=None, backup=False)
    )


async def test_update_core_progress(
    hass: HomeAssistant, hass_supervisor_ws_client: WebSocketGenerator
) -> None:
    """Test progress reporting for core update."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    client = await hass_supervisor_ws_client()
    message_id = 0
    job_uuid = uuid4().hex

    def make_job_message(
        progress: float, done: bool | None, errors: list[dict[str, str]] | None = None
    ):
        nonlocal message_id
        message_id += 1
        return {
            "id": message_id,
            "type": "supervisor/event",
            "data": {
                "event": "job",
                "data": {
                    "uuid": job_uuid,
                    "created": "2025-09-29T00:00:00.000000+00:00",
                    "name": "home_assistant_core_update",
                    "reference": None,
                    "progress": progress,
                    "done": done,
                    "stage": None,
                    "extra": {"total": 1234567890} if progress > 0 else None,
                    "errors": errors or [],
                },
            },
        }

    await client.send_json(make_job_message(progress=0, done=None))
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    assert (
        hass.states.get("update.home_assistant_core_update").attributes.get(
            "in_progress"
        )
        is False
    )
    assert (
        hass.states.get("update.home_assistant_core_update").attributes.get(
            "update_percentage"
        )
        is None
    )

    await client.send_json(make_job_message(progress=5, done=False))
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    assert (
        hass.states.get("update.home_assistant_core_update").attributes.get(
            "in_progress"
        )
        is True
    )
    assert (
        hass.states.get("update.home_assistant_core_update").attributes.get(
            "update_percentage"
        )
        == 5
    )

    await client.send_json(make_job_message(progress=50, done=False))
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    assert (
        hass.states.get("update.home_assistant_core_update").attributes.get(
            "in_progress"
        )
        is True
    )
    assert (
        hass.states.get("update.home_assistant_core_update").attributes.get(
            "update_percentage"
        )
        == 50
    )

    # During a successful update Home Assistant is stopped before the update job
    # reaches the end. An error ends it early so we use that for test
    await client.send_json(
        make_job_message(
            progress=70,
            done=True,
            errors=[
                {"type": "HomeAssistantUpdateError", "message": "bad", "stage": None}
            ],
        )
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    assert (
        hass.states.get("update.home_assistant_core_update").attributes.get(
            "in_progress"
        )
        is False
    )
    assert (
        hass.states.get("update.home_assistant_core_update").attributes.get(
            "update_percentage"
        )
        is None
    )


async def test_core_update_progress_startup(
    hass: HomeAssistant, jobs_info: AsyncMock
) -> None:
    """Test core update in progress during home assistant startup.

    This is an odd test, it's very unlikely core will be starting during an update.
    It is technically possible though as core isn't stopped until the docker portion
    is complete and updates can be started from CLI.
    """
    jobs_info.return_value = JobsInfo(
        ignore_conditions=[],
        jobs=[
            Job(
                name="home_assistant_core_update",
                reference=None,
                uuid=uuid4().hex,
                progress=50,
                stage=None,
                done=False,
                errors=[],
                created=datetime.now(),
                child_jobs=[],
                extra={"total": 1234567890},
            )
        ],
    )

    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    assert (
        hass.states.get("update.home_assistant_core_update").attributes.get(
            "in_progress"
        )
        is True
    )
    assert (
        hass.states.get("update.home_assistant_core_update").attributes.get(
            "update_percentage"
        )
        == 50
    )


@pytest.mark.parametrize(
    ("commands", "default_mount", "expected_kwargs"),
    [
        (
            [],
            None,
            {
                "agent_ids": ["hassio.local"],
                "include_addons": None,
                "include_all_addons": False,
                "include_database": True,
                "include_folders": None,
                "include_homeassistant": True,
                "name": f"Home Assistant Core {HAVERSION}",
                "password": None,
            },
        ),
        (
            [],
            "my_nas",
            {
                "agent_ids": ["hassio.my_nas"],
                "include_addons": None,
                "include_all_addons": False,
                "include_database": True,
                "include_folders": None,
                "include_homeassistant": True,
                "name": f"Home Assistant Core {HAVERSION}",
                "password": None,
            },
        ),
        (
            [
                {
                    "type": "backup/config/update",
                    "create_backup": {
                        "agent_ids": ["test-agent"],
                        "include_addons": ["my-addon"],
                        "include_all_addons": True,
                        "include_database": False,
                        "include_folders": ["share"],
                        "name": "cool_backup",
                        "password": "hunter2",
                    },
                },
            ],
            None,
            {
                "agent_ids": ["test-agent"],
                "include_addons": ["my-addon"],
                "include_all_addons": True,
                "include_database": False,
                "include_folders": ["share"],
                "include_homeassistant": True,
                "name": "cool_backup",
                "password": "hunter2",
                "with_automatic_settings": True,
            },
        ),
    ],
)
async def test_update_core_with_backup(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    supervisor_client: AsyncMock,
    commands: list[dict[str, Any]],
    default_mount: str | None,
    expected_kwargs: dict[str, Any],
) -> None:
    """Test updating core update entity."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await setup_backup_integration(hass)

    client = await hass_ws_client(hass)
    for command in commands:
        await client.send_json_auto_id(command)
        result = await client.receive_json()
        assert result["success"]

    supervisor_client.homeassistant.update.return_value = None
    supervisor_client.mounts.info.return_value.default_backup_mount = default_mount
    with patch(
        "homeassistant.components.backup.manager.BackupManager.async_create_backup",
    ) as mock_create_backup:
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.home_assistant_core_update", "backup": True},
            blocking=True,
        )
    mock_create_backup.assert_called_once_with(**expected_kwargs)
    supervisor_client.homeassistant.update.assert_called_once_with(
        HomeAssistantUpdateOptions(version=None, backup=False)
    )


async def test_update_core_sets_progress_immediately(
    hass: HomeAssistant, supervisor_client: AsyncMock
) -> None:
    """Test core update sets in_progress immediately when install starts."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    state = hass.states.get("update.home_assistant_core_update")
    assert state.attributes.get("in_progress") is False

    # Mock update_core to verify in_progress is set before it's called
    async def check_progress(
        hass: HomeAssistant, version: str | None, backup: bool
    ) -> None:
        assert (
            hass.states.get("update.home_assistant_core_update").attributes.get(
                "in_progress"
            )
            is True
        )

    with patch(
        "homeassistant.components.hassio.update.update_core",
        side_effect=check_progress,
    ) as mock_update:
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.home_assistant_core_update", "backup": True},
            blocking=True,
        )

    mock_update.assert_called_once()


async def test_update_core_resets_progress_on_error(
    hass: HomeAssistant, supervisor_client: AsyncMock
) -> None:
    """Test core update resets in_progress to False when update fails."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    state = hass.states.get("update.home_assistant_core_update")
    assert state.attributes.get("in_progress") is False

    with (
        patch(
            "homeassistant.components.hassio.update.update_core",
            side_effect=HomeAssistantError,
        ),
        pytest.raises(HomeAssistantError),
    ):
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.home_assistant_core_update", "backup": True},
            blocking=True,
        )

    state = hass.states.get("update.home_assistant_core_update")
    assert state.attributes.get("in_progress") is False, (
        "in_progress should be reset to False after error"
    )


async def test_update_addon_sets_progress_immediately(
    hass: HomeAssistant, supervisor_client: AsyncMock
) -> None:
    """Test addon update sets in_progress immediately when install starts."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    state = hass.states.get("update.test_update")
    assert state.attributes.get("in_progress") is False

    # Mock update_addon to verify in_progress is set before it's called
    async def check_progress(
        hass: HomeAssistant,
        addon: str,
        backup: bool,
        addon_name: str | None,
        installed_version: str | None,
    ) -> None:
        assert (
            hass.states.get("update.test_update").attributes.get("in_progress") is True
        )

    with patch(
        "homeassistant.components.hassio.update.update_addon",
        side_effect=check_progress,
    ) as mock_update:
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.test_update", "backup": True},
            blocking=True,
        )

    mock_update.assert_called_once()


async def test_update_addon_resets_progress_on_error(
    hass: HomeAssistant,
    hass_supervisor_ws_client: WebSocketGenerator,
    supervisor_client: AsyncMock,
) -> None:
    """Test addon update resets in_progress and update_percentage on failure."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    state = hass.states.get("update.test_update")
    assert state.attributes.get("in_progress") is False
    assert state.attributes.get("update_percentage") is None

    ws = await hass_supervisor_ws_client()
    job_uuid = uuid4().hex

    async def fake_update_addon_error(
        _hass: HomeAssistant,
        _addon: str,
        _backup: bool,
        _addon_name: str | None,
        _installed_version: str | None,
    ) -> None:
        """Report some progress, then fail - as a mid-pull network error would."""
        await ws.send_json(
            {
                "id": 1,
                "type": "supervisor/event",
                "data": {
                    "event": "job",
                    "data": {
                        "uuid": job_uuid,
                        "created": "2025-09-29T00:00:00.000000+00:00",
                        "name": "addon_manager_update",
                        "reference": "test",
                        "progress": 42,
                        "done": False,
                        "stage": None,
                        "extra": {"total": 1234567890},
                        "errors": [],
                    },
                },
            }
        )
        msg = await ws.receive_json()
        assert msg["success"]
        await hass.async_block_till_done()
        raise HomeAssistantError

    with (
        patch(
            "homeassistant.components.hassio.update.update_addon",
            side_effect=fake_update_addon_error,
        ),
        pytest.raises(HomeAssistantError),
    ):
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.test_update", "backup": True},
            blocking=True,
        )

    state = hass.states.get("update.test_update")
    assert state.attributes.get("in_progress") is False, (
        "in_progress should be reset to False after error"
    )
    assert state.attributes.get("update_percentage") is None, (
        "update_percentage should be reset to None after error"
    )


def _bump_addon_to(
    addons_list: AsyncMock,
    addon_installed: AsyncMock,
    version: str,
    version_latest: str,
) -> None:
    """Rewrite the addon fixtures to report a post-update version."""
    current = addons_list.return_value
    addons_list.return_value = [
        replace(
            current[0],
            version=version,
            version_latest=version_latest,
            update_available=version != version_latest,
        ),
        *current[1:],
    ]

    def _updated_info(slug: str):
        addon = Mock(
            spec=InstalledAddonComplete,
            to_dict=addon_installed.return_value.to_dict,
            **addon_installed.return_value.to_dict(),
        )
        addon.name = "test"
        addon.slug = "test"
        addon.version = version
        addon.version_latest = version_latest
        addon.update_available = version != version_latest
        addon.state = AddonState.STARTED
        addon.url = "https://github.com/home-assistant/addons/test"
        addon.auto_update = True
        return addon

    addon_installed.side_effect = _updated_info


async def test_update_addon_stays_in_progress_until_refresh(
    hass: HomeAssistant,
    hass_supervisor_ws_client: WebSocketGenerator,
    update_addon: AsyncMock,
    addon_installed: AsyncMock,
    addons_list: AsyncMock,
) -> None:
    """Test addon update entity stays in progress until coordinator refresh.

    Supervisor emits the ``addon_manager_update`` job ``done=True`` WS event a
    few milliseconds before ``/store/addons/<slug>/update`` returns. Without
    the ``_update_ongoing`` guard, ``_attr_in_progress`` is cleared while the
    coordinator still holds the pre-update version and the UI briefly flips
    back to "Update available".
    """
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        assert await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
    await hass.async_block_till_done()

    entity_id = "update.test_update"
    assert hass.states.get(entity_id).state == "on"

    ws = await hass_supervisor_ws_client()
    job_uuid = uuid4().hex
    in_progress_after_done: list[bool | None] = []

    async def fake_update_addon(slug: str, _options: StoreAddonUpdate) -> None:
        """Mimic Supervisor: fire done=True on WS, then return HTTP response."""
        await ws.send_json(
            {
                "id": 1,
                "type": "supervisor/event",
                "data": {
                    "event": "job",
                    "data": {
                        "uuid": job_uuid,
                        "created": "2025-09-29T00:00:00.000000+00:00",
                        "name": "addon_manager_update",
                        "reference": "test",
                        "progress": 100,
                        "done": True,
                        "stage": None,
                        "extra": {"total": 1234567890},
                        "errors": [],
                    },
                },
            }
        )
        msg = await ws.receive_json()
        assert msg["success"]
        await hass.async_block_till_done()
        in_progress_after_done.append(
            hass.states.get(entity_id).attributes.get("in_progress")
        )
        _bump_addon_to(addons_list, addon_installed, "2.0.1", "2.0.1")

    update_addon.side_effect = fake_update_addon

    await hass.services.async_call(
        "update", "install", {"entity_id": entity_id}, blocking=True
    )

    # The done=True WS event fired mid-install must not drop in_progress; the
    # coordinator data at that instant still carries the pre-update version.
    assert in_progress_after_done == [True]

    state = hass.states.get(entity_id)
    assert state.attributes.get("in_progress") is False
    assert state.state == "off"


async def test_update_addon_completes_on_any_version_change(
    hass: HomeAssistant,
    update_addon: AsyncMock,
    addon_installed: AsyncMock,
    addons_list: AsyncMock,
) -> None:
    """Test completion when installed version changes from the pre-install one.

    If a newer upstream release appears between install start and the refresh,
    ``installed_version`` will not equal ``latest_version`` but will differ
    from the pre-install version. The ongoing flag must still clear.
    """
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        assert await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
    await hass.async_block_till_done()

    entity_id = "update.test_update"

    async def fake_update_addon(slug: str, _options: StoreAddonUpdate) -> None:
        _bump_addon_to(addons_list, addon_installed, "2.0.1", "2.0.2")

    update_addon.side_effect = fake_update_addon

    await hass.services.async_call(
        "update", "install", {"entity_id": entity_id}, blocking=True
    )

    state = hass.states.get(entity_id)
    assert state.attributes.get("in_progress") is False
    assert state.state == "on"


async def test_update_supervisor(
    hass: HomeAssistant, supervisor_client: AsyncMock
) -> None:
    """Test updating supervisor update entity."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    supervisor_client.supervisor.update.return_value = None
    await hass.services.async_call(
        "update",
        "install",
        {"entity_id": "update.home_assistant_supervisor_update"},
        blocking=True,
    )
    supervisor_client.supervisor.update.assert_called_once()


async def test_update_supervisor_progress(
    hass: HomeAssistant,
    hass_supervisor_ws_client: WebSocketGenerator,
    supervisor_info: AsyncMock,
) -> None:
    """Test progress reporting for a Supervisor update not initiated via entity.

    Covers CLI-triggered and Supervisor self-update flows: the entity must
    show download progress from job events and stay in the installing state
    across the Supervisor restart until the coordinator observes the new
    installed version.
    """
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        assert await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
    await hass.async_block_till_done()

    client = await hass_supervisor_ws_client()
    message_id = 0
    job_uuid = uuid4().hex
    entity_id = "update.home_assistant_supervisor_update"

    def make_job_message(progress: float, done: bool | None) -> dict[str, Any]:
        nonlocal message_id
        message_id += 1
        return {
            "id": message_id,
            "type": "supervisor/event",
            "data": {
                "event": "job",
                "data": {
                    "uuid": job_uuid,
                    "created": "2025-09-29T00:00:00.000000+00:00",
                    "name": "supervisor_update",
                    "reference": None,
                    "progress": progress,
                    "done": done,
                    "stage": None,
                    "extra": {"total": 1234567890} if progress > 0 else None,
                    "errors": [],
                },
            },
        }

    await client.send_json(make_job_message(progress=0, done=None))
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()
    assert hass.states.get(entity_id).attributes.get("in_progress") is False
    assert hass.states.get(entity_id).attributes.get("update_percentage") is None

    await client.send_json(make_job_message(progress=5, done=False))
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()
    assert hass.states.get(entity_id).attributes.get("in_progress") is True
    assert hass.states.get(entity_id).attributes.get("update_percentage") == 5

    await client.send_json(make_job_message(progress=50, done=False))
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()
    assert hass.states.get(entity_id).attributes.get("update_percentage") == 50

    # Job done: download finished, Supervisor is about to restart. The entity
    # must stay in the installing state until the new version is observed.
    await client.send_json(make_job_message(progress=100, done=True))
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()
    assert hass.states.get(entity_id).attributes.get("in_progress") is True
    assert hass.states.get(entity_id).attributes.get("update_percentage") is None

    # New Supervisor comes up and fires STARTUP_COMPLETE.
    supervisor_info.return_value = replace(
        supervisor_info.return_value,
        version="1.0.1dev222",
        update_available=False,
    )
    await client.send_json(
        {
            "id": message_id + 1,
            "type": "supervisor/event",
            "data": {
                "event": "supervisor_update",
                "update_key": "supervisor",
                "data": {"startup": "complete"},
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]

    async_fire_time_changed(
        hass, dt_util.utcnow() + timedelta(seconds=REQUEST_REFRESH_DELAY + 1)
    )
    await hass.async_block_till_done()

    assert hass.states.get(entity_id).attributes.get("in_progress") is False


async def test_update_supervisor_stays_in_progress_until_restart(
    hass: HomeAssistant,
    hass_supervisor_ws_client: WebSocketGenerator,
    supervisor_client: AsyncMock,
    supervisor_info: AsyncMock,
) -> None:
    """Test in_progress stays True after install returns, until Supervisor restart."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        assert await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
    await hass.async_block_till_done()

    entity_id = "update.home_assistant_supervisor_update"
    assert hass.states.get(entity_id).attributes.get("in_progress") is False

    supervisor_client.supervisor.update.return_value = None
    await hass.services.async_call(
        "update", "install", {"entity_id": entity_id}, blocking=True
    )

    # The install HTTP call returned, but Supervisor is still restarting.
    # The base UpdateEntity reset _attr_in_progress;
    # _update_ongoing keeps us in progress.
    assert hass.states.get(entity_id).attributes.get("in_progress") is True

    # Supervisor comes up with the new version and fires STARTUP_COMPLETE.
    supervisor_info.return_value = replace(
        supervisor_info.return_value,
        version="1.0.1dev222",
        update_available=False,
    )

    client = await hass_supervisor_ws_client()
    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "supervisor_update",
                "update_key": "supervisor",
                "data": {"startup": "complete"},
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]

    async_fire_time_changed(
        hass, dt_util.utcnow() + timedelta(seconds=REQUEST_REFRESH_DELAY + 1)
    )
    await hass.async_block_till_done()

    assert hass.states.get(entity_id).attributes.get("in_progress") is False


async def test_update_supervisor_completes_on_any_version_change(
    hass: HomeAssistant,
    hass_supervisor_ws_client: WebSocketGenerator,
    supervisor_client: AsyncMock,
    supervisor_info: AsyncMock,
) -> None:
    """Test completion is detected when installed version changes.

    If upstream publishes an even newer release between install-start and the
    post-restart refresh, installed_version will not equal latest_version but
    will differ from the pre-install version. _update_ongoing must still clear.
    """
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        assert await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
    await hass.async_block_till_done()

    entity_id = "update.home_assistant_supervisor_update"

    supervisor_client.supervisor.update.return_value = None
    await hass.services.async_call(
        "update", "install", {"entity_id": entity_id}, blocking=True
    )
    assert hass.states.get(entity_id).attributes.get("in_progress") is True

    supervisor_info.return_value = replace(
        supervisor_info.return_value,
        version="1.0.1dev222",
        version_latest="1.0.2dev223",
        update_available=True,
    )

    client = await hass_supervisor_ws_client()
    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "supervisor_update",
                "update_key": "supervisor",
                "data": {"startup": "complete"},
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]

    async_fire_time_changed(
        hass, dt_util.utcnow() + timedelta(seconds=REQUEST_REFRESH_DELAY + 1)
    )
    await hass.async_block_till_done()

    assert hass.states.get(entity_id).attributes.get("in_progress") is False


async def test_update_addon_with_error(
    hass: HomeAssistant,
    update_addon: AsyncMock,
) -> None:
    """Test updating addon update entity with error."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        assert await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
    await hass.async_block_till_done()

    update_addon.side_effect = SupervisorError
    with pytest.raises(HomeAssistantError, match=r"^Error updating test:"):
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.test_update"},
            blocking=True,
        )


@pytest.mark.parametrize(
    ("create_backup_error", "delete_filtered_backups_error", "message"),
    [
        (BackupManagerError, None, r"^Error creating backup: "),
        (None, BackupManagerError, r"^Error deleting old backups: "),
    ],
)
async def test_update_addon_with_backup_and_error(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    create_backup_error: Exception | None,
    delete_filtered_backups_error: Exception | None,
    message: str,
) -> None:
    """Test updating addon update entity with error."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await setup_backup_integration(hass)

    supervisor_client.homeassistant.update.return_value = None
    supervisor_client.mounts.info.return_value.default_backup_mount = None
    with (
        patch(
            "homeassistant.components.backup.manager.BackupManager.async_create_backup",
            side_effect=create_backup_error,
        ),
        patch(
            "homeassistant.components.backup.manager.BackupManager.async_delete_filtered_backups",
            side_effect=delete_filtered_backups_error,
        ),
        pytest.raises(HomeAssistantError, match=message),
    ):
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.test_update", "backup": True},
            blocking=True,
        )


async def test_update_os_with_error(
    hass: HomeAssistant, supervisor_client: AsyncMock
) -> None:
    """Test updating OS update entity with error."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        assert await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
    await hass.async_block_till_done()

    supervisor_client.os.update.side_effect = SupervisorError
    with pytest.raises(
        HomeAssistantError, match=r"^Error updating Home Assistant Operating System:"
    ):
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.home_assistant_operating_system_update"},
            blocking=True,
        )


async def test_update_os_with_backup_and_error(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
) -> None:
    """Test updating OS update entity with error."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await setup_backup_integration(hass)

    supervisor_client.os.update.return_value = None
    supervisor_client.mounts.info.return_value.default_backup_mount = None
    with (
        patch(
            "homeassistant.components.backup.manager.BackupManager.async_create_backup",
            side_effect=BackupManagerError,
        ),
        pytest.raises(HomeAssistantError, match=r"^Error creating backup:"),
    ):
        await hass.services.async_call(
            "update",
            "install",
            {
                "entity_id": "update.home_assistant_operating_system_update",
                "backup": True,
            },
            blocking=True,
        )


async def test_update_supervisor_with_error(
    hass: HomeAssistant, supervisor_client: AsyncMock
) -> None:
    """Test updating supervisor update entity with error."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        assert await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
    await hass.async_block_till_done()

    supervisor_client.supervisor.update.side_effect = SupervisorError
    with pytest.raises(
        HomeAssistantError, match=r"^Error updating Home Assistant Supervisor:"
    ):
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.home_assistant_supervisor_update"},
            blocking=True,
        )


async def test_update_core_with_error(
    hass: HomeAssistant, supervisor_client: AsyncMock
) -> None:
    """Test updating core update entity with error."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        assert await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
    await hass.async_block_till_done()

    supervisor_client.homeassistant.update.side_effect = SupervisorError
    with pytest.raises(
        HomeAssistantError, match=r"^Error updating Home Assistant Core:"
    ):
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.home_assistant_core_update"},
            blocking=True,
        )


async def test_update_core_with_backup_and_error(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
) -> None:
    """Test updating core update entity with error."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await setup_backup_integration(hass)

    supervisor_client.homeassistant.update.return_value = None
    supervisor_client.mounts.info.return_value.default_backup_mount = None
    with (
        patch(
            "homeassistant.components.backup.manager.BackupManager.async_create_backup",
            side_effect=BackupManagerError,
        ),
        pytest.raises(HomeAssistantError, match=r"^Error creating backup:"),
    ):
        await hass.services.async_call(
            "update",
            "install",
            {"entity_id": "update.home_assistant_core_update", "backup": True},
            blocking=True,
        )


async def test_release_notes_between_versions(
    hass: HomeAssistant, addon_changelog: AsyncMock, hass_ws_client: WebSocketGenerator
) -> None:
    """Test release notes between versions."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    addon_changelog.return_value = "# 2.0.1\nNew updates\n# 2.0.0\nOld updates"

    with (
        patch.dict(os.environ, MOCK_ENVIRON),
    ):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    client = await hass_ws_client(hass)
    await hass.async_block_till_done()

    await client.send_json(
        {
            "id": 1,
            "type": "update/release_notes",
            "entity_id": "update.test_update",
        }
    )
    result = await client.receive_json()
    assert "Old updates" not in result["result"]
    assert "New updates" in result["result"]


async def test_release_notes_full(
    hass: HomeAssistant, addon_changelog: AsyncMock, hass_ws_client: WebSocketGenerator
) -> None:
    """Test release notes no match."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    full_changelog = "# 2.0.0\nNew updates\n# 2.0.0\nOld updates"
    addon_changelog.return_value = full_changelog

    with (
        patch.dict(os.environ, MOCK_ENVIRON),
    ):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    client = await hass_ws_client(hass)
    await hass.async_block_till_done()

    await client.send_json(
        {
            "id": 1,
            "type": "update/release_notes",
            "entity_id": "update.test_update",
        }
    )
    result = await client.receive_json()
    assert "Old updates" in result["result"]
    assert "New updates" in result["result"]

    # Update entity without update should returns full changelog
    await client.send_json(
        {
            "id": 2,
            "type": "update/release_notes",
            "entity_id": "update.test2_update",
        }
    )
    result = await client.receive_json()
    assert result["result"] == full_changelog


async def test_not_release_notes(
    hass: HomeAssistant, addon_changelog: AsyncMock, hass_ws_client: WebSocketGenerator
) -> None:
    """Test handling where there are no release notes."""
    config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
    config_entry.add_to_hass(hass)

    addon_changelog.side_effect = SupervisorNotFoundError()

    with (
        patch.dict(os.environ, MOCK_ENVIRON),
    ):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    client = await hass_ws_client(hass)
    await hass.async_block_till_done()

    await client.send_json(
        {
            "id": 1,
            "type": "update/release_notes",
            "entity_id": "update.test_update",
        }
    )
    result = await client.receive_json()
    assert result["result"] is None


async def test_no_os_entity(
    hass: HomeAssistant, supervisor_root_info: AsyncMock
) -> None:
    """Test handling where there is no os entity."""
    supervisor_root_info.return_value = replace(
        supervisor_root_info.return_value, hassos=None
    )
    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        assert result
    await hass.async_block_till_done()

    # Verify that the entity does not exist
    assert not hass.states.get("update.home_assistant_operating_system_update")


async def test_setting_up_core_update_when_addon_fails(
    hass: HomeAssistant,
    caplog: pytest.LogCaptureFixture,
    addon_installed: AsyncMock,
    addon_stats: AsyncMock,
    addon_changelog: AsyncMock,
) -> None:
    """Test setting up core update when single addon fails."""
    addon_installed.side_effect = SupervisorBadRequestError("Addon Test does not exist")
    addon_stats.side_effect = SupervisorBadRequestError("add-on is not running")
    addon_changelog.side_effect = SupervisorBadRequestError("add-on is not running")
    with patch.dict(os.environ, MOCK_ENVIRON):
        result = await async_setup_component(
            hass,
            "hassio",
            {"http": {"server_port": 9999, "server_host": "127.0.0.1"}, "hassio": {}},
        )
        await hass.async_block_till_done()
    assert result

    # There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer
    async_fire_time_changed(
        hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
    )
    await hass.async_block_till_done()

    # Verify that the core update entity does exist
    state = hass.states.get("update.home_assistant_core_update")
    assert state
    assert state.state == "on"
