"""Support for System Bridge sensors."""
from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from typing import Final, cast

from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    CONF_PORT,
    PERCENTAGE,
    REVOLUTIONS_PER_MINUTE,
    UnitOfElectricPotential,
    UnitOfFrequency,
    UnitOfInformation,
    UnitOfPower,
    UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.util.dt import utcnow

from . import SystemBridgeEntity
from .const import DOMAIN
from .coordinator import SystemBridgeCoordinatorData, SystemBridgeDataUpdateCoordinator

ATTR_AVAILABLE: Final = "available"
ATTR_FILESYSTEM: Final = "filesystem"
ATTR_MOUNT: Final = "mount"
ATTR_SIZE: Final = "size"
ATTR_TYPE: Final = "type"
ATTR_USED: Final = "used"

PIXELS: Final = "px"


@dataclass
class SystemBridgeSensorEntityDescription(SensorEntityDescription):
    """Class describing System Bridge sensor entities."""

    # SystemBridgeSensor does not support UNDEFINED or None,
    # restrict the type to str.
    name: str = ""

    value: Callable = round


def battery_time_remaining(data: SystemBridgeCoordinatorData) -> datetime | None:
    """Return the battery time remaining."""
    if (value := getattr(data.battery, "sensors_secsleft", None)) is not None:
        return utcnow() + timedelta(seconds=value)
    return None


def cpu_power_package(data: SystemBridgeCoordinatorData) -> float | None:
    """Return the CPU package power."""
    if data.cpu.power_package is not None:
        return data.cpu.power_package
    return None


def cpu_power_per_cpu(
    data: SystemBridgeCoordinatorData,
    cpu: int,
) -> float | None:
    """Return CPU power per CPU."""
    if (value := getattr(data.cpu, f"power_per_cpu_{cpu}", None)) is not None:
        return value
    return None


def cpu_speed(data: SystemBridgeCoordinatorData) -> float | None:
    """Return the CPU speed."""
    if data.cpu.frequency_current is not None:
        return round(data.cpu.frequency_current / 1000, 2)
    return None


def gpu_core_clock_speed(data: SystemBridgeCoordinatorData, key: str) -> float | None:
    """Return the GPU core clock speed."""
    if (value := getattr(data.gpu, f"{key}_core_clock", None)) is not None:
        return round(value)
    return None


def gpu_memory_clock_speed(data: SystemBridgeCoordinatorData, key: str) -> float | None:
    """Return the GPU memory clock speed."""
    if (value := getattr(data.gpu, f"{key}_memory_clock", None)) is not None:
        return round(value)
    return None


def gpu_memory_free(data: SystemBridgeCoordinatorData, key: str) -> float | None:
    """Return the free GPU memory."""
    if (value := getattr(data.gpu, f"{key}_memory_free", None)) is not None:
        return round(value)
    return None


def gpu_memory_used(data: SystemBridgeCoordinatorData, key: str) -> float | None:
    """Return the used GPU memory."""
    if (value := getattr(data.gpu, f"{key}_memory_used", None)) is not None:
        return round(value)
    return None


def gpu_memory_used_percentage(
    data: SystemBridgeCoordinatorData, key: str
) -> float | None:
    """Return the used GPU memory percentage."""
    if ((used := getattr(data.gpu, f"{key}_memory_used", None)) is not None) and (
        (total := getattr(data.gpu, f"{key}_memory_total", None)) is not None
    ):
        return round(
            used / total * 100,
            2,
        )
    return None


def memory_free(data: SystemBridgeCoordinatorData) -> float | None:
    """Return the free memory."""
    if data.memory.virtual_free is not None:
        return round(data.memory.virtual_free / 1000**3, 2)
    return None


def memory_used(data: SystemBridgeCoordinatorData) -> float | None:
    """Return the used memory."""
    if data.memory.virtual_used is not None:
        return round(data.memory.virtual_used / 1000**3, 2)
    return None


