from __future__ import annotations

import struct
from collections.abc import Iterable

import win32wnet

# Constants generated by h2py from nb30.h
NCBNAMSZ = 16
MAX_LANA = 254
NAME_FLAGS_MASK = 0x87
GROUP_NAME = 0x80
UNIQUE_NAME = 0x00
REGISTERING = 0x00
REGISTERED = 0x04
DEREGISTERED = 0x05
DUPLICATE = 0x06
DUPLICATE_DEREG = 0x07
LISTEN_OUTSTANDING = 0x01
CALL_PENDING = 0x02
SESSION_ESTABLISHED = 0x03
HANGUP_PENDING = 0x04
HANGUP_COMPLETE = 0x05
SESSION_ABORTED = 0x06
ALL_TRANSPORTS = "M\0\0\0"
MS_NBF = "MNBF"
NCBCALL = 0x10
NCBLISTEN = 0x11
NCBHANGUP = 0x12
NCBSEND = 0x14
NCBRECV = 0x15
NCBRECVANY = 0x16
NCBCHAINSEND = 0x17
NCBDGSEND = 0x20
NCBDGRECV = 0x21
NCBDGSENDBC = 0x22
NCBDGRECVBC = 0x23
NCBADDNAME = 0x30
NCBDELNAME = 0x31
NCBRESET = 0x32
NCBASTAT = 0x33
NCBSSTAT = 0x34
NCBCANCEL = 0x35
NCBADDGRNAME = 0x36
NCBENUM = 0x37
NCBUNLINK = 0x70
NCBSENDNA = 0x71
NCBCHAINSENDNA = 0x72
NCBLANSTALERT = 0x73
NCBACTION = 0x77
NCBFINDNAME = 0x78
NCBTRACE = 0x79
ASYNCH = 0x80
NRC_GOODRET = 0x00
NRC_BUFLEN = 0x01
NRC_ILLCMD = 0x03
NRC_CMDTMO = 0x05
NRC_INCOMP = 0x06
NRC_BADDR = 0x07
NRC_SNUMOUT = 0x08
NRC_NORES = 0x09
NRC_SCLOSED = 0x0A
NRC_CMDCAN = 0x0B
NRC_DUPNAME = 0x0D
NRC_NAMTFUL = 0x0E
NRC_ACTSES = 0x0F
NRC_LOCTFUL = 0x11
NRC_REMTFUL = 0x12
NRC_ILLNN = 0x13
NRC_NOCALL = 0x14
NRC_NOWILD = 0x15
NRC_INUSE = 0x16
NRC_NAMERR = 0x17
NRC_SABORT = 0x18
NRC_NAMCONF = 0x19
NRC_IFBUSY = 0x21
NRC_TOOMANY = 0x22
NRC_BRIDGE = 0x23
NRC_CANOCCR = 0x24
NRC_CANCEL = 0x26
NRC_DUPENV = 0x30
NRC_ENVNOTDEF = 0x34
NRC_OSRESNOTAV = 0x35
NRC_MAXAPPS = 0x36
NRC_NOSAPS = 0x37
NRC_NORESOURCES = 0x38
NRC_INVADDRESS = 0x39
NRC_INVDDID = 0x3B
NRC_LOCKFAIL = 0x3C
NRC_OPENERR = 0x3F
NRC_SYSTEM = 0x40
NRC_PENDING = 0xFF


UCHAR = "B"
WORD = "H"
DWORD = "I"
USHORT = "H"
ULONG = "I"

ADAPTER_STATUS_ITEMS = [
    ("6s", "adapter_address"),
    (UCHAR, "rev_major"),
    (UCHAR, "reserved0"),
    (UCHAR, "adapter_type"),
    (UCHAR, "rev_minor"),
    (WORD, "duration"),
    (WORD, "frmr_recv"),
    (WORD, "frmr_xmit"),
    (WORD, "iframe_recv_err"),
    (WORD, "xmit_aborts"),
    (DWORD, "xmit_success"),
    (DWORD, "recv_success"),
    (WORD, "iframe_xmit_err"),
    (WORD, "recv_buff_unavail"),
    (WORD, "t1_timeouts"),
    (WORD, "ti_timeouts"),
    (DWORD, "reserved1"),
    (WORD, "free_ncbs"),
    (WORD, "max_cfg_ncbs"),
    (WORD, "max_ncbs"),
    (WORD, "xmit_buf_unavail"),
    (WORD, "max_dgram_size"),
    (WORD, "pending_sess"),
    (WORD, "max_cfg_sess"),
    (WORD, "max_sess"),
    (WORD, "max_sess_pkt_size"),
    (WORD, "name_count"),
]

NAME_BUFFER_ITEMS = [
    (str(NCBNAMSZ) + "s", "name"),
    (UCHAR, "name_num"),
    (UCHAR, "name_flags"),
]

