"""SNMP handlers for Cisco devices."""

from typing import Dict, List, Optional

from easysnmp import Session
from easysnmp.exceptions import EasySNMPError

from inventory_monitor.helper import stripper
from inventory_monitor.logger import logger

from .base import ISnmp
from .constants import CISCO_ENTITY_FIELD, OID
from .errors import sanitize_snmp_error


class SnmpCisco(ISnmp):
    """SNMP handler for Cisco devices."""

    def get_snmp_connection(
        self, device_name: str, device_ip: str
    ) -> Optional[Session]:
        return self.snmp_session(device_name, device_ip, OID["CISCO_ENTITY_MIB"])

    def get_snmp_inventory(
        self, snmp_connection: Session
    ) -> Optional[Dict[str, Dict[str, str]]]:
        port_classes = {
            0: "-",
            1: "other",
            2: "unknown",
            3: "chassis",
            4: "backplane",
            5: "container",
            6: "powerSupply",
            7: "fan",
            8: "sensor",
            9: "module",
            10: "port",
            11: "stack",
            12: "cpu",
        }

        inventory = {}

        try:
            result_ids = snmp_connection.bulkwalk(OID["CISCO_ENTITY_MIB"])
        except (SystemError, EasySNMPError) as e:
            logger.error(
                f"Unable to do snmpwalk: {snmp_connection.hostname} version: {snmp_connection.version} - {sanitize_snmp_error(e)}"
            )
            return None

        for res_id in result_ids:
            element_id = res_id.oid.replace(f"{OID['CISCO_ENTITY_MIB']}.", "")

            if element_id == CISCO_ENTITY_FIELD["IS_FRU"]:
                value = "true" if (res_id.value or "").strip() == "1" else "false"
            elif element_id == CISCO_ENTITY_FIELD["CLASS"]:
                try:
                    class_value = int(res_id.value)
                except (ValueError, TypeError):
                    class_value = 0
                value = port_classes.get(class_value, port_classes[0])
            else:
                value = res_id.value

            self._add_to_inventory(inventory, res_id.oid_index, element_id, value)

        return inventory

    def get_human_readable_inventory(
        self, inventory: Dict[str, Dict[str, str]]
    ) -> List[Dict[str, str]]:
        ef = CISCO_ENTITY_FIELD  # Entity Field constants
        array_of_inventory_items = []
        for _, values in inventory.items():
            if values.get(ef["SERIAL_NUM"]) and ISnmp.is_valid_serial(
                values.get(ef["SERIAL_NUM"])
            ):
                array_of_inventory_items.append(
                    {
                        "entPhysicalDescr": stripper(values.get(ef["DESCR"], "")),
                        "entPhysicalVendorType": stripper(
                            values.get(ef["VENDOR_TYPE"], "")
                        ),
                        "entPhysicalContainedIn": stripper(
                            values.get(ef["CONTAINED_IN"], "")
                        ),
                        "entPhysicalClass": stripper(values.get(ef["CLASS"], "")),
                        "entPhysicalParentRelPos": stripper(
                            values.get(ef["PARENT_REL_POS"], "")
                        ),
                        "entPhysicalName": stripper(values.get(ef["NAME"], "")),
                        "entPhysicalHardwareRev": stripper(values.get(ef["HW_REV"], "")),
                        "entPhysicalFirmwareRev": stripper(values.get(ef["FW_REV"], "")),
                        "entPhysicalSoftwareRev": stripper(values.get(ef["SW_REV"], "")),
                        "entPhysicalSerialNum": stripper(
                            values.get(ef["SERIAL_NUM"], "")
                        ),
                        "entPhysicalMfgName": stripper(
                            values.get(ef["MFG_NAME"], "")
                        ),
                        "entPhysicalModelName": stripper(
                            values.get(ef["MODEL_NAME"], "")
                        ),
                        "entPhysicalAlias": stripper(values.get(ef["ALIAS"], "")),
                        "entPhysicalAssetID": stripper(values.get(ef["ASSET_ID"], "")),
                        "entPhysicalIsFRU": stripper(values.get(ef["IS_FRU"], "")),
                    }
                )
        if not array_of_inventory_items:
            logger.debug(
                f"Cisco: No items with valid serial numbers found "
                f"(raw inventory had {len(inventory)} entries)"
            )
        return array_of_inventory_items


class SnmpCiscoLegacy(ISnmp):
    """SNMP handler for legacy Cisco devices."""

    def get_snmp_connection(
        self, device_name: str, device_ip: str
    ) -> Optional[Session]:
        return self.snmp_session(device_name, device_ip, OID["CISCO_LEGACY_BASE"])

    def get_snmp_inventory(
        self, snmp_connection: Session
    ) -> Optional[Dict[str, Dict[str, str]]]:
        inventory = {}

        try:
            result_ids = snmp_connection.bulkwalk(OID["CISCO_LEGACY_CHASSIS"])
            for res_id in result_ids:
                element_id = res_id.oid.replace(f"{OID['CISCO_LEGACY_CHASSIS']}.", "")
                self._add_to_inventory(inventory, res_id.oid_index, element_id, res_id.value)

            result_ids = snmp_connection.bulkwalk(OID["CISCO_LEGACY_COMPONENT"])
            for res_id in result_ids:
                element_id = res_id.oid.replace(f"{OID['CISCO_LEGACY_COMPONENT']}.", "")
                self._add_to_inventory(inventory, f"cont-{res_id.oid_index}", element_id, res_id.value)

            return inventory
        except (SystemError, EasySNMPError) as e:
            logger.error(f"Error retrieving Cisco Legacy inventory: {sanitize_snmp_error(e)}")
            return None

    def get_human_readable_inventory(
        self, inventory: Dict[str, Dict[str, str]]
    ) -> List[Dict[str, str]]:
        array_of_inventory_items = []

        for element_id, values in inventory.items():
            if element_id == "0":
                serial_num = values.get("0", "").strip()
                if ISnmp.is_valid_serial(serial_num):
                    array_of_inventory_items.append(
                        {
                            "entPhysicalSerialNum": serial_num,
                            "entPhysicalModelName": "",
                            "entPhysicalName": "Chassis",
                            "entPhysicalClass": "chassis",
                            "entPhysicalDescr": "",
                        }
                    )

            elif element_id.startswith("cont-"):
                serial_num = values.get("4", "").strip()
                if ISnmp.is_valid_serial(serial_num):
                    descr = values.get("3", "").strip()
                    slot = values.get("7", "").strip()
                    array_of_inventory_items.append(
                        {
                            "entPhysicalSerialNum": serial_num,
                            "entPhysicalModelName": "",
                            "entPhysicalName": f"Slot {slot}",
                            "entPhysicalClass": "module",
                            "entPhysicalDescr": descr,
                        }
                    )

        if not array_of_inventory_items:
            logger.debug(
                f"Cisco Legacy: No items with valid serial numbers found "
                f"(raw inventory had {len(inventory)} entries)"
            )
        return array_of_inventory_items
