"""Test issues from supervisor issues."""
from __future__ import annotations

from asyncio import TimeoutError
import os
from typing import Any
from unittest.mock import ANY, patch

import pytest

from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component

from .test_init import MOCK_ENVIRON

from tests.test_util.aiohttp import AiohttpClientMocker
from tests.typing import WebSocketGenerator


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


@pytest.fixture(autouse=True)
async def mock_all(all_setup_requests):
    """Mock all setup requests."""


@pytest.fixture(autouse=True)
async def fixture_supervisor_environ():
    """Mock os environ for supervisor."""
    with patch.dict(os.environ, MOCK_ENVIRON):
        yield


def mock_resolution_info(
    aioclient_mock: AiohttpClientMocker,
    unsupported: list[str] | None = None,
    unhealthy: list[str] | None = None,
    issues: list[dict[str, str]] | None = None,
):
    """Mock resolution/info endpoint with unsupported/unhealthy reasons and/or issues."""
    aioclient_mock.get(
        "http://127.0.0.1/resolution/info",
        json={
            "result": "ok",
            "data": {
                "unsupported": unsupported or [],
                "unhealthy": unhealthy or [],
                "suggestions": [],
                "issues": [
                    {k: v for k, v in issue.items() if k != "suggestions"}
                    for issue in issues
                ]
                if issues
                else [],
                "checks": [
                    {"enabled": True, "slug": "supervisor_trust"},
                    {"enabled": True, "slug": "free_space"},
                ],
            },
        },
    )

    if issues:
        suggestions_by_issue = {
            issue["uuid"]: issue.get("suggestions", []) for issue in issues
        }
        for issue_uuid, suggestions in suggestions_by_issue.items():
            aioclient_mock.get(
                f"http://127.0.0.1/resolution/issue/{issue_uuid}/suggestions",
                json={"result": "ok", "data": {"suggestions": suggestions}},
            )
            for suggestion in suggestions:
                aioclient_mock.post(
                    f"http://127.0.0.1/resolution/suggestion/{suggestion['uuid']}",
                    json={"result": "ok"},
                )


def assert_repair_in_list(issues: list[dict[str, Any]], unhealthy: bool, reason: str):
    """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,
):
    """Assert repair for unhealthy/unsupported in list."""
    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": {"reference": reference} if reference else None,
    } in issues


async def test_unhealthy_issues(
    hass: HomeAssistant,
    aioclient_mock: AiohttpClientMocker,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test issues added for unhealthy systems."""
    mock_resolution_info(aioclient_mock, unhealthy=["docker", "setup"])

    result = await async_setup_component(hass, "hassio", {})
    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")


async def test_unsupported_issues(
    hass: HomeAssistant,
    aioclient_mock: AiohttpClientMocker,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test issues added for unsupported systems."""
    mock_resolution_info(aioclient_mock, unsupported=["content_trust", "os"])

    result = await async_setup_component(hass, "hassio", {})
    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="content_trust"
    )
    assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")


async def test_unhealthy_issues_add_remove(
    hass: HomeAssistant,
    aioclient_mock: AiohttpClientMocker,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test unhealthy issues added and removed from dispatches."""
    mock_resolution_info(aioclient_mock)

    result = await async_setup_component(hass, "hassio", {})
    assert result

    client = await hass_ws_client(hass)

    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": []}


async def test_unsupported_issues_add_remove(
    hass: HomeAssistant,
    aioclient_mock: AiohttpClientMocker,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test unsupported issues added and removed from dispatches."""
    mock_resolution_info(aioclient_mock)

    result = await async_setup_component(hass, "hassio", {})
    assert result

    client = await hass_ws_client(hass)

    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": []}


async def test_reset_issues_supervisor_restart(
    hass: HomeAssistant,
    aioclient_mock: AiohttpClientMocker,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """All issues reset on supervisor restart."""
    mock_resolution_info(
        aioclient_mock,
        unsupported=["os"],
        unhealthy=["docker"],
        issues=[
            {
                "uuid": "1234",
                "type": "reboot_required",
                "context": "system",
                "reference": None,
            }
        ],
    )

    result = await async_setup_component(hass, "hassio", {})
    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"]) == 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="1234",
        context="system",
        type_="reboot_required",
        fixable=False,
        reference=None,
    )

    aioclient_mock.clear_requests()
    mock_resolution_info(aioclient_mock)
    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 msg["result"] == {"issues": []}


async def test_reasons_added_and_removed(
    hass: HomeAssistant,
    aioclient_mock: AiohttpClientMocker,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test an unsupported/unhealthy reasons being added and removed at same time."""
    mock_resolution_info(aioclient_mock, unsupported=["os"], unhealthy=["docker"])

    result = await async_setup_component(hass, "hassio", {})
    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=False, reason="os")

    aioclient_mock.clear_requests()
    mock_resolution_info(
        aioclient_mock, unsupported=["content_trust"], unhealthy=["setup"]
    )
    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"]) == 2
    assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="setup")
    assert_repair_in_list(
        msg["result"]["issues"], unhealthy=False, reason="content_trust"
    )