BASE_SENSOR_TYPES: tuple[SystemBridgeSensorEntityDescription, ...] = (
    SystemBridgeSensorEntityDescription(
        key="boot_time",
        name="Boot time",
        device_class=SensorDeviceClass.TIMESTAMP,
        icon="mdi:av-timer",
        value=lambda data: datetime.fromtimestamp(
            data.system.boot_time, tz=timezone.utc
        ),
    ),
    SystemBridgeSensorEntityDescription(
        key="cpu_power_package",
        name="CPU Package Power",
        native_unit_of_measurement=UnitOfPower.WATT,
        state_class=SensorStateClass.MEASUREMENT,
        suggested_display_precision=2,
        icon="mdi:chip",
        value=cpu_power_package,
    ),
    SystemBridgeSensorEntityDescription(
        key="cpu_speed",
        name="CPU speed",
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=UnitOfFrequency.GIGAHERTZ,
        device_class=SensorDeviceClass.FREQUENCY,
        icon="mdi:speedometer",
        value=cpu_speed,
    ),
    SystemBridgeSensorEntityDescription(
        key="cpu_temperature",
        name="CPU temperature",
        entity_registry_enabled_default=False,
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        value=lambda data: data.cpu.temperature,
    ),
    SystemBridgeSensorEntityDescription(
        key="cpu_voltage",
        name="CPU voltage",
        entity_registry_enabled_default=False,
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=UnitOfElectricPotential.VOLT,
        value=lambda data: data.cpu.voltage,
    ),
    SystemBridgeSensorEntityDescription(
        key="kernel",
        name="Kernel",
        icon="mdi:devices",
        value=lambda data: data.system.platform,
    ),
    SystemBridgeSensorEntityDescription(
        key="memory_free",
        name="Memory free",
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=UnitOfInformation.GIGABYTES,
        device_class=SensorDeviceClass.DATA_SIZE,
        icon="mdi:memory",
        value=memory_free,
    ),
    SystemBridgeSensorEntityDescription(
        key="memory_used_percentage",
        name="Memory used %",
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=PERCENTAGE,
        icon="mdi:memory",
        value=lambda data: data.memory.virtual_percent,
    ),
    SystemBridgeSensorEntityDescription(
        key="memory_used",
        name="Memory used",
        entity_registry_enabled_default=False,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=UnitOfInformation.GIGABYTES,
        device_class=SensorDeviceClass.DATA_SIZE,
        icon="mdi:memory",
        value=memory_used,
    ),
    SystemBridgeSensorEntityDescription(
        key="os",
        name="Operating system",
        icon="mdi:devices",
        value=lambda data: f"{data.system.platform} {data.system.platform_version}",
    ),
    SystemBridgeSensorEntityDescription(
        key="processes_load",
        name="Load",
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=PERCENTAGE,
        icon="mdi:percent",
        value=lambda data: data.cpu.usage,
    ),
    SystemBridgeSensorEntityDescription(
        key="version",
        name="Version",
        icon="mdi:counter",
        value=lambda data: data.system.version,
    ),
    SystemBridgeSensorEntityDescription(
        key="version_latest",
        name="Latest version",
        icon="mdi:counter",
        value=lambda data: data.system.version_latest,
    ),
)

BATTERY_SENSOR_TYPES: tuple[SystemBridgeSensorEntityDescription, ...] = (
    SystemBridgeSensorEntityDescription(
        key="battery",
        name="Battery",
        device_class=SensorDeviceClass.BATTERY,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=PERCENTAGE,
        value=lambda data: data.battery.percentage,
    ),
    SystemBridgeSensorEntityDescription(
        key="battery_time_remaining",
        name="Battery time remaining",
        device_class=SensorDeviceClass.TIMESTAMP,
        value=battery_time_remaining,
    ),
)


