"""Test issues from supervisor issues."""

from collections.abc import Generator
import os
from typing import Any
from unittest.mock import ANY, AsyncMock, patch
from uuid import UUID, uuid4

from aiohasupervisor import (
    SupervisorBadRequestError,
    SupervisorError,
    SupervisorTimeoutError,
)
from aiohasupervisor.models import (
    Check,
    CheckType,
    ContextType,
    Issue,
    IssueType,
    ResolutionInfo,
    Suggestion,
    SuggestionType,
    UnhealthyReason,
    UnsupportedReason,
)
import pytest

from homeassistant.components.hassio.const import DOMAIN, HASSIO_ISSUES_UPDATE_INTERVAL
from homeassistant.components.hassio.coordinator import (
    IssueSubscription,
    IssueSubscriptionEvent,
    get_issues_info,
)
from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN
from homeassistant.core import HomeAssistant, callback
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util

from .test_init import MOCK_ENVIRON

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


@pytest.fixture(autouse=True)
async def setup_repairs(hass: HomeAssistant) -> None:
    """Set up the repairs integration."""
    assert await async_setup_component(hass, REPAIRS_DOMAIN, {REPAIRS_DOMAIN: {}})


@pytest.fixture(autouse=True)
def fixture_supervisor_environ() -> Generator[None]:
    """Mock os environ for supervisor."""
    with patch.dict(os.environ, MOCK_ENVIRON):
        yield


def mock_resolution_info(
    supervisor_client: AsyncMock,
    unsupported: list[UnsupportedReason | str] | None = None,
    unhealthy: list[UnhealthyReason | str] | None = None,
    issues: list[Issue] | None = None,
    suggestions_by_issue: dict[UUID, list[Suggestion]] | None = None,
    suggestion_result: SupervisorError | None = None,
) -> None:
    """Mock resolution/info endpoint with unsupported/unhealthy reasons."""
    supervisor_client.resolution.info.return_value = ResolutionInfo(
        unsupported=unsupported or [],
        unhealthy=unhealthy or [],
        issues=issues or [],
        suggestions=[
            suggestion
            for issue_list in suggestions_by_issue.values()
            for suggestion in issue_list
        ]
        if suggestions_by_issue
        else [],
        checks=[
            Check(enabled=True, slug=CheckType.DOCKER_CONFIG),
            Check(enabled=True, slug=CheckType.FREE_SPACE),
        ],
    )

    if suggestions_by_issue:

        async def mock_suggestions_for_issue(uuid: UUID) -> list[Suggestion]:
            """Mock of suggestions for issue api."""
            return suggestions_by_issue.get(uuid, [])

        supervisor_client.resolution.suggestions_for_issue.side_effect = (
            mock_suggestions_for_issue
        )
        supervisor_client.resolution.apply_suggestion.side_effect = suggestion_result


def assert_repair_in_list(
    issues: list[dict[str, Any]], unhealthy: bool, reason: str
) -> None:
    """Assert repair for unhealthy/unsupported in list."""
    repair_type = "unhealthy" if unhealthy else "unsupported"
    assert {
        "breaks_in_ha_version": None,
        "created": ANY,
        "dismissed_version": None,
        "domain": "hassio",
        "ignored": False,
        "is_fixable": False,
        "issue_id": f"{repair_type}_system_{reason}",
        "issue_domain": None,
        "learn_more_url": f"https://www.home-assistant.io/more-info/{repair_type}/{reason}",
        "severity": "critical" if unhealthy else "warning",
        "translation_key": f"{repair_type}_{reason}",
        "translation_placeholders": None,
    } in issues


