import re
import json
import logging
import numpy as np

from datetime import datetime
from dmidecode import DMIParse

from utils.constants import CHASSIS_DH, DATASTORAGEINTERFACE


logger = logging.getLogger('django')


def get_inxi_key(inxi, component):
    for n in inxi:
        for k, v in n.items():
            if component in k:
                return v


def get_inxi(n, name):
    for k, v in n.items():
        if f"#{name}" in k:
            return v

    return ""


class ParseSnapshot:
    def __init__(self, snapshot, default="n/a"):
        self.default = default
        self.dmidecode_raw = snapshot.get("data", {}).get("dmidecode", "{}")
        self.smart_raw = snapshot.get("data", {}).get("smartctl", [])
        self.inxi_raw = snapshot.get("data", {}).get("inxi", "") or ""
        for ev in snapshot.get("evidence", []):
            if "dmidecode" == ev.get("operation"):
                self.dmidecode_raw = ev["output"]
            if "inxi" == ev.get("operation"):
                self.inxi_raw = ev["output"]
            if "smartctl" == ev.get("operation"):
                self.smart_raw = ev["output"]
        data = snapshot
        if snapshot.get("credentialSubject"):
            data = snapshot["credentialSubject"]

        self.device = {"actions": []}
        self.components = []

        self.dmi = DMIParse(self.dmidecode_raw)
        self.smart = self.loads(self.smart_raw)
        self.inxi = self.loads(self.inxi_raw)

        self.set_computer()
        self.set_components()
        self.snapshot_json = {
            "type": "Snapshot",
            "device": self.device,
            "software": data["software"],
            "components": self.components,
            "uuid": data['uuid'],
            "endTime": data["timestamp"],
            "elapsed": 1,
        }

    def set_computer(self):
        machine = get_inxi_key(self.inxi, 'Machine') or []
        for m in machine:
            system = get_inxi(m, "System")
            if system:
                self.device['manufacturer'] = system
                self.device['model'] = get_inxi(m, "product")
                self.device['serialNumber'] = get_inxi(m, "serial")
                self.device['type'] = get_inxi(m, "Type")
                self.device['chassis'] = self.device['type']
                self.device['version'] = get_inxi(m, "v")
            else:
                self.device['system_uuid'] = get_inxi(m, "uuid")
                self.device['sku'] = get_inxi(m, "part-nu")

    def set_components(self):
        self.get_mother_board()
        self.get_cpu()
        self.get_ram()
        self.get_graphic()
        self.get_display()
        self.get_networks()
        self.get_sound_card()
        self.get_data_storage()
        self.get_battery()

    def get_mother_board(self):
        machine = get_inxi_key(self.inxi, 'Machine') or []
        mb = {"type": "Motherboard",}
        for m in machine:
            bios_date = get_inxi(m, "date")
            if not bios_date:
                continue
            mb["manufacturer"] = get_inxi(m, "Mobo")
            mb["model"] = get_inxi(m, "model")
            mb["serialNumber"] = get_inxi(m, "serial")
            mb["version"] = get_inxi(m, "v")
            mb["biosDate"] = bios_date
            mb["biosVersion"] = self.get_bios_version()
            mb["firewire"]: self.get_firmware_num()
            mb["pcmcia"]: self.get_pcmcia_num()
            mb["serial"]: self.get_serial_num()
            mb["usb"]: self.get_usb_num()

            self.get_ram_slots(mb)

        self.components.append(mb)

    def get_ram_slots(self, mb):
        memory = get_inxi_key(self.inxi, 'Memory') or []
        for m in memory:
            slots = get_inxi(m, "slots")
            if not slots:
                continue
            mb["slots"] = slots
            mb["ramSlots"] = get_inxi(m, "modules")
            mb["ramMaxSize"] = get_inxi(m, "capacity")


    def get_cpu(self):
        cpu = get_inxi_key(self.inxi, 'CPU') or []
        cp = {"type": "Processor"}
        vulnerabilities = []
        for c in cpu:
            base = get_inxi(c, "model")
            if base:
                cp["model"] = get_inxi(c, "model")
                cp["arch"] = get_inxi(c, "arch")
                cp["bits"] = get_inxi(c, "bits")
                cp["gen"] = get_inxi(c, "gen")
                cp["family"] = get_inxi(c, "family")
                cp["date"] = get_inxi(c, "built")
                continue
            des = get_inxi(c, "L1")
            if des:
                cp["L1"] = des
                cp["L2"] = get_inxi(c, "L2")
                cp["L3"] = get_inxi(c, "L3")
                cp["cpus"] = get_inxi(c, "cpus")
                cp["cores"] = get_inxi(c, "cores")
                cp["threads"] = get_inxi(c, "threads")
                continue
            bogo = get_inxi(c, "bogomips")
            if bogo:
                cp["bogomips"] = bogo
                cp["base/boost"] = get_inxi(c, "base/boost")
                cp["min/max"] = get_inxi(c, "min/max")
                cp["ext-clock"] = get_inxi(c, "ext-clock")
                cp["volts"] = get_inxi(c, "volts")
                continue
            ctype = get_inxi(c, "Type")
            if ctype:
                v = {"Type": ctype}
                status = get_inxi(c, "status")
                if status:
                    v["status"] = status
                mitigation = get_inxi(c, "mitigation")
                if mitigation:
                    v["mitigation"] = mitigation
                vulnerabilities.append(v)

        self.components.append(cp)


    def get_ram(self):
        memory = get_inxi_key(self.inxi, 'Memory') or []
        mem = {"type": "RamModule"}

        for m in memory:
            base = get_inxi(m, "System RAM")
            if base:
                mem["size"] = get_inxi(m, "total")
            slot = get_inxi(m, "manufacturer")
            if slot:
                mem["manufacturer"] = slot
                mem["model"] = get_inxi(m, "part-no")
                mem["serialNumber"] = get_inxi(m, "serial")
                mem["speed"] = get_inxi(m, "speed")
                mem["bits"] = get_inxi(m, "data")
                mem["interface"] = get_inxi(m, "type")
            module = get_inxi(m, "modules")
            if module:
                mem["modules"] = module

        self.components.append(mem)

    def get_graphic(self):
        graphics = get_inxi_key(self.inxi, 'Graphics') or []

        for c in graphics:
            if not get_inxi(c, "Device") or not get_inxi(c, "vendor"):
                continue

            self.components.append(
                {
                    "type": "GraphicCard",
                    "memory": self.get_memory_video(c),
                    "manufacturer": get_inxi(c, "vendor"),
                    "model": get_inxi(c, "Device"),
                    "arch": get_inxi(c, "arch"),
                    "serialNumber": get_inxi(c, "serial"),
                    "integrated": True if get_inxi(c, "port") else False
                }
            )

    def get_battery(self):
        bats = get_inxi_key(self.inxi, 'Battery') or []
        for b in bats:
            self.components.append(
                {
                    "type": "Battery",
                    "model": get_inxi(b, "model"),
                    "serialNumber": get_inxi(b, "serial"),
                    "condition": get_inxi(b, "condition"),
                    "cycles": get_inxi(b, "cycles"),
                    "volts": get_inxi(b, "volts")
                }
            )

    def get_memory_video(self, c):
        memory = get_inxi_key(self.inxi, 'Memory') or []

        for m in memory:
            igpu = get_inxi(m, "igpu")
            agpu = get_inxi(m, "agpu")
            ngpu = get_inxi(m, "ngpu")
            gpu = get_inxi(m, "gpu")
            if igpu or agpu or gpu or ngpu:
                return igpu or agpu or gpu or ngpu

        return self.default

    def get_data_storage(self):
        hdds= get_inxi_key(self.inxi, 'Drives') or []
        for d in hdds:
            usb = get_inxi(d, "type")
            if usb == "USB":
                continue

            serial = get_inxi(d, "serial")
            if serial:
                hd = {
                    "type": "Storage",
                    "manufacturer": get_inxi(d, "vendor"),
                    "model": get_inxi(d, "model"),
                    "serialNumber": get_inxi(d, "serial"),
                    "size": get_inxi(d, "size"),
                    "speed": get_inxi(d, "speed"),
                    "interface": get_inxi(d, "tech"),
                    "firmware": get_inxi(d, "fw-rev")
                }
                rpm = get_inxi(d, "rpm")
                if rpm:
                    hd["rpm"] = rpm

                family = get_inxi(d, "family")
                if family:
                    hd["family"] = family

                sata = get_inxi(d, "sata")
                if sata:
                    hd["sata"] = sata

                continue


            cycles = get_inxi(d, "cycles")
            if cycles:
                hd['cycles'] = cycles
                hd["health"] = get_inxi(d, "health")
                hd["time of used"] = get_inxi(d, "on")
                hd["read used"] = get_inxi(d, "read-units")
                hd["written used"] = get_inxi(d, "written-units")

                self.components.append(hd)
                continue

            hd = {}

    def sanitize(self, action):
        return []

    def get_networks(self):
        nets = get_inxi_key(self.inxi, "Network") or []
        networks = [(nets[i], nets[i + 1]) for i in range(0, len(nets) - 1, 2)]

        for n, iface in networks:
            model = get_inxi(n, "Device")
            if not model:
                continue

            interface = ''
            for k in n.keys():
                if "port" in k:
                    interface = "Integrated"
                if "pcie" in k:
                    interface = "PciExpress"
                if get_inxi(n, "type") == "USB":
                    interface = "USB"

            self.components.append(
                {
                    "type": "NetworkAdapter",
                    "model": model,
                    "manufacturer": get_inxi(n, 'vendor'),
                    "serialNumber": get_inxi(iface, 'mac'),
                    "speed": get_inxi(n, "speed"),
                    "interface": interface,
                }
            )

    def get_sound_card(self):
        audio = get_inxi_key(self.inxi, "Audio") or []

        for c in audio:
            model = get_inxi(c, "Device")
            if not model:
                continue

            self.components.append(
                {
                    "type": "SoundCard",
                    "model": model,
                    "manufacturer": get_inxi(c, 'vendor'),
                    "serialNumber": get_inxi(c, 'serial'),
                }
            )

    def get_display(self):
        graphics = get_inxi_key(self.inxi, "Graphics") or []
        for c in graphics:
            if not get_inxi(c, "Monitor"):
                continue

            self.components.append(
                {
                    "type": "Display",
                    "model": get_inxi(c, "model"),
                    "manufacturer": get_inxi(c, "vendor"),
                    "serialNumber": get_inxi(c, "serial"),
                    'size': get_inxi(c, "size"),
                    'diagonal': get_inxi(c, "diag"),
                    'resolution': get_inxi(c, "res"),
                    "date": get_inxi(c, "built"),
                    'ratio': get_inxi(c, "ratio"),
                }
            )

    def get_usb_num(self):
        return len(
            [
                u
                for u in self.dmi.get("Port Connector")
                if "USB" in u.get("Port Type", "").upper()
            ]
        )

    def get_serial_num(self):
        return len(
            [
                u
                for u in self.dmi.get("Port Connector")
                if "SERIAL" in u.get("Port Type", "").upper()
            ]
        )

    def get_firmware_num(self):
        return len(
            [
                u
                for u in self.dmi.get("Port Connector")
                if "FIRMWARE" in u.get("Port Type", "").upper()
            ]
        )

    def get_pcmcia_num(self):
        return len(
            [
                u
                for u in self.dmi.get("Port Connector")
                if "PCMCIA" in u.get("Port Type", "").upper()
            ]
        )

    def get_bios_version(self):
        return self.dmi.get("BIOS")[0].get("BIOS Revision", '1')

    def loads(self, x):
        if isinstance(x, str):
            try:
                return json.loads(x)
            except Exception as ss:
                logger.warning("%s", ss)
                return {}
        return x

    def errors(self, txt=None):
        if not txt:
            return self._errors

        logger.error(txt)
        self._errors.append("%s", txt)