async def async_setup_entry(
    hass: HomeAssistant,
    entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up System Bridge sensor based on a config entry."""
    coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]

    entities = []
    for description in BASE_SENSOR_TYPES:
        entities.append(
            SystemBridgeSensor(coordinator, description, entry.data[CONF_PORT])
        )

    for partition in coordinator.data.disk.partitions:
        entities.append(
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"filesystem_{partition.replace(':', '')}",
                    name=f"{partition} space used",
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=PERCENTAGE,
                    icon="mdi:harddisk",
                    value=lambda data, p=partition: getattr(
                        data.disk, f"usage_{p}_percent", None
                    ),
                ),
                entry.data[CONF_PORT],
            )
        )

    if (
        coordinator.data.battery
        and coordinator.data.battery.percentage
        and coordinator.data.battery.percentage > -1
    ):
        for description in BATTERY_SENSOR_TYPES:
            entities.append(
                SystemBridgeSensor(coordinator, description, entry.data[CONF_PORT])
            )

    displays: list[dict[str, str]] = []
    if coordinator.data.display.displays is not None:
        displays.extend(
            {
                "key": display,
                "name": getattr(coordinator.data.display, f"{display}_name").replace(
                    "Display ", ""
                ),
            }
            for display in coordinator.data.display.displays
            if hasattr(coordinator.data.display, f"{display}_name")
        )
    display_count = len(displays)

    entities.append(
        SystemBridgeSensor(
            coordinator,
            SystemBridgeSensorEntityDescription(
                key="displays_connected",
                name="Displays connected",
                state_class=SensorStateClass.MEASUREMENT,
                icon="mdi:monitor",
                value=lambda _, count=display_count: count,
            ),
            entry.data[CONF_PORT],
        )
    )

    for _, display in enumerate(displays):
        entities = [
            *entities,
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"display_{display['name']}_resolution_x",
                    name=f"Display {display['name']} resolution x",
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=PIXELS,
                    icon="mdi:monitor",
                    value=lambda data, k=display["key"]: getattr(
                        data.display, f"{k}_resolution_horizontal", None
                    ),
                ),
                entry.data[CONF_PORT],
            ),
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"display_{display['name']}_resolution_y",
                    name=f"Display {display['name']} resolution y",
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=PIXELS,
                    icon="mdi:monitor",
                    value=lambda data, k=display["key"]: getattr(
                        data.display, f"{k}_resolution_vertical", None
                    ),
                ),
                entry.data[CONF_PORT],
            ),
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"display_{display['name']}_refresh_rate",
                    name=f"Display {display['name']} refresh rate",
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=UnitOfFrequency.HERTZ,
                    device_class=SensorDeviceClass.FREQUENCY,
                    icon="mdi:monitor",
                    value=lambda data, k=display["key"]: getattr(
                        data.display, f"{k}_refresh_rate", None
                    ),
                ),
                entry.data[CONF_PORT],
            ),
        ]

    gpus: list[dict[str, str]] = []
    if coordinator.data.gpu.gpus is not None:
        gpus.extend(
            {
                "key": gpu,
                "name": getattr(coordinator.data.gpu, f"{gpu}_name"),
            }
            for gpu in coordinator.data.gpu.gpus
            if hasattr(coordinator.data.gpu, f"{gpu}_name")
        )

    for index, gpu in enumerate(gpus):
        entities = [
            *entities,
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"gpu_{index}_core_clock_speed",
                    name=f"{gpu['name']} clock speed",
                    entity_registry_enabled_default=False,
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ,
                    device_class=SensorDeviceClass.FREQUENCY,
                    icon="mdi:speedometer",
                    value=lambda data, k=gpu["key"]: gpu_core_clock_speed(data, k),
                ),
                entry.data[CONF_PORT],
            ),
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"gpu_{index}_memory_clock_speed",
                    name=f"{gpu['name']} memory clock speed",
                    entity_registry_enabled_default=False,
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ,
                    device_class=SensorDeviceClass.FREQUENCY,
                    icon="mdi:speedometer",
                    value=lambda data, k=gpu["key"]: gpu_memory_clock_speed(data, k),
                ),
                entry.data[CONF_PORT],
            ),
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"gpu_{index}_memory_free",
                    name=f"{gpu['name']} memory free",
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=UnitOfInformation.GIGABYTES,
                    device_class=SensorDeviceClass.DATA_SIZE,
                    icon="mdi:memory",
                    value=lambda data, k=gpu["key"]: gpu_memory_free(data, k),
                ),
                entry.data[CONF_PORT],
            ),
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"gpu_{index}_memory_used_percentage",
                    name=f"{gpu['name']} memory used %",
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=PERCENTAGE,
                    icon="mdi:memory",
                    value=lambda data, k=gpu["key"]: gpu_memory_used_percentage(
                        data, k
                    ),
                ),
                entry.data[CONF_PORT],
            ),
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"gpu_{index}_memory_used",
                    name=f"{gpu['name']} memory used",
                    entity_registry_enabled_default=False,
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=UnitOfInformation.GIGABYTES,
                    device_class=SensorDeviceClass.DATA_SIZE,
                    icon="mdi:memory",
                    value=lambda data, k=gpu["key"]: gpu_memory_used(data, k),
                ),
                entry.data[CONF_PORT],
            ),
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"gpu_{index}_fan_speed",
                    name=f"{gpu['name']} fan speed",
                    entity_registry_enabled_default=False,
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
                    icon="mdi:fan",
                    value=lambda data, k=gpu["key"]: getattr(
                        data.gpu, f"{k}_fan_speed", None
                    ),
                ),
                entry.data[CONF_PORT],
            ),
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"gpu_{index}_power_usage",
                    name=f"{gpu['name']} power usage",
                    entity_registry_enabled_default=False,
                    device_class=SensorDeviceClass.POWER,
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=UnitOfPower.WATT,
                    value=lambda data, k=gpu["key"]: getattr(
                        data.gpu, f"{k}_power", None
                    ),
                ),
                entry.data[CONF_PORT],
            ),
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"gpu_{index}_temperature",
                    name=f"{gpu['name']} temperature",
                    entity_registry_enabled_default=False,
                    device_class=SensorDeviceClass.TEMPERATURE,
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=UnitOfTemperature.CELSIUS,
                    value=lambda data, k=gpu["key"]: getattr(
                        data.gpu, f"{k}_temperature", None
                    ),
                ),
                entry.data[CONF_PORT],
            ),
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"gpu_{index}_usage_percentage",
                    name=f"{gpu['name']} usage %",
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=PERCENTAGE,
                    icon="mdi:percent",
                    value=lambda data, k=gpu["key"]: getattr(
                        data.gpu, f"{k}_core_load", None
                    ),
                ),
                entry.data[CONF_PORT],
            ),
        ]

    for index in range(coordinator.data.cpu.count):
        entities.append(
            SystemBridgeSensor(
                coordinator,
                SystemBridgeSensorEntityDescription(
                    key=f"processes_load_cpu_{index}",
                    name=f"Load CPU {index}",
                    entity_registry_enabled_default=False,
                    state_class=SensorStateClass.MEASUREMENT,
                    native_unit_of_measurement=PERCENTAGE,
                    icon="mdi:percent",
                    value=lambda data, k=index: getattr(data.cpu, f"usage_{k}", None),
                ),
                entry.data[CONF_PORT],
            )
        )
        if hasattr(coordinator.data.cpu, f"power_per_cpu_{index}"):
            entities.append(
                SystemBridgeSensor(
                    coordinator,
                    SystemBridgeSensorEntityDescription(
                        key=f"cpu_power_core_{index}",
                        name=f"CPU Core {index} Power",
                        entity_registry_enabled_default=False,
                        native_unit_of_measurement=UnitOfPower.WATT,
                        state_class=SensorStateClass.MEASUREMENT,
                        suggested_display_precision=2,
                        icon="mdi:chip",
                        value=lambda data, k=index: cpu_power_per_cpu(data, k),
                    ),
                    entry.data[CONF_PORT],
                )
            )

    async_add_entities(entities)


class SystemBridgeSensor(SystemBridgeEntity, SensorEntity):
    """Define a System Bridge sensor."""

    entity_description: SystemBridgeSensorEntityDescription

    def __init__(
        self,
        coordinator: SystemBridgeDataUpdateCoordinator,
        description: SystemBridgeSensorEntityDescription,
        api_port: int,
    ) -> None:
        """Initialize."""
        super().__init__(
            coordinator,
            api_port,
            description.key,
            description.name,
        )
        self.entity_description = description

    @property
    def native_value(self) -> StateType:
        """Return the state."""
        try:
            return cast(StateType, self.entity_description.value(self.coordinator.data))
        except TypeError:
            return None