def assert_issue_repair_in_list(
    issues: list[dict[str, Any]],
    uuid: str,
    context: str,
    type_: str,
    fixable: bool,
    *,
    reference: str | None = None,
    placeholders: dict[str, str] | None = None,
) -> None:
    """Assert repair for unhealthy/unsupported in list."""
    if reference:
        placeholders = (placeholders or {}) | {"reference": reference}
    assert {
        "breaks_in_ha_version": None,
        "created": ANY,
        "dismissed_version": None,
        "domain": "hassio",
        "ignored": False,
        "is_fixable": fixable,
        "issue_id": uuid,
        "issue_domain": None,
        "learn_more_url": None,
        "severity": "warning",
        "translation_key": f"issue_{context}_{type_}",
        "translation_placeholders": placeholders,
    } in issues


@pytest.mark.usefixtures("all_setup_requests")
async def test_unhealthy_issues(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test issues added for unhealthy systems."""
    mock_resolution_info(
        supervisor_client, unhealthy=[UnhealthyReason.DOCKER, UnhealthyReason.SETUP]
    )

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_ws_client(hass)

    await client.send_json({"id": 1, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 2
    assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="docker")
    assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="setup")


@pytest.mark.usefixtures("all_setup_requests")
@pytest.mark.parametrize("unhealthy_reason", list(UnhealthyReason))
async def test_unhealthy_reasons(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_ws_client: WebSocketGenerator,
    unhealthy_reason: UnhealthyReason,
) -> None:
    """Test all unhealthy reasons in client library are made into repairs."""
    mock_resolution_info(supervisor_client, unhealthy=[unhealthy_reason])

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_ws_client(hass)

    await client.send_json({"id": 1, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1
    assert_repair_in_list(
        msg["result"]["issues"], unhealthy=True, reason=unhealthy_reason.value
    )


@pytest.mark.usefixtures("all_setup_requests")
async def test_unsupported_issues(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test issues added for unsupported systems."""
    mock_resolution_info(
        supervisor_client,
        unsupported=[UnsupportedReason.CONNECTIVITY_CHECK, UnsupportedReason.OS],
    )

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_ws_client(hass)

    await client.send_json({"id": 1, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 2
    assert_repair_in_list(
        msg["result"]["issues"], unhealthy=False, reason="connectivity_check"
    )
    assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")


@pytest.mark.usefixtures("all_setup_requests")
@pytest.mark.parametrize(
    "unsupported_reason",
    [r for r in UnsupportedReason if r != UnsupportedReason.PRIVILEGED],
)
async def test_unsupported_reasons(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_ws_client: WebSocketGenerator,
    unsupported_reason: UnsupportedReason,
) -> None:
    """Test all unsupported reasons in client library are made into repairs."""
    mock_resolution_info(supervisor_client, unsupported=[unsupported_reason])

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_ws_client(hass)

    await client.send_json({"id": 1, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1
    assert_repair_in_list(
        msg["result"]["issues"], unhealthy=False, reason=unsupported_reason.value
    )


@pytest.mark.usefixtures("all_setup_requests")
async def test_unhealthy_issues_add_remove(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test unhealthy issues added and removed from dispatches."""
    mock_resolution_info(supervisor_client)

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "health_changed",
                "data": {
                    "healthy": False,
                    "unhealthy_reasons": ["docker"],
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 2, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1
    assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="docker")

    await client.send_json(
        {
            "id": 3,
            "type": "supervisor/event",
            "data": {
                "event": "health_changed",
                "data": {"healthy": True},
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 4, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert msg["result"] == {"issues": []}


@pytest.mark.usefixtures("all_setup_requests")
async def test_unsupported_issues_add_remove(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test unsupported issues added and removed from dispatches."""
    mock_resolution_info(supervisor_client)

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "supported_changed",
                "data": {
                    "supported": False,
                    "unsupported_reasons": ["os"],
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 2, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1
    assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")

    await client.send_json(
        {
            "id": 3,
            "type": "supervisor/event",
            "data": {
                "event": "supported_changed",
                "data": {"supported": True},
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 4, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert msg["result"] == {"issues": []}


@pytest.mark.usefixtures("all_setup_requests")
async def test_reset_issues_supervisor_restart(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """All issues reset on supervisor restart."""
    mock_resolution_info(
        supervisor_client,
        unsupported=[UnsupportedReason.OS],
        unhealthy=[UnhealthyReason.DOCKER],
        issues=[
            Issue(
                type=IssueType.REBOOT_REQUIRED,
                context=ContextType.SYSTEM,
                reference=None,
                uuid=(uuid := uuid4()),
                reference_extra=None,
            )
        ],
        suggestions_by_issue={
            uuid: [
                Suggestion(
                    SuggestionType.EXECUTE_REBOOT,
                    context=ContextType.SYSTEM,
                    reference=None,
                    uuid=uuid4(),
                    auto=False,
                    reference_extra=None,
                )
            ]
        },
    )

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json({"id": 1, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 3
    assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="docker")
    assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")
    assert_issue_repair_in_list(
        msg["result"]["issues"],
        uuid=uuid.hex,
        context="system",
        type_="reboot_required",
        fixable=True,
        reference=None,
    )

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

    await client.send_json({"id": 3, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert msg["result"] == {"issues": []}


@pytest.mark.usefixtures("all_setup_requests")
async def test_no_reset_issues_supervisor_update_found(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Issues do not reset because a supervisor update was found."""
    mock_resolution_info(
        supervisor_client,
        unsupported=[UnsupportedReason.OS],
    )

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json({"id": 1, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1

    mock_resolution_info(supervisor_client)
    await client.send_json(
        {
            "id": 2,
            "type": "supervisor/event",
            "data": {
                "event": "supervisor_update",
                "update_key": "supervisor",
                "data": {},
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 3, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1


@pytest.mark.usefixtures("all_setup_requests")
async def test_reasons_added_and_removed(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test an unsupported/unhealthy reasons being added and removed at same time."""
    mock_resolution_info(
        supervisor_client,
        unsupported=[UnsupportedReason.OS],
        unhealthy=[UnhealthyReason.DOCKER],
    )

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json({"id": 1, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 2
    assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="docker")
    assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")

    mock_resolution_info(
        supervisor_client,
        unsupported=[UnsupportedReason.CONNECTIVITY_CHECK],
        unhealthy=[UnhealthyReason.SETUP],
    )
    await client.send_json(
        {
            "id": 2,
            "type": "supervisor/event",
            "data": {
                "event": "supervisor_update",
                "update_key": "supervisor",
                "data": {"startup": "complete"},
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 3, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 2
    assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="setup")
    assert_repair_in_list(
        msg["result"]["issues"], unhealthy=False, reason="connectivity_check"
    )


@pytest.mark.usefixtures("all_setup_requests")
async def test_ignored_unsupported_skipped(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Unsupported reasons which have an identical unhealthy reason are ignored."""
    mock_resolution_info(
        supervisor_client,
        unsupported=[UnsupportedReason.PRIVILEGED],
        unhealthy=[UnhealthyReason.PRIVILEGED],
    )

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_ws_client(hass)

    await client.send_json({"id": 1, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1
    assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="privileged")


@pytest.mark.usefixtures("all_setup_requests")
async def test_new_unsupported_unhealthy_reason(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """New unsupported/unhealthy reasons result in a generic repair."""
    mock_resolution_info(
        supervisor_client,
        unsupported=["fake_unsupported"],
        unhealthy=["fake_unhealthy"],
    )

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_ws_client(hass)

    await client.send_json({"id": 1, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 2
    assert {
        "breaks_in_ha_version": None,
        "created": ANY,
        "dismissed_version": None,
        "domain": "hassio",
        "ignored": False,
        "is_fixable": False,
        "issue_id": "unhealthy_system_fake_unhealthy",
        "issue_domain": None,
        "learn_more_url": "https://www.home-assistant.io/more-info/unhealthy/fake_unhealthy",
        "severity": "critical",
        "translation_key": "unhealthy",
        "translation_placeholders": {"reason": "fake_unhealthy"},
    } in msg["result"]["issues"]
    assert {
        "breaks_in_ha_version": None,
        "created": ANY,
        "dismissed_version": None,
        "domain": "hassio",
        "ignored": False,
        "is_fixable": False,
        "issue_id": "unsupported_system_fake_unsupported",
        "issue_domain": None,
        "learn_more_url": "https://www.home-assistant.io/more-info/unsupported/fake_unsupported",
        "severity": "warning",
        "translation_key": "unsupported",
        "translation_placeholders": {"reason": "fake_unsupported"},
    } in msg["result"]["issues"]


@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test repairs added for supervisor issue."""
    mock_resolution_info(
        supervisor_client,
        issues=[
            Issue(
                type=IssueType.DETACHED_ADDON_MISSING,
                context=ContextType.ADDON,
                reference="test",
                uuid=(uuid_issue1 := uuid4()),
                reference_extra=None,
            ),
            Issue(
                type=IssueType.MULTIPLE_DATA_DISKS,
                context=ContextType.SYSTEM,
                reference="/dev/sda1",
                uuid=(uuid_issue2 := uuid4()),
                reference_extra=None,
            ),
            Issue(
                type="should_not_be_repair",
                context=ContextType.OS,
                reference=None,
                uuid=uuid4(),
                reference_extra=None,
            ),
        ],
        suggestions_by_issue={
            uuid_issue2: [
                Suggestion(
                    type=SuggestionType.RENAME_DATA_DISK,
                    context=ContextType.SYSTEM,
                    reference="/dev/sda1",
                    uuid=uuid4(),
                    auto=False,
                    reference_extra=None,
                )
            ]
        },
    )

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_ws_client(hass)

    await client.send_json({"id": 1, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 2
    assert_issue_repair_in_list(
        msg["result"]["issues"],
        uuid=uuid_issue1.hex,
        context="addon",
        type_="detached_addon_missing",
        fixable=False,
        reference="test",
        placeholders={"addon_url": "/hassio/addon/test", "addon": "test"},
    )
    assert_issue_repair_in_list(
        msg["result"]["issues"],
        uuid=uuid_issue2.hex,
        context="system",
        type_="multiple_data_disks",
        fixable=True,
        reference="/dev/sda1",
    )


@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_initial_failure(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    resolution_info: AsyncMock,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test initial issues refresh failure does not block hassio setup."""
    mock_resolution_info(
        supervisor_client,
        unsupported=[],
        unhealthy=[],
        issues=[
            Issue(
                type=IssueType.REBOOT_REQUIRED,
                context=ContextType.SYSTEM,
                reference=None,
                uuid=(uuid := uuid4()),
                reference_extra=None,
            )
        ],
        suggestions_by_issue={
            uuid: [
                Suggestion(
                    SuggestionType.EXECUTE_REBOOT,
                    context=ContextType.SYSTEM,
                    reference=None,
                    uuid=uuid4(),
                    auto=False,
                    reference_extra=None,
                )
            ]
        },
    )
    resolution_info.side_effect = [
        SupervisorBadRequestError("System is not ready with state: setup"),
        resolution_info.return_value,
    ]

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_ws_client(hass)
    await client.send_json({"id": 1, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 0


@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_add_remove(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test supervisor issues added and removed from dispatches."""
    mock_resolution_info(supervisor_client)

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "issue_changed",
                "data": {
                    "uuid": (issue_uuid := uuid4().hex),
                    "type": "reboot_required",
                    "context": "system",
                    "reference": None,
                    "reference_extra": None,
                    "suggestions": [
                        {
                            "uuid": uuid4().hex,
                            "type": "execute_reboot",
                            "context": "system",
                            "reference": None,
                            "reference_extra": None,
                        }
                    ],
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 2, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1
    assert_issue_repair_in_list(
        msg["result"]["issues"],
        uuid=issue_uuid,
        context="system",
        type_="reboot_required",
        fixable=True,
        reference=None,
    )

    await client.send_json(
        {
            "id": 3,
            "type": "supervisor/event",
            "data": {
                "event": "issue_removed",
                "data": {
                    "uuid": issue_uuid,
                    "type": "reboot_required",
                    "context": "system",
                    "reference": None,
                    "reference_extra": None,
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 4, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert msg["result"] == {"issues": []}


@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_suggestions_fail(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    resolution_suggestions_for_issue: AsyncMock,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test failing to get suggestions for issue skips it."""
    mock_resolution_info(
        supervisor_client,
        issues=[
            Issue(
                type=IssueType.REBOOT_REQUIRED,
                context=ContextType.SYSTEM,
                reference=None,
                uuid=uuid4(),
                reference_extra=None,
            )
        ],
    )
    resolution_suggestions_for_issue.side_effect = SupervisorTimeoutError

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_ws_client(hass)

    await client.send_json({"id": 1, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 0


@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_remove_missing_issue_without_error(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test HA skips message to remove issue that it didn't know about (sync issue)."""
    mock_resolution_info(supervisor_client)

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json(
        {
            "id": 5,
            "type": "supervisor/event",
            "data": {
                "event": "issue_removed",
                "data": {
                    "uuid": "1234",
                    "type": "reboot_required",
                    "context": "system",
                    "reference": None,
                    "reference_extra": None,
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()


@pytest.mark.usefixtures("all_setup_requests")
async def test_system_is_not_ready(
    hass: HomeAssistant,
    resolution_info: AsyncMock,
) -> None:
    """Ensure hassio starts despite issues refresh errors."""
    resolution_info.side_effect = SupervisorBadRequestError(
        "System is not ready with state: setup"
    )

    assert await async_setup_component(hass, DOMAIN, {})
    issues_coordinator = get_issues_info(hass)
    assert issues_coordinator is not None
    assert not issues_coordinator.issues


@pytest.mark.parametrize(
    "all_setup_requests", [{"include_addons": True}], indirect=True
)
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_app_port_conflict_single(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test supervisor issue for app port conflict with single execute_start suggestion."""
    mock_resolution_info(supervisor_client)

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "issue_changed",
                "data": {
                    "uuid": (issue_uuid := uuid4().hex),
                    "type": "app_port_conflict",
                    "context": "addon",
                    "reference": "test",
                    "reference_extra": {"port": 11443},
                    "suggestions": [
                        {
                            "uuid": uuid4().hex,
                            "type": "execute_start",
                            "context": "addon",
                            "reference": "test",
                            "reference_extra": {"port": 11443},
                        }
                    ],
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 2, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1
    assert_issue_repair_in_list(
        msg["result"]["issues"],
        uuid=issue_uuid,
        context="addon",
        type_="app_port_conflict",
        fixable=True,
        placeholders={
            "reference": "test",
            "addon": "test",
            "addon_url": "/hassio/addon/test",
            "port": "11443",
        },
    )


@pytest.mark.parametrize(
    "all_setup_requests", [{"include_addons": True}], indirect=True
)
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_app_port_conflict_menu(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test supervisor issue for app port conflict with two suggestions."""
    mock_resolution_info(supervisor_client)

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "issue_changed",
                "data": {
                    "uuid": (issue_uuid := uuid4().hex),
                    "type": "app_port_conflict",
                    "context": "addon",
                    "reference": "test",
                    "reference_extra": {"port": 11443},
                    "suggestions": [
                        {
                            "uuid": uuid4().hex,
                            "type": "execute_start",
                            "context": "addon",
                            "reference": "test",
                            "reference_extra": {"port": 11443},
                        },
                        {
                            "uuid": uuid4().hex,
                            "type": "clear_port_config",
                            "context": "addon",
                            "reference": "test",
                            "reference_extra": {"port": 11443},
                        },
                    ],
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 2, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1
    assert_issue_repair_in_list(
        msg["result"]["issues"],
        uuid=issue_uuid,
        context="addon",
        type_="app_port_conflict",
        fixable=True,
        placeholders={
            "reference": "test",
            "addon": "test",
            "addon_url": "/hassio/addon/test",
            "port": "11443",
        },
    )


@pytest.mark.parametrize(
    "all_setup_requests", [{"include_addons": True}], indirect=True
)
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_detached_addon_missing(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test supervisor issue for detached addon due to missing repository."""
    mock_resolution_info(supervisor_client)

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "issue_changed",
                "data": {
                    "uuid": (issue_uuid := uuid4().hex),
                    "type": "detached_addon_missing",
                    "context": "addon",
                    "reference": "test",
                    "reference_extra": None,
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 2, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1
    assert_issue_repair_in_list(
        msg["result"]["issues"],
        uuid=issue_uuid,
        context="addon",
        type_="detached_addon_missing",
        fixable=False,
        placeholders={
            "reference": "test",
            "addon": "test",
            "addon_url": "/hassio/addon/test",
        },
    )


@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_ntp_sync_failed(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test supervisor issue for NTP sync failed."""
    mock_resolution_info(supervisor_client)

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "issue_changed",
                "data": {
                    "uuid": (issue_uuid := uuid4().hex),
                    "type": "ntp_sync_failed",
                    "context": "system",
                    "reference": None,
                    "reference_extra": None,
                    "suggestions": [
                        {
                            "uuid": uuid4().hex,
                            "type": "enable_ntp",
                            "context": "system",
                            "reference": None,
                            "reference_extra": None,
                        }
                    ],
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 2, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1
    assert_issue_repair_in_list(
        msg["result"]["issues"],
        uuid=issue_uuid,
        context="system",
        type_="ntp_sync_failed",
        fixable=True,
        placeholders=None,
    )


@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_disk_lifetime(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test supervisor issue for disk lifetime nearly exceeded."""
    mock_resolution_info(supervisor_client)

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "issue_changed",
                "data": {
                    "uuid": (issue_uuid := uuid4().hex),
                    "type": "disk_lifetime",
                    "context": "system",
                    "reference": None,
                    "reference_extra": None,
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 2, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1
    assert_issue_repair_in_list(
        msg["result"]["issues"],
        uuid=issue_uuid,
        context="system",
        type_="disk_lifetime",
        fixable=False,
        placeholders=None,
    )


@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_free_space(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test supervisor issue for too little free space remaining."""
    mock_resolution_info(supervisor_client)

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "issue_changed",
                "data": {
                    "uuid": (issue_uuid := uuid4().hex),
                    "type": "free_space",
                    "context": "system",
                    "reference": None,
                    "reference_extra": None,
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 2, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1
    assert_issue_repair_in_list(
        msg["result"]["issues"],
        uuid=issue_uuid,
        context="system",
        type_="free_space",
        fixable=False,
        placeholders={
            "more_info_free_space": "https://www.home-assistant.io/more-info/free-space",
            "storage_url": "/config/storage",
            "free_space": "1.6",
        },
    )


@pytest.mark.parametrize(
    "all_setup_requests", [{"include_addons": True}], indirect=True
)
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_addon_pwned(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test supervisor issue for pwned secret in an addon."""
    mock_resolution_info(supervisor_client)

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    client = await hass_supervisor_ws_client()

    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "issue_changed",
                "data": {
                    "uuid": (issue_uuid := uuid4().hex),
                    "type": "pwned",
                    "context": "addon",
                    "reference": "test",
                    "reference_extra": None,
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()

    await client.send_json({"id": 2, "type": "repairs/list_issues"})
    msg = await client.receive_json()
    assert msg["success"]
    assert len(msg["result"]["issues"]) == 1
    assert_issue_repair_in_list(
        msg["result"]["issues"],
        uuid=issue_uuid,
        context="addon",
        type_="pwned",
        fixable=False,
        placeholders={
            "reference": "test",
            "addon": "test",
            "addon_url": "/hassio/addon/test",
            "more_info_pwned": "https://www.home-assistant.io/more-info/pwned-passwords",
        },
    )


@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_subscription_events(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test subscription callbacks for issue add/update/remove by key."""
    mock_resolution_info(supervisor_client)

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    issues_coordinator = get_issues_info(hass)
    assert issues_coordinator is not None

    events: list[str] = []

    @callback
    def _handle_subscription_event(event: IssueSubscriptionEvent) -> None:
        events.append(event.event)

    unsubscribe = issues_coordinator.subscribe(
        IssueSubscription(
            event_callback=_handle_subscription_event,
            key="issue_system_should_not_be_repair",
        )
    )

    client = await hass_supervisor_ws_client()

    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "issue_changed",
                "data": {
                    "uuid": (issue_uuid := uuid4().hex),
                    "type": "should_not_be_repair",
                    "context": "system",
                    "reference": None,
                    "reference_extra": None,
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()
    assert events == ["changed"]

    await client.send_json(
        {
            "id": 2,
            "type": "supervisor/event",
            "data": {
                "event": "issue_changed",
                "data": {
                    "uuid": issue_uuid,
                    "type": "should_not_be_repair",
                    "context": "system",
                    "reference": "updated",
                    "reference_extra": None,
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()
    assert events == ["changed", "changed"]

    await client.send_json(
        {
            "id": 3,
            "type": "supervisor/event",
            "data": {
                "event": "issue_removed",
                "data": {
                    "uuid": issue_uuid,
                    "type": "should_not_be_repair",
                    "context": "system",
                    "reference": "updated",
                    "reference_extra": None,
                },
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()
    assert events == ["changed", "changed", "removed"]

    unsubscribe()


@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_periodic_refresh_backstop(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
) -> None:
    """Test issues coordinator polls periodically without requiring subscribers."""
    mock_resolution_info(
        supervisor_client,
        issues=[
            Issue(
                type="should_not_be_repair",
                context=ContextType.SYSTEM,
                reference=None,
                uuid=uuid4(),
                reference_extra=None,
            )
        ],
    )

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    issues_coordinator = get_issues_info(hass)
    assert issues_coordinator is not None

    supervisor_client.resolution.info.reset_mock()

    async_fire_time_changed(hass, dt_util.utcnow() + HASSIO_ISSUES_UPDATE_INTERVAL)
    await hass.async_block_till_done()

    supervisor_client.resolution.info.assert_called_once()


@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_suggestions_change_updates_fixable_state(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test suggestion-only issue changes are not treated as unchanged."""
    mock_resolution_info(supervisor_client)

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    supervisor_client.resolution.info.reset_mock()
    issue_uuid = uuid4()

    supervisor_client.resolution.info.return_value = ResolutionInfo(
        unsupported=[],
        unhealthy=[],
        issues=[
            Issue(
                type="should_not_be_repair",
                context=ContextType.SYSTEM,
                reference=None,
                uuid=issue_uuid,
                reference_extra=None,
            )
        ],
        suggestions=[
            Suggestion(
                type=SuggestionType.EXECUTE_REBOOT,
                context=ContextType.SYSTEM,
                reference=None,
                uuid=uuid4(),
                auto=False,
                reference_extra=None,
            )
        ],
        checks=[
            Check(enabled=True, slug=CheckType.DOCKER_CONFIG),
            Check(enabled=True, slug=CheckType.FREE_SPACE),
        ],
    )
    supervisor_client.resolution.suggestions_for_issue.return_value = [
        Suggestion(
            type=SuggestionType.EXECUTE_REBOOT,
            context=ContextType.SYSTEM,
            reference=None,
            uuid=uuid4(),
            auto=False,
            reference_extra=None,
        )
    ]

    issues_coordinator = get_issues_info(hass)
    assert issues_coordinator is not None
    events: list[str] = []

    @callback
    def _subscription_event(event: IssueSubscriptionEvent) -> None:
        events.append(event.event)

    unsubscribe = issues_coordinator.subscribe(
        IssueSubscription(
            event_callback=_subscription_event,
            key="issue_system_should_not_be_repair",
        )
    )

    supervisor_client_ws = await hass_supervisor_ws_client()
    await supervisor_client_ws.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "issue_changed",
                "data": {
                    "uuid": issue_uuid.hex,
                    "type": "should_not_be_repair",
                    "context": "system",
                    "reference": None,
                    "reference_extra": None,
                },
            },
        }
    )
    msg = await supervisor_client_ws.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()
    assert events == ["changed"]

    await issues_coordinator.async_refresh()
    await hass.async_block_till_done()
    assert events == ["changed", "changed"]

    unsubscribe()


@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_periodic_refresh_recovers_after_initial_failure(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    resolution_info: AsyncMock,
) -> None:
    """Test a later refresh recovers issue state after initial refresh failure."""
    issue_uuid = uuid4()
    mock_resolution_info(
        supervisor_client,
        issues=[
            Issue(
                type="should_not_be_repair",
                context=ContextType.SYSTEM,
                reference=None,
                uuid=issue_uuid,
                reference_extra=None,
            )
        ],
        suggestions_by_issue={
            issue_uuid: [
                Suggestion(
                    SuggestionType.EXECUTE_REBOOT,
                    context=ContextType.SYSTEM,
                    reference=None,
                    uuid=uuid4(),
                    auto=False,
                    reference_extra=None,
                )
            ]
        },
    )
    resolution_info.side_effect = [
        SupervisorBadRequestError("System is not ready with state: setup"),
        resolution_info.return_value,
    ]

    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    issues_coordinator = get_issues_info(hass)
    assert issues_coordinator is not None
    assert len(issues_coordinator.issues) == 0

    await issues_coordinator.async_refresh()
    await hass.async_block_till_done()
    assert len(issues_coordinator.issues) == 1


@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_unload_disconnects_listener(
    hass: HomeAssistant,
    supervisor_client: AsyncMock,
    resolution_info: AsyncMock,
    hass_supervisor_ws_client: WebSocketGenerator,
) -> None:
    """Test SupervisorIssues.unload() disconnects the EVENT_SUPERVISOR_EVENT listener.

    After calling unload(), dispatching supervisor events must not trigger
    the listener — preventing listener accumulation on config-entry reload.
    """
    mock_resolution_info(supervisor_client)
    result = await async_setup_component(hass, DOMAIN, {})
    assert result

    # Get config entry
    entries = hass.config_entries.async_entries(DOMAIN)
    assert len(entries) == 1
    entry = entries[0]

    # Get issues instance
    issues = get_issues_info(hass)
    assert issues is not None
    assert issues.unhealthy_reasons == set()

    # While connected, a health_changed event updates unhealthy_reasons.
    client = await hass_supervisor_ws_client()
    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "health_changed",
                "data": {"healthy": False, "unhealthy_reasons": ["docker"]},
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()
    assert "docker" in issues.unhealthy_reasons

    # After config entry unload, the same event is silently ignored.
    await hass.config_entries.async_remove(entry.entry_id)
    await hass.async_block_till_done()

    await client.send_json(
        {
            "id": 2,
            "type": "supervisor/event",
            "data": {
                "event": "health_changed",
                "data": {"healthy": True},
            },
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await hass.async_block_till_done()
    # unhealthy_reasons unchanged — listener did not fire.
    assert "docker" in issues.unhealthy_reasons