async def test_ignored_unsupported_skipped(
    hass: HomeAssistant,
    aioclient_mock: AiohttpClientMocker,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Unsupported reasons which have an identical unhealthy reason are ignored."""
    mock_resolution_info(
        aioclient_mock, unsupported=["privileged"], unhealthy=["privileged"]
    )

    result = await async_setup_component(hass, "hassio", {})
    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")


async def test_new_unsupported_unhealthy_reason(
    hass: HomeAssistant,
    aioclient_mock: AiohttpClientMocker,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """New unsupported/unhealthy reasons result in a generic repair until next core update."""
    mock_resolution_info(
        aioclient_mock, unsupported=["fake_unsupported"], unhealthy=["fake_unhealthy"]
    )

    result = await async_setup_component(hass, "hassio", {})
    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"]


async def test_supervisor_issues(
    hass: HomeAssistant,
    aioclient_mock: AiohttpClientMocker,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test repairs added for supervisor issue."""
    mock_resolution_info(
        aioclient_mock,
        issues=[
            {
                "uuid": "1234",
                "type": "reboot_required",
                "context": "system",
                "reference": None,
            },
            {
                "uuid": "1235",
                "type": "multiple_data_disks",
                "context": "system",
                "reference": "/dev/sda1",
                "suggestions": [
                    {
                        "uuid": "1236",
                        "type": "rename_data_disk",
                        "context": "system",
                        "reference": "/dev/sda1",
                    }
                ],
            },
            {
                "uuid": "1237",
                "type": "should_not_be_repair",
                "context": "fake",
                "reference": None,
            },
        ],
    )

    result = await async_setup_component(hass, "hassio", {})
    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="1234",
        context="system",
        type_="reboot_required",
        fixable=False,
        reference=None,
    )
    assert_issue_repair_in_list(
        msg["result"]["issues"],
        uuid="1235",
        context="system",
        type_="multiple_data_disks",
        fixable=True,
        reference="/dev/sda1",
    )


async def test_supervisor_issues_add_remove(
    hass: HomeAssistant,
    aioclient_mock: AiohttpClientMocker,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test supervisor issues added and removed from dispatches."""
    mock_resolution_info(aioclient_mock)

    result = await async_setup_component(hass, "hassio", {})
    assert result

    client = await hass_ws_client(hass)

    await client.send_json(
        {
            "id": 1,
            "type": "supervisor/event",
            "data": {
                "event": "issue_changed",
                "data": {
                    "uuid": "1234",
                    "type": "reboot_required",
                    "context": "system",
                    "reference": 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="1234",
        context="system",
        type_="reboot_required",
        fixable=False,
        reference=None,
    )

    await client.send_json(
        {
            "id": 3,
            "type": "supervisor/event",
            "data": {
                "event": "issue_changed",
                "data": {
                    "uuid": "1234",
                    "type": "reboot_required",
                    "context": "system",
                    "reference": None,
                    "suggestions": [
                        {
                            "uuid": "1235",
                            "type": "execute_reboot",
                            "context": "system",
                            "reference": 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 len(msg["result"]["issues"]) == 1
    assert_issue_repair_in_list(
        msg["result"]["issues"],
        uuid="1234",
        context="system",
        type_="reboot_required",
        fixable=True,
        reference=None,
    )

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

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


async def test_supervisor_issues_suggestions_fail(
    hass: HomeAssistant,
    aioclient_mock: AiohttpClientMocker,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test failing to get suggestions for issue skips it."""
    aioclient_mock.get(
        "http://127.0.0.1/resolution/info",
        json={
            "result": "ok",
            "data": {
                "unsupported": [],
                "unhealthy": [],
                "suggestions": [],
                "issues": [
                    {
                        "uuid": "1234",
                        "type": "reboot_required",
                        "context": "system",
                        "reference": None,
                    }
                ],
                "checks": [
                    {"enabled": True, "slug": "supervisor_trust"},
                    {"enabled": True, "slug": "free_space"},
                ],
            },
        },
    )
    aioclient_mock.get(
        "http://127.0.0.1/resolution/issue/1234/suggestions",
        exc=TimeoutError(),
    )

    result = await async_setup_component(hass, "hassio", {})
    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


async def test_supervisor_remove_missing_issue_without_error(
    hass: HomeAssistant,
    aioclient_mock: AiohttpClientMocker,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test HA skips message to remove issue that it didn't know about (sync issue)."""
    mock_resolution_info(aioclient_mock)

    result = await async_setup_component(hass, "hassio", {})
    assert result

    client = await hass_ws_client(hass)

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