""" 
This module handles the interaction with libstoragemgmt for local disk 
devices. Interaction may fail with LSM for a number of issues, but the
intent here is to make this a soft fail, since LSM related data is not
a critical component of ceph-volume.
"""
import logging

try:
    from lsm import LocalDisk, LsmError
    from lsm import Disk as lsm_Disk
except ImportError:
    lsm_available = False
    transport_map = {}
    health_map = {}
    lsm_Disk = None
else:
    lsm_available = True
    transport_map = {
            lsm_Disk.LINK_TYPE_UNKNOWN: "Unavailable",
            lsm_Disk.LINK_TYPE_FC: "Fibre Channel",
            lsm_Disk.LINK_TYPE_SSA: "IBM SSA",
            lsm_Disk.LINK_TYPE_SBP: "Serial Bus",
            lsm_Disk.LINK_TYPE_SRP: "SCSI RDMA",
            lsm_Disk.LINK_TYPE_ISCSI: "iSCSI",
            lsm_Disk.LINK_TYPE_SAS: "SAS",
            lsm_Disk.LINK_TYPE_ADT: "ADT (Tape)",
            lsm_Disk.LINK_TYPE_ATA: "ATA/SATA",
            lsm_Disk.LINK_TYPE_USB: "USB",
            lsm_Disk.LINK_TYPE_SOP: "SCSI over PCI-E",
            lsm_Disk.LINK_TYPE_PCIE: "PCI-E",
    }
    health_map = {
        lsm_Disk.HEALTH_STATUS_UNKNOWN: "Unknown",
        lsm_Disk.HEALTH_STATUS_FAIL: "Fail",
        lsm_Disk.HEALTH_STATUS_WARN: "Warn",
        lsm_Disk.HEALTH_STATUS_GOOD: "Good",
    }

logger = logging.getLogger(__name__)


class LSMDisk:
    def __init__(self, dev_path):
        self.dev_path = dev_path
        self.error_list = set()

        if lsm_available:
            self.lsm_available = True
            self.disk = LocalDisk()
        else:
            self.lsm_available = False
            self.error_list.add("libstoragemgmt (lsm module) is unavailable")
            logger.info("LSM information is unavailable: libstoragemgmt is not installed")
            self.disk = None

        self.led_bits = None

    @property
    def errors(self):
        """show any errors that the LSM interaction has encountered (str)"""
        return ", ".join(self.error_list)

    def _query_lsm(self, func, path):
        """Common method used to call the LSM functions, returning the function's result or None"""

        # if disk is None, lsm is unavailable so all calls should return None
        if self.disk is None:
            return None
        
        method = getattr(self.disk, func)
        try:
            output = method(path)
        except LsmError as err:
            logger.error("LSM Error: {}".format(err._msg))
            self.error_list.add(err._msg)
            return None
        else:
            return output

    @property
    def led_status(self):
        """Fetch LED status, store in the LSMDisk object and return current status (int)"""
        if self.led_bits is None:
            self.led_bits = self._query_lsm('led_status_get', self.dev_path) or 1
            return self.led_bits
        else:
            return self.led_bits

    @property
    def led_ident_state(self):
        """Query a disks IDENT LED state to discover when it is On, Off or Unknown (str)"""
        if self.led_status == 1:
            return "Unsupported"
        if self.led_status & lsm_Disk.LED_STATUS_IDENT_ON == lsm_Disk.LED_STATUS_IDENT_ON:
            return "On"
        elif self.led_status & lsm_Disk.LED_STATUS_IDENT_OFF == lsm_Disk.LED_STATUS_IDENT_OFF:
            return "Off"
        elif self.led_status & lsm_Disk.LED_STATUS_IDENT_UNKNOWN == lsm_Disk.LED_STATUS_IDENT_UNKNOWN:
            return "Unknown"
        
        return "Unsupported"

    @property
    def led_fault_state(self):
        """Query a disks FAULT LED state to discover when it is On, Off or Unknown (str)"""
        if self.led_status == 1:
            return "Unsupported"
        if self.led_status & lsm_Disk.LED_STATUS_FAULT_ON == lsm_Disk.LED_STATUS_FAULT_ON:
            return "On"
        elif self.led_status & lsm_Disk.LED_STATUS_FAULT_OFF == lsm_Disk.LED_STATUS_FAULT_OFF:
            return "Off"
        elif self.led_status & lsm_Disk.LED_STATUS_FAULT_UNKNOWN == lsm_Disk.LED_STATUS_FAULT_UNKNOWN:
            return "Unknown"
        
        return "Unsupported"

    @property
    def led_ident_support(self):
        """Query the LED state to determine IDENT support: Unknown, Supported, Unsupported (str)"""
        if self.led_status == 1:
            return "Unknown"

        ident_states = (
            lsm_Disk.LED_STATUS_IDENT_ON + 
            lsm_Disk.LED_STATUS_IDENT_OFF + 
            lsm_Disk.LED_STATUS_IDENT_UNKNOWN
        )

        if (self.led_status & ident_states) == 0:
            return "Unsupported"
        
        return "Supported"

    @property
    def led_fault_support(self):
        """Query the LED state to determine FAULT support: Unknown, Supported, Unsupported (str)"""
        if self.led_status == 1:
            return "Unknown"

        fail_states = (
            lsm_Disk.LED_STATUS_FAULT_ON + 
            lsm_Disk.LED_STATUS_FAULT_OFF + 
            lsm_Disk.LED_STATUS_FAULT_UNKNOWN
        )

        if self.led_status & fail_states == 0:
            return "Unsupported"

        return "Supported"

    @property
    def health(self):
        """Determine the health of the disk from LSM : Unknown, Fail, Warn or Good (str)"""
        _health_int = self._query_lsm('health_status_get', self.dev_path)
        return health_map.get(_health_int, "Unknown")

    @property
    def transport(self):
        """Translate a disks link type to a human readable format (str)"""
        _link_type = self._query_lsm('link_type_get', self.dev_path)
        return transport_map.get(_link_type, "Unknown")


    @property
    def media_type(self):
        """Use the rpm value to determine the type of disk media: Flash or HDD (str)"""
        _rpm = self._query_lsm('rpm_get', self.dev_path)
        if _rpm is not None:
            if _rpm == 0:
                return "Flash"
            elif _rpm > 1:
                return "HDD"

        return "Unknown"

    def json_report(self):
        """Return the LSM related metadata for the current local disk (dict)"""
        if self.lsm_available:
            return {
                "serialNum": self._query_lsm('serial_num_get', self.dev_path) or "Unknown",
                "transport": self.transport,
                "mediaType": self.media_type,
                "rpm": self._query_lsm('rpm_get', self.dev_path) or "Unknown",
                "linkSpeed": self._query_lsm('link_speed_get', self.dev_path) or "Unknown",
                "health": self.health,
                "ledSupport": {
                    "IDENTsupport": self.led_ident_support,
                    "IDENTstatus": self.led_ident_state,
                    "FAILsupport": self.led_fault_support,
                    "FAILstatus": self.led_fault_state,
                },
                "errors": list(self.error_list)
            }
        else:
            return {}
