"""The tests for the hassio component."""

from __future__ import annotations

from typing import Any, Literal

import aiohttp
from aiohttp import hdrs, web
import pytest

from homeassistant.components.hassio import handler
from homeassistant.components.hassio.handler import HassIO, HassioAPIError
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from tests.test_util.aiohttp import AiohttpClientMocker


async def test_api_ping(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API ping."""
    aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"})

    assert await hassio_handler.is_connected()
    assert aioclient_mock.call_count == 1


async def test_api_ping_error(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API ping error."""
    aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "error"})

    assert not (await hassio_handler.is_connected())
    assert aioclient_mock.call_count == 1


async def test_api_ping_exeption(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API ping exception."""
    aioclient_mock.get("http://127.0.0.1/supervisor/ping", exc=aiohttp.ClientError())

    assert not (await hassio_handler.is_connected())
    assert aioclient_mock.call_count == 1


async def test_api_info(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API generic info."""
    aioclient_mock.get(
        "http://127.0.0.1/info",
        json={
            "result": "ok",
            "data": {"supervisor": "222", "homeassistant": "0.110.0", "hassos": None},
        },
    )

    data = await hassio_handler.get_info()
    assert aioclient_mock.call_count == 1
    assert data["hassos"] is None
    assert data["homeassistant"] == "0.110.0"
    assert data["supervisor"] == "222"


async def test_api_info_error(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API Home Assistant info error."""
    aioclient_mock.get(
        "http://127.0.0.1/info", json={"result": "error", "message": None}
    )

    with pytest.raises(HassioAPIError):
        await hassio_handler.get_info()

    assert aioclient_mock.call_count == 1


async def test_api_host_info(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API Host info."""
    aioclient_mock.get(
        "http://127.0.0.1/host/info",
        json={
            "result": "ok",
            "data": {
                "chassis": "vm",
                "operating_system": "Debian GNU/Linux 10 (buster)",
                "kernel": "4.19.0-6-amd64",
            },
        },
    )

    data = await hassio_handler.get_host_info()
    assert aioclient_mock.call_count == 1
    assert data["chassis"] == "vm"
    assert data["kernel"] == "4.19.0-6-amd64"
    assert data["operating_system"] == "Debian GNU/Linux 10 (buster)"


async def test_api_supervisor_info(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API Supervisor info."""
    aioclient_mock.get(
        "http://127.0.0.1/supervisor/info",
        json={
            "result": "ok",
            "data": {"supported": True, "version": "2020.11.1", "channel": "stable"},
        },
    )

    data = await hassio_handler.get_supervisor_info()
    assert aioclient_mock.call_count == 1
    assert data["supported"]
    assert data["version"] == "2020.11.1"
    assert data["channel"] == "stable"


async def test_api_os_info(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API OS info."""
    aioclient_mock.get(
        "http://127.0.0.1/os/info",
        json={
            "result": "ok",
            "data": {"board": "odroid-n2", "version": "2020.11.1"},
        },
    )

    data = await hassio_handler.get_os_info()
    assert aioclient_mock.call_count == 1
    assert data["board"] == "odroid-n2"
    assert data["version"] == "2020.11.1"


async def test_api_host_info_error(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API Home Assistant info error."""
    aioclient_mock.get(
        "http://127.0.0.1/host/info", json={"result": "error", "message": None}
    )

    with pytest.raises(HassioAPIError):
        await hassio_handler.get_host_info()

    assert aioclient_mock.call_count == 1


async def test_api_core_info(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API Home Assistant Core info."""
    aioclient_mock.get(
        "http://127.0.0.1/core/info",
        json={"result": "ok", "data": {"version_latest": "1.0.0"}},
    )

    data = await hassio_handler.get_core_info()
    assert aioclient_mock.call_count == 1
    assert data["version_latest"] == "1.0.0"


async def test_api_core_info_error(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API Home Assistant Core info error."""
    aioclient_mock.get(
        "http://127.0.0.1/core/info", json={"result": "error", "message": None}
    )

    with pytest.raises(HassioAPIError):
        await hassio_handler.get_core_info()

    assert aioclient_mock.call_count == 1


async def test_api_homeassistant_stop(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API Home Assistant stop."""
    aioclient_mock.post("http://127.0.0.1/homeassistant/stop", json={"result": "ok"})

    assert await hassio_handler.stop_homeassistant()
    assert aioclient_mock.call_count == 1


async def test_api_homeassistant_restart(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API Home Assistant restart."""
    aioclient_mock.post("http://127.0.0.1/homeassistant/restart", json={"result": "ok"})

    assert await hassio_handler.restart_homeassistant()
    assert aioclient_mock.call_count == 1


async def test_api_addon_info(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API Add-on info."""
    aioclient_mock.get(
        "http://127.0.0.1/addons/test/info",
        json={"result": "ok", "data": {"name": "bla"}},
    )

    data = await hassio_handler.get_addon_info("test")
    assert data["name"] == "bla"
    assert aioclient_mock.call_count == 1


async def test_api_addon_stats(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API Add-on stats."""
    aioclient_mock.get(
        "http://127.0.0.1/addons/test/stats",
        json={"result": "ok", "data": {"memory_percent": 0.01}},
    )

    data = await hassio_handler.get_addon_stats("test")
    assert data["memory_percent"] == 0.01
    assert aioclient_mock.call_count == 1


async def test_api_core_stats(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API Add-on stats."""
    aioclient_mock.get(
        "http://127.0.0.1/core/stats",
        json={"result": "ok", "data": {"memory_percent": 0.01}},
    )

    data = await hassio_handler.get_core_stats()
    assert data["memory_percent"] == 0.01
    assert aioclient_mock.call_count == 1


async def test_api_supervisor_stats(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API Add-on stats."""
    aioclient_mock.get(
        "http://127.0.0.1/supervisor/stats",
        json={"result": "ok", "data": {"memory_percent": 0.01}},
    )

    data = await hassio_handler.get_supervisor_stats()
    assert data["memory_percent"] == 0.01
    assert aioclient_mock.call_count == 1


async def test_api_discovery_message(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API discovery message."""
    aioclient_mock.get(
        "http://127.0.0.1/discovery/test",
        json={"result": "ok", "data": {"service": "mqtt"}},
    )

    data = await hassio_handler.get_discovery_message("test")
    assert data["service"] == "mqtt"
    assert aioclient_mock.call_count == 1


async def test_api_retrieve_discovery(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API discovery message."""
    aioclient_mock.get(
        "http://127.0.0.1/discovery",
        json={"result": "ok", "data": {"discovery": [{"service": "mqtt"}]}},
    )

    data = await hassio_handler.retrieve_discovery_messages()
    assert data["discovery"][-1]["service"] == "mqtt"
    assert aioclient_mock.call_count == 1


async def test_api_ingress_panels(
    hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API Ingress panels."""
    aioclient_mock.get(
        "http://127.0.0.1/ingress/panels",
        json={
            "result": "ok",
            "data": {
                "panels": {
                    "slug": {
                        "enable": True,
                        "title": "Test",
                        "icon": "mdi:test",
                        "admin": False,
                    }
                }
            },
        },
    )

    data = await hassio_handler.get_ingress_panels()
    assert aioclient_mock.call_count == 1
    assert data["panels"]
    assert "slug" in data["panels"]


@pytest.mark.parametrize(
    ("api_call", "method", "payload"),
    [
        ("retrieve_discovery_messages", "GET", None),
        ("refresh_updates", "POST", None),
        ("update_diagnostics", "POST", True),
    ],
)
@pytest.mark.usefixtures("socket_enabled")
async def test_api_headers(
    aiohttp_raw_server,  # 'aiohttp_raw_server' must be before 'hass'!
    hass: HomeAssistant,
    api_call: str,
    method: Literal["GET", "POST"],
    payload: Any,
) -> None:
    """Test headers are forwarded correctly."""
    received_request = None

    async def mock_handler(request):
        """Return OK."""
        nonlocal received_request
        received_request = request
        return web.json_response({"result": "ok", "data": None})

    server = await aiohttp_raw_server(mock_handler)
    hassio_handler = HassIO(
        hass.loop,
        async_get_clientsession(hass),
        f"{server.host}:{server.port}",
    )

    api_func = getattr(hassio_handler, api_call)
    if payload:
        await api_func(payload)
    else:
        await api_func()
    assert received_request is not None

    assert received_request.method == method
    assert received_request.headers.get("X-Hass-Source") == "core.handler"

    if method == "GET":
        assert hdrs.CONTENT_TYPE not in received_request.headers
        return

    assert hdrs.CONTENT_TYPE in received_request.headers
    if payload:
        assert received_request.headers[hdrs.CONTENT_TYPE] == "application/json"
    else:
        assert received_request.headers[hdrs.CONTENT_TYPE] == "application/octet-stream"


@pytest.mark.usefixtures("hassio_stubs")
async def test_api_get_green_settings(
    hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API ping."""
    aioclient_mock.get(
        "http://127.0.0.1/os/boards/green",
        json={
            "result": "ok",
            "data": {
                "activity_led": True,
                "power_led": True,
                "system_health_led": True,
            },
        },
    )

    assert await handler.async_get_green_settings(hass) == {
        "activity_led": True,
        "power_led": True,
        "system_health_led": True,
    }
    assert aioclient_mock.call_count == 1


@pytest.mark.usefixtures("hassio_stubs")
async def test_api_set_green_settings(
    hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API ping."""
    aioclient_mock.post(
        "http://127.0.0.1/os/boards/green",
        json={"result": "ok", "data": {}},
    )

    assert (
        await handler.async_set_green_settings(
            hass, {"activity_led": True, "power_led": True, "system_health_led": True}
        )
        == {}
    )
    assert aioclient_mock.call_count == 1


@pytest.mark.usefixtures("hassio_stubs")
async def test_api_get_yellow_settings(
    hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API ping."""
    aioclient_mock.get(
        "http://127.0.0.1/os/boards/yellow",
        json={
            "result": "ok",
            "data": {"disk_led": True, "heartbeat_led": True, "power_led": True},
        },
    )

    assert await handler.async_get_yellow_settings(hass) == {
        "disk_led": True,
        "heartbeat_led": True,
        "power_led": True,
    }
    assert aioclient_mock.call_count == 1


@pytest.mark.usefixtures("hassio_stubs")
async def test_api_set_yellow_settings(
    hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API ping."""
    aioclient_mock.post(
        "http://127.0.0.1/os/boards/yellow",
        json={"result": "ok", "data": {}},
    )

    assert (
        await handler.async_set_yellow_settings(
            hass, {"disk_led": True, "heartbeat_led": True, "power_led": True}
        )
        == {}
    )
    assert aioclient_mock.call_count == 1


@pytest.mark.usefixtures("hassio_stubs")
async def test_api_reboot_host(
    hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
    """Test setup with API ping."""
    aioclient_mock.post(
        "http://127.0.0.1/host/reboot",
        json={"result": "ok", "data": {}},
    )

    assert await handler.async_reboot_host(hass) == {}
    assert aioclient_mock.call_count == 1


@pytest.mark.usefixtures("hassio_stubs")
async def test_send_command_invalid_command(hass: HomeAssistant) -> None:
    """Test send command fails when command is invalid."""
    hassio: HassIO = hass.data["hassio"]
    with pytest.raises(HassioAPIError):
        await hassio.send_command("/test/../bad")
