"""TrueNAS Controller."""

from __future__ import annotations

import logging

from datetime import datetime, timedelta

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.entity_registry import async_entries_for_config_entry
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from homeassistant.const import (
    CONF_API_KEY,
    CONF_HOST,
    CONF_NAME,
    CONF_SSL,
    CONF_VERIFY_SSL,
)

from .api import TrueNASAPI
from .apiparser import parse_api, utc_from_timestamp
from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)


# ---------------------------
#   TrueNASControllerData
# ---------------------------
class TrueNASCoordinator(DataUpdateCoordinator[None]):
    """TrueNASCoordinator Class."""

    def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry):
        """Initialize TrueNASCoordinator."""
        self.hass = hass
        self.config_entry: ConfigEntry = config_entry

        super().__init__(
            self.hass,
            _LOGGER,
            name=DOMAIN,
            update_interval=timedelta(seconds=60),
        )

        self.name = config_entry.data[CONF_NAME]
        self.host = config_entry.data[CONF_HOST]

        self.ds = {
            "interface": {},
            "disk": {},
            "pool": {},
            "dataset": {},
            "system_info": {},
            "service": {},
            "jail": {},
            "vm": {},
            "cloudsync": {},
            "replication": {},
            "snapshottask": {},
            "app": {},
        }

        self.api = TrueNASAPI(
            hass,
            config_entry.data[CONF_HOST],
            config_entry.data[CONF_API_KEY],
            config_entry.data[CONF_SSL],
            config_entry.data[CONF_VERIFY_SSL],
        )

        self._systemstats_errored = []
        self.datasets_hass_device_id = None
        self.last_updatecheck_update = datetime(1970, 1, 1)

        self._is_scale = False
        self._is_virtual = False
        self._version_major = 0
        self._version_minor = 0

    # ---------------------------
    #   connected
    # ---------------------------
    def connected(self) -> bool:
        """Return connected state."""
        return self.api.connected()

    # ---------------------------
    #   _async_update_data
    # ---------------------------
    async def _async_update_data(self):
        """Update TrueNAS data."""
        await self.hass.async_add_executor_job(self.get_systeminfo)
        if self.api.connected():
            await self.hass.async_add_executor_job(self.get_systemstats)
        if self.api.connected():
            await self.hass.async_add_executor_job(self.get_service)
        if self.api.connected():
            await self.hass.async_add_executor_job(self.get_disk)
        if self.api.connected():
            await self.hass.async_add_executor_job(self.get_dataset)
        if self.api.connected():
            await self.hass.async_add_executor_job(self.get_pool)
        if self.api.connected():
            await self.hass.async_add_executor_job(self.get_jail)
        if self.api.connected():
            await self.hass.async_add_executor_job(self.get_vm)
        if self.api.connected():
            await self.hass.async_add_executor_job(self.get_cloudsync)
        if self.api.connected():
            await self.hass.async_add_executor_job(self.get_replication)
        if self.api.connected():
            await self.hass.async_add_executor_job(self.get_snapshottask)
        if self.api.connected():
            await self.hass.async_add_executor_job(self.get_app)

        delta = datetime.now().replace(microsecond=0) - self.last_updatecheck_update
        if self.api.connected() and delta.total_seconds() > 60 * 60 * 12:
            await self.hass.async_add_executor_job(self.get_updatecheck)
            self.last_updatecheck_update = datetime.now().replace(microsecond=0)

        if not self.api.connected():
            raise UpdateFailed("TrueNas Disconnected")

        return self.ds

    # ---------------------------
    #   get_systeminfo
    # ---------------------------
    def get_systeminfo(self) -> None:
        """Get system info from TrueNAS."""
        self.ds["system_info"] = parse_api(
            data=self.ds["system_info"],
            source=self.api.query("system/info"),
            vals=[
                {"name": "version", "default": "unknown"},
                {"name": "hostname", "default": "unknown"},
                {"name": "uptime_seconds", "default": 0},
                {"name": "system_serial", "default": "unknown"},
                {"name": "system_product", "default": "unknown"},
                {"name": "system_manufacturer", "default": "unknown"},
            ],
            ensure_vals=[
                {"name": "uptimeEpoch", "default": 0},
                {"name": "cpu_temperature", "default": 0.0},
                {"name": "load_shortterm", "default": 0.0},
                {"name": "load_midterm", "default": 0.0},
                {"name": "load_longterm", "default": 0.0},
                {"name": "cpu_interrupt", "default": 0.0},
                {"name": "cpu_system", "default": 0.0},
                {"name": "cpu_user", "default": 0.0},
                {"name": "cpu_nice", "default": 0.0},
                {"name": "cpu_idle", "default": 0.0},
                {"name": "cpu_usage", "default": 0.0},
                {"name": "cache_size-arc_value", "default": 0.0},
                {"name": "memory-used_value", "default": 0.0},
                {"name": "memory-free_value", "default": 0.0},
                {"name": "memory-cached_value", "default": 0.0},
                {"name": "memory-buffered_value", "default": 0.0},
                {"name": "memory-total_value", "default": 0.0},
                {"name": "memory-usage_percent", "default": 0},
                {"name": "update_available", "type": "bool", "default": False},
                {"name": "update_progress", "default": 0},
                {"name": "update_jobid", "default": 0},
                {"name": "update_state", "default": "unknown"},
            ],
        )
        if not self.api.connected():
            return

        if not self.ds["system_info"]["update_available"]:
            self.ds["system_info"]["update_version"] = self.ds["system_info"]["version"]

        if self.ds["system_info"]["update_jobid"]:
            self.ds["system_info"] = parse_api(
                data=self.ds["system_info"],
                source=self.api.query(
                    "core/get_jobs",
                    method="get",
                    params={"id": self.ds["system_info"]["update_jobid"]},
                ),
                vals=[
                    {
                        "name": "update_progress",
                        "source": "progress/percent",
                        "default": 0,
                    },
                    {
                        "name": "update_state",
                        "source": "state",
                        "default": "unknown",
                    },
                ],
            )
            if not self.api.connected():
                return

            if (
                self.ds["system_info"]["update_state"] != "RUNNING"
                or not self.ds["system_info"]["update_available"]
            ):
                self.ds["system_info"]["update_progress"] = 0
                self.ds["system_info"]["update_jobid"] = 0
                self.ds["system_info"]["update_state"] = "unknown"

        self._is_scale = bool(
            self.ds["system_info"]["version"].startswith("TrueNAS-SCALE-")
        )

        if self._is_scale:
            if not self._version_major:
                self._version_major = int(
                    self.ds["system_info"]
                    .get("version")
                    .removeprefix("TrueNAS-")
                    .removeprefix("SCALE-")
                    .split(".")[0]
                )

            if not self._version_minor:
                self._version_minor = int(
                    self.ds["system_info"]
                    .get("version")
                    .removeprefix("TrueNAS-")
                    .removeprefix("SCALE-")
                    .split(".")[1]
                )

        self._is_virtual = self.ds["system_info"]["system_manufacturer"] in [
            "QEMU",
            "VMware, Inc.",
        ] or self.ds["system_info"]["system_product"] in [
            "VirtualBox",
        ]

        if self.ds["system_info"]["uptime_seconds"] > 0:
            now = datetime.now().replace(microsecond=0)
            uptime_tm = datetime.timestamp(
                now - timedelta(seconds=int(self.ds["system_info"]["uptime_seconds"]))
            )
            self.ds["system_info"]["uptimeEpoch"] = utc_from_timestamp(uptime_tm)

        self.ds["interface"] = parse_api(
            data=self.ds["interface"],
            source=self.api.query("interface"),
            key="id",
            vals=[
                {"name": "id", "default": "unknown"},
                {"name": "name", "default": "unknown"},
                {"name": "description", "default": "unknown"},
                {"name": "mtu", "default": "unknown"},
                {
                    "name": "link_state",
                    "source": "state/link_state",
                    "default": "unknown",
                },
                {
                    "name": "active_media_type",
                    "source": "state/active_media_type",
                    "default": "unknown",
                },
                {
                    "name": "active_media_subtype",
                    "source": "state/active_media_subtype",
                    "default": "unknown",
                },
                {
                    "name": "link_address",
                    "source": "state/link_address",
                    "default": "unknown",
                },
            ],
            ensure_vals=[
                {"name": "rx", "default": 0},
                {"name": "tx", "default": 0},
            ],
        )

    # ---------------------------
    #   get_updatecheck
    # ---------------------------
    def get_updatecheck(self) -> None:
        self.ds["system_info"] = parse_api(
            data=self.ds["system_info"],
            source=self.api.query("update/check_available", method="post"),
            vals=[
                {
                    "name": "update_status",
                    "source": "status",
                    "default": "unknown",
                },
                {
                    "name": "update_version",
                    "source": "version",
                    "default": "unknown",
                },
            ],
        )

        if not self.api.connected():
            return

        if (
            self.ds["system_info"]["update_version"] == "unknown"
            and self.ds["system_info"]["version"]
        ):
            self.ds["system_info"]["update_version"] = self.ds["system_info"]["version"]

        self.ds["system_info"]["update_available"] = (
            self.ds["system_info"]["update_status"] == "AVAILABLE"
        )

    # ---------------------------
    #   get_systemstats
    # ---------------------------
    def get_systemstats(self) -> None:
        """Get system statistics."""
        tmp_params = {
            "graphs": [
                {"name": "load"},
                {"name": "cputemp"},
                {"name": "cpu"},
                {"name": "arcsize"},
                {"name": "memory"},
            ],
            "reporting_query": {
                "start": "now-90s",
                "end": "now-30s",
                "aggregate": True,
            },
        }
        if self._is_scale and self._version_major == 23:
            tmp_params = {
                "graphs": [
                    {"name": "load"},
                    {"name": "cputemp"},
                    {"name": "cpu"},
                    {"name": "arcsize"},
                    {"name": "memory"},
                ],
                "reporting_query_netdata": {
                    "start": "-90",
                    "end": "-30",
                    "aggregate": True,
                },
            }
        elif self._is_scale and self._version_major >= 24:
            tmp_params = {
                "graphs": [
                    {"name": "load"},
                    {"name": "cputemp"},
                    {"name": "cpu"},
                    {"name": "arcsize"},
                    {"name": "memory"},
                ],
                "reporting_query": {
                    "start": "-90",
                    "end": "-30",
                    "aggregate": True,
                },
            }

        for uid, vals in self.ds["interface"].items():
            tmp_params["graphs"].append({"name": "interface", "identifier": uid})

        if self._is_virtual:
            tmp_params["graphs"].remove({"name": "cputemp"})

        for tmp in tmp_params["graphs"]:
            if tmp["name"] in self._systemstats_errored:
                tmp_params["graphs"].remove(tmp)

        if not tmp_params["graphs"]:
            return

        reporting_path = "reporting/get_data"
        if self._is_scale and self._version_major >= 23:
            reporting_path = "reporting/netdata_get_data"

        tmp_graph = self.api.query(
            reporting_path,
            method="post",
            params=tmp_params,
        )

        if not isinstance(tmp_graph, list):
            if self.api.error == 500:
                for tmp in tmp_params["graphs"]:
                    tmp_params2 = {
                        "graphs": [tmp],
                        "reporting_query": {
                            "start": "now-90s",
                            "end": "now-30s",
                            "aggregate": True,
                        },
                    }

                    if self._is_scale and self._version_major == 23:
                        tmp_params2 = {
                            "graphs": [tmp],
                            "reporting_query_netdata": {
                                "start": "-90",
                                "end": "-30",
                                "aggregate": True,
                            },
                        }
                    elif self._is_scale and self._version_major >= 24:
                        tmp_params2 = {
                            "graphs": [tmp],
                            "reporting_query": {
                                "start": "-90",
                                "end": "-30",
                                "aggregate": "true",
                            },
                        }

                    tmp2 = self.api.query(
                        reporting_path,
                        method="post",
                        params=tmp_params2,
                    )
                    if not isinstance(tmp2, list) and self.api.error == 500:
                        self._systemstats_errored.append(tmp["name"])

                _LOGGER.warning(
                    "TrueNAS %s fetching following graphs failed, check your NAS: %s",
                    self.host,
                    self._systemstats_errored,
                )
                self.get_systemstats()

            return

        for i in range(len(tmp_graph)):
            if "name" not in tmp_graph[i]:
                continue

            # CPU temperature
            if tmp_graph[i]["name"] == "cputemp":
                if "aggregations" in tmp_graph[i]:
                    if self._is_scale and self._version_major >= 23:
                        self.ds["system_info"]["cpu_temperature"] = round(
                            max(tmp_graph[i]["aggregations"]["mean"].values()), 2
                        )
                    else:
                        self.ds["system_info"]["cpu_temperature"] = round(
                            max(
                                list(filter(None, tmp_graph[i]["aggregations"]["mean"]))
                            ),
                            1,
                        )
                else:
                    self.ds["system_info"]["cpu_temperature"] = 0.0

            # CPU load
            if tmp_graph[i]["name"] == "load":
                tmp_arr = ("load_shortterm", "load_midterm", "load_longterm")
                if self._is_scale and self._version_major >= 23:
                    tmp_arr = ("shortterm", "midterm", "longterm")

                self._systemstats_process(tmp_arr, tmp_graph[i], "load")

            # CPU usage
            if tmp_graph[i]["name"] == "cpu":
                tmp_arr = ("interrupt", "system", "user", "nice", "idle")
                if self._is_scale and self._version_major >= 23:
                    tmp_arr = ("softirq", "system", "user", "nice", "iowait", "idle")

                self._systemstats_process(tmp_arr, tmp_graph[i], "cpu")
                self.ds["system_info"]["cpu_usage"] = round(
                    self.ds["system_info"]["cpu_system"]
                    + self.ds["system_info"]["cpu_user"],
                    2,
                )

            # Interface
            if tmp_graph[i]["name"] == "interface":
                tmp_etc = tmp_graph[i]["identifier"]
                if tmp_etc in self.ds["interface"]:
                    # 12->13 API change
                    tmp_graph[i]["legend"] = [
                        tmp.replace("if_octets_", "") for tmp in tmp_graph[i]["legend"]
                    ]
                    if self._is_scale and self._version_major >= 23:
                        tmp_graph[i]["legend"] = [
                            tmp.replace("received", "rx")
                            for tmp in tmp_graph[i]["legend"]
                        ]
                        tmp_graph[i]["legend"] = [
                            tmp.replace("sent", "tx") for tmp in tmp_graph[i]["legend"]
                        ]
                        tmp_graph[i]["aggregations"]["mean"] = {
                            k.replace("received", "rx"): v
                            for k, v in tmp_graph[i]["aggregations"]["mean"].items()
                        }
                        tmp_graph[i]["aggregations"]["mean"] = {
                            k.replace("sent", "tx"): v
                            for k, v in tmp_graph[i]["aggregations"]["mean"].items()
                        }

                    tmp_arr = ("rx", "tx")
                    if "aggregations" in tmp_graph[i]:
                        for e in range(len(tmp_graph[i]["legend"])):
                            tmp_var = tmp_graph[i]["legend"][e]
                            if tmp_var in tmp_arr:
                                if self._is_scale and self._version_major >= 23:
                                    tmp_val = (
                                        tmp_graph[i]["aggregations"]["mean"][tmp_var]
                                        or 0.0
                                        if tmp_var
                                        in tmp_graph[i]["aggregations"]["mean"]
                                        else 0.0
                                    )
                                    self.ds["interface"][tmp_etc][tmp_var] = round(
                                        (tmp_val * 0.12207), 2
                                    )
                                else:
                                    tmp_val = (
                                        tmp_graph[i]["aggregations"]["mean"][e] or 0.0
                                        if e in tmp_graph[i]["aggregations"]["mean"]
                                        else 0.0
                                    )
                                    self.ds["interface"][tmp_etc][tmp_var] = round(
                                        (tmp_val / 1024), 2
                                    )
                    else:
                        for tmp_load in tmp_arr:
                            self.ds["interface"][tmp_etc][tmp_load] = 0.0

            # memory
            if tmp_graph[i]["name"] == "memory":
                tmp_arr = (
                    "memory-used_value",
                    "memory-free_value",
                    "memory-cached_value",
                    "memory-buffered_value",
                )
                if self._is_scale and self._version_major >= 23:
                    tmp_arr = (
                        "free",
                        "used",
                        "cached",
                        "buffers",
                    )
                self._systemstats_process(tmp_arr, tmp_graph[i], "memory")
                self.ds["system_info"]["memory-total_value"] = round(
                    self.ds["system_info"]["memory-used_value"]
                    + self.ds["system_info"]["memory-free_value"]
                    + self.ds["system_info"]["cache_size-arc_value"]
                )
                if self.ds["system_info"]["memory-total_value"] > 0:
                    self.ds["system_info"]["memory-usage_percent"] = round(
                        100
                        * (
                            float(self.ds["system_info"]["memory-total_value"])
                            - float(self.ds["system_info"]["memory-free_value"])
                        )
                        / float(self.ds["system_info"]["memory-total_value"])
                    )

            # arcsize
            if tmp_graph[i]["name"] == "arcsize":
                tmp_arr = "cache_size-arc_value"
                if self._is_scale and self._version_major >= 23:
                    tmp_arr = "arc_size"
                self._systemstats_process(tmp_arr, tmp_graph[i], "arcsize")

    # ---------------------------
    #   _systemstats_process
    # ---------------------------
    def _systemstats_process(self, arr, graph, t) -> None:
        if "aggregations" in graph:
            for e in range(len(graph["legend"])):
                tmp_var = graph["legend"][e]
                if tmp_var in arr:
                    if self._is_scale and self._version_major >= 23:
                        e = tmp_var

                    tmp_val = graph["aggregations"]["mean"][e] or 0.0
                    if t == "memory":

                        if self._is_scale and self._version_major >= 23:
                            if tmp_var == "free":
                                self.ds["system_info"]["memory-free_value"] = round(
                                    tmp_val * 1024 * 1024
                                )
                            elif tmp_var == "used":
                                self.ds["system_info"]["memory-used_value"] = round(
                                    tmp_val * 1024 * 1024
                                )
                            elif tmp_var == "cached":
                                self.ds["system_info"]["memory-cached_value"] = round(
                                    tmp_val * 1024 * 1024
                                )
                            elif tmp_var == "buffers":
                                self.ds["system_info"]["memory-buffered_value"] = round(
                                    tmp_val * 1024 * 1024
                                )
                        else:
                            self.ds["system_info"][tmp_var] = tmp_val
                    elif t == "cpu":
                        self.ds["system_info"][f"cpu_{tmp_var}"] = round(tmp_val, 2)
                    elif t == "load":
                        if self._is_scale and self._version_major >= 23:
                            self.ds["system_info"][f"load_{tmp_var}"] = round(
                                tmp_val, 2
                            )
                        else:
                            self.ds["system_info"][tmp_var] = round(tmp_val, 2)
                    elif t == "arcsize":
                        if self._is_scale and self._version_major >= 23:
                            tmp_val = tmp_val * 1024 * 1024
                            self.ds["system_info"]["cache_size-arc_value"] = round(
                                tmp_val, 2
                            )
                        else:
                            self.ds["system_info"][tmp_var] = round(tmp_val, 2)
                    else:
                        self.ds["system_info"][tmp_var] = round(tmp_val, 2)
        else:
            for tmp_load in arr:
                if t == "cpu":
                    self.ds["system_info"][f"cpu_{tmp_load}"] = 0.0
                else:
                    self.ds["system_info"][tmp_load] = 0.0

    # ---------------------------
    #   get_service
    # ---------------------------
    def get_service(self) -> None:
        """Get service info from TrueNAS."""
        self.ds["service"] = parse_api(
            data=self.ds["service"],
            source=self.api.query("service"),
            key="id",
            vals=[
                {"name": "id", "default": 0},
                {"name": "service", "default": "unknown"},
                {"name": "enable", "type": "bool", "default": False},
                {"name": "state", "default": "unknown"},
            ],
            ensure_vals=[
                {"name": "running", "type": "bool", "default": False},
            ],
        )

        for uid, vals in self.ds["service"].items():
            self.ds["service"][uid]["running"] = vals["state"] == "RUNNING"

    # ---------------------------
    #   get_pool
    # ---------------------------
    def get_pool(self) -> None:
        """Get pools from TrueNAS."""
        self.ds["pool"] = parse_api(
            data=self.ds["pool"],
            source=self.api.query("pool"),
            key="guid",
            vals=[
                {"name": "guid", "default": 0},
                {"name": "id", "default": 0},
                {"name": "name", "default": "unknown"},
                {"name": "path", "default": "unknown"},
                {"name": "status", "default": "unknown"},
                {"name": "healthy", "type": "bool", "default": False},
                {"name": "is_decrypted", "type": "bool", "default": False},
                {
                    "name": "autotrim",
                    "source": "autotrim/parsed",
                    "type": "bool",
                    "default": False,
                },
                {
                    "name": "scan_function",
                    "source": "scan/function",
                    "default": "unknown",
                },
                {"name": "scrub_state", "source": "scan/state", "default": "unknown"},
                {
                    "name": "scrub_start",
                    "source": "scan/start_time/$date",
                    "default": 0,
                    "convert": "utc_from_timestamp",
                },
                {
                    "name": "scrub_end",
                    "source": "scan/end_time/$date",
                    "default": 0,
                    "convert": "utc_from_timestamp",
                },
                {
                    "name": "scrub_secs_left",
                    "source": "scan/total_secs_left",
                    "default": 0,
                },
            ],
            ensure_vals=[
                {"name": "available_gib", "default": 0.0},
                {"name": "total_gib", "default": 0.0},
                {"name": "usage", "default": 0.0},
            ],
        )

        self.ds["pool"] = parse_api(
            data=self.ds["pool"],
            source=self.api.query("boot/get_state"),
            key="name",
            vals=[
                {"name": "guid", "default": "boot-pool"},
                {"name": "id", "default": "boot-pool"},
                {"name": "name", "default": "unknown"},
                {"name": "path", "default": "unknown"},
                {"name": "status", "default": "unknown"},
                {"name": "healthy", "type": "bool", "default": False},
                {"name": "is_decrypted", "type": "bool", "default": False},
                {
                    "name": "autotrim",
                    "source": "autotrim/parsed",
                    "type": "bool",
                    "default": False,
                },
                {"name": "root_dataset"},
                {
                    "name": "root_dataset_available",
                    "source": "root_dataset/properties/available/parsed",
                    "default": 0,
                },
                {
                    "name": "root_dataset_used",
                    "source": "root_dataset/properties/used/parsed",
                    "default": 0,
                },
                {
                    "name": "scan_function",
                    "source": "scan/function",
                    "default": "unknown",
                },
                {"name": "scrub_state", "source": "scan/state", "default": "unknown"},
                {
                    "name": "scrub_start",
                    "source": "scan/start_time/$date",
                    "default": 0,
                    "convert": "utc_from_timestamp",
                },
                {
                    "name": "scrub_end",
                    "source": "scan/end_time/$date",
                    "default": 0,
                    "convert": "utc_from_timestamp",
                },
                {
                    "name": "scrub_secs_left",
                    "source": "scan/total_secs_left",
                    "default": 0,
                },
                {"name": "allocated", "default": 0},
                {"name": "free", "default": 0},
            ],
            ensure_vals=[
                {"name": "available_gib", "default": 0.0},
                {"name": "total_gib", "default": 0.0},
                {"name": "usage", "default": 0.0},
            ],
        )

        # Process pools
        tmp_dataset_available = {}
        tmp_dataset_total = {}
        for uid, vals in self.ds["dataset"].items():
            tmp_dataset_available[self.ds["dataset"][uid]["mountpoint"]] = vals[
                "available"
            ]

            tmp_dataset_total[self.ds["dataset"][uid]["mountpoint"]] = (
                vals["available"] + vals["used"]
            )

        for uid, vals in self.ds["pool"].items():
            if vals["path"] in tmp_dataset_available:
                self.ds["pool"][uid]["available_gib"] = tmp_dataset_available[
                    vals["path"]
                ]

            if vals["path"] in tmp_dataset_total:
                self.ds["pool"][uid]["total_gib"] = tmp_dataset_total[vals["path"]]

            if vals["name"] in ["boot-pool", "freenas-boot"]:
                if self._is_scale and self._version_major >= 23:
                    self.ds["pool"][uid]["available_gib"] = vals["free"]
                    self.ds["pool"][uid]["total_gib"] = vals["free"] + vals["allocated"]
                else:
                    self.ds["pool"][uid]["available_gib"] = vals[
                        "root_dataset_available"
                    ]
                    self.ds["pool"][uid]["total_gib"] = (
                        vals["root_dataset_available"] + vals["root_dataset_used"]
                    )

                self.ds["pool"][uid].pop("root_dataset")

            if self.ds["pool"][uid]["total_gib"] > 0:
                self.ds["pool"][uid]["usage"] = round(
                    (
                        self.ds["pool"][uid]["available_gib"]
                        / self.ds["pool"][uid]["total_gib"]
                    )
                    * 100
                )
            else:
                self.ds["pool"][uid]["usage"] = 0

    # ---------------------------
    #   get_dataset
    # ---------------------------
    def get_dataset(self) -> None:
        """Get datasets from TrueNAS."""
        self.ds["dataset"] = parse_api(
            data={},
            source=self.api.query("pool/dataset"),
            key="id",
            vals=[
                {"name": "id", "default": "unknown"},
                {"name": "type", "default": "unknown"},
                {"name": "name", "default": "unknown"},
                {"name": "pool", "default": "unknown"},
                {"name": "mountpoint", "default": "unknown"},
                {"name": "comments", "source": "comments/parsed", "default": ""},
                {
                    "name": "deduplication",
                    "source": "deduplication/parsed",
                    "type": "bool",
                    "default": False,
                },
                {
                    "name": "atime",
                    "source": "atime/parsed",
                    "type": "bool",
                    "default": False,
                },
                {
                    "name": "casesensitivity",
                    "source": "casesensitivity/parsed",
                    "default": "unknown",
                },
                {"name": "checksum", "source": "checksum/parsed", "default": "unknown"},
                {
                    "name": "exec",
                    "source": "exec/parsed",
                    "type": "bool",
                    "default": False,
                },
                {"name": "sync", "source": "sync/parsed", "default": "unknown"},
                {
                    "name": "compression",
                    "source": "compression/parsed",
                    "default": "unknown",
                },
                {
                    "name": "compressratio",
                    "source": "compressratio/parsed",
                    "default": "unknown",
                },
                {"name": "quota", "source": "quota/parsed", "default": "unknown"},
                {"name": "copies", "source": "copies/parsed", "default": 0},
                {
                    "name": "readonly",
                    "source": "readonly/parsed",
                    "type": "bool",
                    "default": False,
                },
                {"name": "recordsize", "source": "recordsize/parsed", "default": 0},
                {
                    "name": "encryption_algorithm",
                    "source": "encryption_algorithm/parsed",
                    "default": "unknown",
                },
                {"name": "used", "source": "used/parsed", "default": 0},
                {"name": "available", "source": "available/parsed", "default": 0},
            ],
            ensure_vals=[
                {"name": "used_gb", "default": 0},
            ],
        )

        for uid, vals in self.ds["dataset"].items():
            self.ds["dataset"][uid]["used_gb"] = vals["used"]

        if len(self.ds["dataset"]) == 0:
            return

        # entities_to_be_removed = []
        # if not self.datasets_hass_device_id:
        #     device_registry = dr.async_get(self.hass)
        #     for device in device_registry.devices.values():
        #         if (
        #             self.config_entry.entry_id in device.config_entries
        #             and device.name.endswith("Datasets")
        #         ):
        #             self.datasets_hass_device_id = device.id
        #             _LOGGER.debug(f"datasets device: {device.name}")
        #
        #     if not self.datasets_hass_device_id:
        #         return
        #
        # _LOGGER.debug(f"datasets_hass_device_id: {self.datasets_hass_device_id}")
        # entity_registry = er.async_get(self.hass)
        # entity_entries = async_entries_for_config_entry(
        #     entity_registry, self.config_entry.entry_id
        # )
        # for entity in entity_entries:
        #     if (
        #         entity.device_id == self.datasets_hass_device_id
        #         and entity.unique_id.removeprefix(f"{self.name.lower()}-dataset-")
        #         not in map(
        #             lambda x: str.replace(x, "/", "_"),
        #             map(str.lower, self.ds["dataset"].keys()),
        #         )
        #     ):
        #         _LOGGER.debug(f"dataset to be removed: {entity.unique_id}")
        #         entities_to_be_removed.append(entity.entity_id)
        #
        # for entity_id in entities_to_be_removed:
        #     entity_registry.async_remove(entity_id)

    # ---------------------------
    #   get_disk
    # ---------------------------
    def get_disk(self) -> None:
        """Get disks from TrueNAS."""
        self.ds["disk"] = parse_api(
            data=self.ds["disk"],
            source=self.api.query("disk"),
            key="identifier",
            vals=[
                {"name": "name", "default": "unknown"},
                {"name": "devname", "default": "unknown"},
                {"name": "serial", "default": "unknown"},
                {"name": "size", "default": "unknown"},
                {"name": "hddstandby", "default": "unknown"},
                {"name": "hddstandby_force", "type": "bool", "default": False},
                {"name": "advpowermgmt", "default": "unknown"},
                {"name": "acousticlevel", "default": "unknown"},
                {"name": "togglesmart", "type": "bool", "default": False},
                {"name": "model", "default": "unknown"},
                {"name": "rotationrate", "default": "unknown"},
                {"name": "type", "default": "unknown"},
                {"name": "zfs_guid", "default": "unknown"},
                {"name": "identifier", "default": "unknown"},
            ],
            ensure_vals=[
                {"name": "temperature", "default": 0},
            ],
        )

        # Get disk temperatures
        temps = self.api.query(
            "disk/temperatures",
            method="post",
            params={"names": []},
        )

        if temps:
            for uid, vals in self.ds["disk"].items():
                if vals["name"] in temps:  # looks for devname here
                    self.ds["disk"][uid]["temperature"] = temps[vals["name"]]
                    # return devname temp to uid disk
                    # I feel like this will break in the future when TrueNAS updates to a more sensible system. Currently their own long term stats are broken by the changing devnames.

    # ---------------------------
    #   get_jail
    # ---------------------------
    def get_jail(self) -> None:
        """Get jails from TrueNAS."""
        if self._is_scale:
            return

        self.ds["jail"] = parse_api(
            data=self.ds["jail"],
            source=self.api.query("jail"),
            key="id",
            vals=[
                {"name": "id", "default": "unknown"},
                {"name": "comment", "default": "unknown"},
                {"name": "host_hostname", "default": "unknown"},
                {"name": "jail_zfs_dataset", "default": "unknown"},
                {"name": "last_started", "default": "unknown"},
                {"name": "ip4_addr", "default": "unknown"},
                {"name": "ip6_addr", "default": "unknown"},
                {"name": "release", "default": "unknown"},
                {"name": "state", "type": "bool", "default": False},
                {"name": "type", "default": "unknown"},
                {"name": "plugin_name", "default": "unknown"},
            ],
        )

    # ---------------------------
    #   get_vm
    # ---------------------------
    def get_vm(self) -> None:
        """Get VMs from TrueNAS."""
        self.ds["vm"] = parse_api(
            data=self.ds["vm"],
            source=self.api.query("vm"),
            key="id",
            vals=[
                {"name": "id", "default": 0},
                {"name": "name", "default": "unknown"},
                {"name": "description", "default": "unknown"},
                {"name": "vcpus", "default": 0},
                {"name": "memory", "default": 0},
                {"name": "autostart", "type": "bool", "default": False},
                {"name": "cores", "default": 0},
                {"name": "threads", "default": 0},
                {"name": "state", "source": "status/state", "default": "unknown"},
            ],
            ensure_vals=[
                {"name": "running", "type": "bool", "default": False},
            ],
        )

        for uid, vals in self.ds["vm"].items():
            self.ds["vm"][uid]["running"] = vals["state"] == "RUNNING"

    # ---------------------------
    #   get_cloudsync
    # ---------------------------
    def get_cloudsync(self) -> None:
        """Get cloudsync from TrueNAS."""
        self.ds["cloudsync"] = parse_api(
            data=self.ds["cloudsync"],
            source=self.api.query("cloudsync"),
            key="id",
            vals=[
                {"name": "id", "default": "unknown"},
                {"name": "description", "default": "unknown"},
                {"name": "direction", "default": "unknown"},
                {"name": "path", "default": "unknown"},
                {"name": "enabled", "type": "bool", "default": False},
                {"name": "transfer_mode", "default": "unknown"},
                {"name": "snapshot", "type": "bool", "default": False},
                {"name": "state", "source": "job/state", "default": "unknown"},
                {
                    "name": "time_started",
                    "source": "job/time_started/$date",
                    "default": 0,
                    "convert": "utc_from_timestamp",
                },
                {
                    "name": "time_finished",
                    "source": "job/time_finished/$date",
                    "default": 0,
                    "convert": "utc_from_timestamp",
                },
                {"name": "job_percent", "source": "job/progress/percent", "default": 0},
                {
                    "name": "job_description",
                    "source": "job/progress/description",
                    "default": "unknown",
                },
            ],
        )

    # ---------------------------
    #   get_replication
    # ---------------------------
    def get_replication(self) -> None:
        """Get replication from TrueNAS."""
        self.ds["replication"] = parse_api(
            data=self.ds["replication"],
            source=self.api.query("replication"),
            key="id",
            vals=[
                {"name": "id", "default": 0},
                {"name": "name", "default": "unknown"},
                {"name": "source_datasets", "default": "unknown"},
                {"name": "target_dataset", "default": "unknown"},
                {"name": "recursive", "type": "bool", "default": False},
                {"name": "enabled", "type": "bool", "default": False},
                {"name": "direction", "default": "unknown"},
                {"name": "transport", "default": "unknown"},
                {"name": "auto", "type": "bool", "default": False},
                {"name": "retention_policy", "default": "unknown"},
                {"name": "state", "source": "job/state", "default": "unknown"},
                {
                    "name": "time_started",
                    "source": "job/time_started/$date",
                    "default": 0,
                    "convert": "utc_from_timestamp",
                },
                {
                    "name": "time_finished",
                    "source": "job/time_finished/$date",
                    "default": 0,
                    "convert": "utc_from_timestamp",
                },
                {"name": "job_percent", "source": "job/progress/percent", "default": 0},
                {
                    "name": "job_description",
                    "source": "job/progress/description",
                    "default": "unknown",
                },
            ],
        )

    # ---------------------------
    #   get_snapshottask
    # ---------------------------
    def get_snapshottask(self) -> None:
        """Get replication from TrueNAS."""
        self.ds["snapshottask"] = parse_api(
            data=self.ds["snapshottask"],
            source=self.api.query("pool/snapshottask"),
            key="id",
            vals=[
                {"name": "id", "default": 0},
                {"name": "dataset", "default": "unknown"},
                {"name": "recursive", "type": "bool", "default": False},
                {"name": "lifetime_value", "default": 0},
                {"name": "lifetime_unit", "default": "unknown"},
                {"name": "enabled", "type": "bool", "default": False},
                {"name": "naming_schema", "default": "unknown"},
                {"name": "allow_empty", "type": "bool", "default": False},
                {"name": "vmware_sync", "type": "bool", "default": False},
                {"name": "state", "source": "state/state", "default": "unknown"},
                {
                    "name": "datetime",
                    "source": "state/datetime/$date",
                    "default": 0,
                    "convert": "utc_from_timestamp",
                },
            ],
        )

    # ---------------------------
    #   get_app
    # ---------------------------
    def get_app(self) -> None:
        """Get Apps from TrueNAS."""
        if not self._is_scale:
            return

        if self._version_major <= 23 or (
            self._version_major == 24 and self._version_minor < 10
        ):
            self.ds["app"] = parse_api(
                data=self.ds["app"],
                source=self.api.query("chart/release"),
                key="id",
                vals=[
                    {"name": "id", "default": 0},
                    {"name": "name", "default": "unknown"},
                    {"name": "human_version", "type": "bool", "default": False},
                    {"name": "update_available", "type": "bool", "default": False},
                    {
                        "name": "image_updates_available",
                        "source": "container_images_update_available",
                        "default": "unknown",
                    },
                    {"name": "portal", "source": "portals/open", "default": "unknown"},
                    {"name": "status", "default": "unknown"},
                ],
                ensure_vals=[
                    {"name": "running", "type": "bool", "default": False},
                ],
            )

            for uid, vals in self.ds["app"].items():
                self.ds["app"][uid]["running"] = vals["status"] == "ACTIVE"

        else:
            self.ds["app"] = parse_api(
                data=self.ds["app"],
                source=self.api.query("app"),
                key="id",
                vals=[
                    {"name": "id", "default": 0},
                    {"name": "name", "default": "unknown"},
                    {"name": "human_version", "default": "unknown"},
                    {
                        "name": "update_available",
                        "source": "upgrade_available",
                        "type": "bool",
                        "default": False,
                    },
                    {
                        "name": "image_updates_available",
                        "type": "bool",
                        "default": False,
                    },
                    {
                        "name": "portal",
                        "source": "portals/Web UI",
                        "default": "unknown",
                    },
                    {"name": "state", "default": "unknown"},
                ],
                ensure_vals=[
                    {"name": "running", "type": "bool", "default": False},
                ],
            )

            for uid, vals in self.ds["app"].items():
                self.ds["app"][uid]["running"] = vals["state"] == "RUNNING"