SESSION_HEADER_ITEMS = [
    (UCHAR, "sess_name"),
    (UCHAR, "num_sess"),
    (UCHAR, "rcv_dg_outstanding"),
    (UCHAR, "rcv_any_outstanding"),
]

SESSION_BUFFER_ITEMS = [
    (UCHAR, "lsn"),
    (UCHAR, "state"),
    (str(NCBNAMSZ) + "s", "local_name"),
    (str(NCBNAMSZ) + "s", "remote_name"),
    (UCHAR, "rcvs_outstanding"),
    (UCHAR, "sends_outstanding"),
]

LANA_ENUM_ITEMS = [
    ("B", "length"),  # Number of valid entries in lana[]
    (str(MAX_LANA + 1) + "s", "lana"),
]

FIND_NAME_HEADER_ITEMS = [
    (WORD, "node_count"),
    (UCHAR, "reserved"),
    (UCHAR, "unique_group"),
]

FIND_NAME_BUFFER_ITEMS = [
    (UCHAR, "length"),
    (UCHAR, "access_control"),
    (UCHAR, "frame_control"),
    ("6s", "destination_addr"),
    ("6s", "source_addr"),
    ("18s", "routing_info"),
]

ACTION_HEADER_ITEMS = [
    (ULONG, "transport_id"),
    (USHORT, "action_code"),
    (USHORT, "reserved"),
]

del UCHAR, WORD, DWORD, USHORT, ULONG

NCB = win32wnet.NCB


def Netbios(ncb):
    ob = ncb.Buffer
    is_ours = hasattr(ob, "_pack")
    if is_ours:
        ob._pack()
    try:
        return win32wnet.Netbios(ncb)
    finally:
        if is_ours:
            ob._unpack()


class NCBStruct:
    def __init__(self, items: Iterable[tuple[str, str]]) -> None:
        self._format = "".join([item[0] for item in items])
        self._items = items
        self._buffer_ = win32wnet.NCBBuffer(struct.calcsize(self._format))

        for format, name in self._items:
            if len(format) == 1:
                if format == "c":
                    val: bytes | int = b"\0"
                else:
                    val = 0
            else:
                l = int(format[:-1])
                val = b"\0" * l
            self.__dict__[name] = val

    def _pack(self):
        vals = [self.__dict__.get(name) for format, name in self._items]

        self._buffer_[:] = struct.pack(self._format, *vals)

    def _unpack(self):
        items = struct.unpack(self._format, self._buffer_)
        assert len(items) == len(self._items), "unexpected number of items to unpack!"
        for (format, name), val in zip(self._items, items):
            self.__dict__[name] = val

    def __setattr__(self, attr, val):
        if attr not in self.__dict__ and attr[0] != "_":
            for format, attr_name in self._items:
                if attr == attr_name:
                    break
            else:
                raise AttributeError(attr)
        self.__dict__[attr] = val


def ADAPTER_STATUS():
    return NCBStruct(ADAPTER_STATUS_ITEMS)


def NAME_BUFFER():
    return NCBStruct(NAME_BUFFER_ITEMS)


def SESSION_HEADER():
    return NCBStruct(SESSION_HEADER_ITEMS)


def SESSION_BUFFER():
    return NCBStruct(SESSION_BUFFER_ITEMS)


def LANA_ENUM():
    return NCBStruct(LANA_ENUM_ITEMS)


def FIND_NAME_HEADER():
    return NCBStruct(FIND_NAME_HEADER_ITEMS)


def FIND_NAME_BUFFER():
    return NCBStruct(FIND_NAME_BUFFER_ITEMS)


def ACTION_HEADER():
    return NCBStruct(ACTION_HEADER_ITEMS)


if __name__ == "__main__":
    # code ported from "HOWTO: Get the MAC Address for an Ethernet Adapter"
    # MS KB ID: Q118623
    ncb = NCB()
    ncb.Command = NCBENUM
    la_enum = LANA_ENUM()
    ncb.Buffer = la_enum
    rc = Netbios(ncb)
    if rc != 0:
        raise RuntimeError("Unexpected result %d" % (rc,))
    for i in range(la_enum.length):
        ncb.Reset()
        ncb.Command = NCBRESET
        ncb.Lana_num = la_enum.lana[i]
        rc = Netbios(ncb)
        if rc != 0:
            raise RuntimeError("Unexpected result %d" % (rc,))
        ncb.Reset()
        ncb.Command = NCBASTAT
        ncb.Lana_num = la_enum.lana[i]
        ncb.Callname = b"*               "
        adapter = ADAPTER_STATUS()
        ncb.Buffer = adapter
        Netbios(ncb)
        print("Adapter address:", end=" ")
        for ch in adapter.adapter_address:
            print(f"{ch:02x}", end=" ")
        print()
