import json
from pathlib import Path

from de.p1st.monitor import datetime_util

from de.p1st.monitor.exec_capture import execute_capture
from de.p1st.monitor.logger import Logger
from de.p1st.monitor.logger_ex import LoggerArgEx, LoggerReadEx
from de.p1st.monitor.warn_data import WarnData


class BlkidException(Exception):
    pass


class DriveLogger(Logger):
    def __init__(self, uuid: str = None,
                 device: Path = None,
                 warn_if_above: int = None,
                 warn_threshold: int = 1,
                 warn_data_range: int = 1,
                 ):

        critical_if_above = warn_if_above + 10
        super().__init__(warn_threshold,
                         warn_data_range,
                         warn_if_above,
                         critical_if_above
                         )

        if uuid is None and device is None:
            raise LoggerArgEx('uuid or device required')

        if uuid is None:
            try:
                self.uuid = self.get_partition_uuid(device)
            except BlkidException as e:
                raise LoggerArgEx(getattr(e, 'message', e))
        else:
            self.uuid = uuid

        if device is None:
            self.device = self.get_partition_path(uuid)
        else:
            self.device = device

        self.warn_if_above = warn_if_above

    def get_warn_data(self, data: list[any]) -> WarnData:
        temp = data[1]
        message = f'Temperature of {self.uuid} ist at {temp}'
        return WarnData(data[0], temp, message)

    def read_data(self) -> list[any]:
        return [
            datetime_util.now(),
            self.get_temp_from_device(self.device),
        ]

    def data_schema(self) -> list[str]:
        return ['datetime#Date', 'int#Temperature']

    def get_log_file(self) -> Path:
        # self.device might change overtime.
        # Thus, we use self.uuid to identify a partition.
        return self.get_log_dir() / f'drive_{self.uuid}.csv'

    #
    # HELPERS
    #

    @classmethod
    def get_partition_path(cls, uuid: str) -> Path:
        """
        :return: Partition path, e.g. /dev/sda1
        """
        return Path(f'/dev/disk/by-uuid/{uuid}').resolve()

    @classmethod
    def get_partition_uuid(cls, device: Path) -> str:
        """
        :param device: E.g. /dev/sda1
        :return: UUID of e.g. partition /dev/sda1
        :raise BlkidException: If UUID could not be determined.
        """
        returncode, stdout, stderr = execute_capture(['blkid', '-s', 'UUID', '-o', 'value', f'{device}'])

        if returncode != 0:
            raise BlkidException(f'blkid failed with returncode {returncode}\nstdout: {stdout}\nstderr: {stderr}')

        return stdout.strip()

    @classmethod
    def get_temp_from_device(cls, device: Path) -> int:
        """
        :param device: For example `/dev/sda` or `/dev/disk/by-uuid/<uuid>`
        :return: Temperature in celsius
        """
        returncode, stdout, stderr = execute_capture(['smartctl', '-j', '-a', f'{device}'])

        if returncode != 0:
            raise LoggerReadEx(f'smartctl failed with returncode {returncode}\nstdout: {stdout}\nstderr: {stderr}')
        j = json.loads(stdout)

        return j['temperature']['current']
