"""Fixtures for the System Monitor integration."""
from __future__ import annotations

from collections import namedtuple
from collections.abc import Generator
import socket
from unittest.mock import AsyncMock, Mock, patch

from psutil import NoSuchProcess, Process
from psutil._common import sdiskpart, sdiskusage, shwtemp, snetio, snicaddr, sswap
import pytest

from homeassistant.components.systemmonitor.const import DOMAIN
from homeassistant.core import HomeAssistant

from tests.common import MockConfigEntry

# Different depending on platform so making according to Linux
svmem = namedtuple(
    "svmem",
    [
        "total",
        "available",
        "percent",
        "used",
        "free",
        "active",
        "inactive",
        "buffers",
        "cached",
        "shared",
        "slab",
    ],
)


@pytest.fixture(autouse=True)
def mock_sys_platform() -> Generator[None, None, None]:
    """Mock sys platform to Linux."""
    with patch("sys.platform", "linux"):
        yield


class MockProcess(Process):
    """Mock a Process class."""

    def __init__(self, name: str, ex: bool = False) -> None:
        """Initialize the process."""
        super().__init__(1)
        self._name = name
        self._ex = ex

    def name(self):
        """Return a name."""
        if self._ex:
            raise NoSuchProcess(1, self._name)
        return self._name


@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
    """Mock setup entry."""
    with patch(
        "homeassistant.components.systemmonitor.async_setup_entry",
        return_value=True,
    ) as mock_setup_entry:
        yield mock_setup_entry


@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
    """Mock ConfigEntry."""
    return MockConfigEntry(
        title="System Monitor",
        domain=DOMAIN,
        data={},
        options={
            "sensor": {"process": ["python3", "pip"]},
            "resources": [
                "disk_use_percent_/",
                "disk_use_percent_/home/notexist/",
                "memory_free_",
                "network_out_eth0",
                "process_python3",
            ],
        },
    )


@pytest.fixture
async def mock_added_config_entry(
    hass: HomeAssistant,
    mock_psutil: Mock,
    mock_os: Mock,
    mock_util: Mock,
    mock_config_entry: MockConfigEntry,
) -> MockConfigEntry:
    """Mock ConfigEntry that's been added to HA."""
    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()
    assert DOMAIN in hass.config_entries.async_domains()
    return mock_config_entry


@pytest.fixture
def mock_process() -> list[MockProcess]:
    """Mock process."""
    _process_python = MockProcess("python3")
    _process_pip = MockProcess("pip")
    return [_process_python, _process_pip]


@pytest.fixture
def mock_psutil(mock_process: list[MockProcess]) -> Mock:
    """Mock psutil."""
    with patch(
        "homeassistant.components.systemmonitor.sensor.psutil",
        autospec=True,
    ) as mock_psutil:
        mock_psutil.disk_usage.return_value = sdiskusage(
            500 * 1024**2, 300 * 1024**2, 200 * 1024**2, 60.0
        )
        mock_psutil.swap_memory.return_value = sswap(
            100 * 1024**2, 60 * 1024**2, 40 * 1024**2, 60.0, 1, 1
        )
        mock_psutil.virtual_memory.return_value = svmem(
            100 * 1024**2,
            40 * 1024**2,
            40.0,
            60 * 1024**2,
            30 * 1024**2,
            1,
            1,
            1,
            1,
            1,
            1,
        )
        mock_psutil.net_io_counters.return_value = {
            "eth0": snetio(100 * 1024**2, 100 * 1024**2, 50, 50, 0, 0, 0, 0),
            "eth1": snetio(200 * 1024**2, 200 * 1024**2, 150, 150, 0, 0, 0, 0),
            "vethxyzxyz": snetio(300 * 1024**2, 300 * 1024**2, 150, 150, 0, 0, 0, 0),
        }
        mock_psutil.net_if_addrs.return_value = {
            "eth0": [
                snicaddr(
                    socket.AF_INET,
                    "192.168.1.1",
                    "255.255.255.0",
                    "255.255.255.255",
                    None,
                )
            ],
            "eth1": [
                snicaddr(
                    socket.AF_INET,
                    "192.168.10.1",
                    "255.255.255.0",
                    "255.255.255.255",
                    None,
                )
            ],
            "vethxyzxyz": [
                snicaddr(
                    socket.AF_INET,
                    "172.16.10.1",
                    "255.255.255.0",
                    "255.255.255.255",
                    None,
                )
            ],
        }
        mock_psutil.cpu_percent.return_value = 10.0
        mock_psutil.boot_time.return_value = 1703973338.0
        mock_psutil.process_iter.return_value = mock_process
        # sensors_temperatures not available on MacOS so we
        # need to override the spec
        mock_psutil.sensors_temperatures = Mock()
        mock_psutil.sensors_temperatures.return_value = {
            "cpu0-thermal": [shwtemp("cpu0-thermal", 50.0, 60.0, 70.0)]
        }
        mock_psutil.NoSuchProcess = NoSuchProcess
        yield mock_psutil


@pytest.fixture
def mock_util(mock_process) -> Mock:
    """Mock psutil."""
    with patch(
        "homeassistant.components.systemmonitor.util.psutil", autospec=True
    ) as mock_util:
        mock_util.net_if_addrs.return_value = {
            "eth0": [
                snicaddr(
                    socket.AF_INET,
                    "192.168.1.1",
                    "255.255.255.0",
                    "255.255.255.255",
                    None,
                )
            ],
            "eth1": [
                snicaddr(
                    socket.AF_INET,
                    "192.168.10.1",
                    "255.255.255.0",
                    "255.255.255.255",
                    None,
                )
            ],
            "vethxyzxyz": [
                snicaddr(
                    socket.AF_INET,
                    "172.16.10.1",
                    "255.255.255.0",
                    "255.255.255.255",
                    None,
                )
            ],
        }
        mock_process = [MockProcess("python3")]
        mock_util.process_iter.return_value = mock_process
        # sensors_temperatures not available on MacOS so we
        # need to override the spec
        mock_util.sensors_temperatures = Mock()
        mock_util.sensors_temperatures.return_value = {
            "cpu0-thermal": [shwtemp("cpu0-thermal", 50.0, 60.0, 70.0)]
        }
        mock_util.disk_partitions.return_value = [
            sdiskpart("test", "/", "ext4", "", 1, 1),
            sdiskpart("test2", "/media/share", "ext4", "", 1, 1),
            sdiskpart("test3", "/incorrect", "", "", 1, 1),
            sdiskpart("proc", "/proc/run", "proc", "", 1, 1),
        ]
        mock_util.disk_usage.return_value = sdiskusage(10, 10, 0, 0)
        yield mock_util


@pytest.fixture
def mock_os() -> Mock:
    """Mock os."""
    with patch("homeassistant.components.systemmonitor.sensor.os") as mock_os, patch(
        "homeassistant.components.systemmonitor.util.os"
    ) as mock_os_util:
        mock_os_util.name = "nt"
        mock_os.getloadavg.return_value = (1, 2, 3)
        yield mock_os