from typing import TYPE_CHECKING, Generic, TypeVar
from typing_extensions import Self

from construct import (
    Bytes,
    Container,
    CString,
    Default,
    FlagsEnum,
    FormatField,
    Int8ul,
    Int16sl,
    Int16ub,
    Int16ul,
    Int32ub,
    Int32ul,
    Int64sl,
    Int64ul,
    PaddedString,
    Padding,
    Pointer,
    Struct,
    this,
)

from rpcclient.core.structs.consts import AF_INET, AF_INET6, AF_UNIX


if TYPE_CHECKING:
    from rpcclient.core.client import BaseCoreClient
    from rpcclient.core.symbols_jar import BaseSymbol

SymbolT_co = TypeVar("SymbolT_co", bound="BaseSymbol", covariant=True)

UNIX_PATH_MAX = 104

u_char = Int8ul
uint8_t = Int8ul
short = Int16sl
u_short = Int16ul
uint32_t = Int32ul
uint64_t = Int64ul
u_int32_t = uint32_t
uid_t = Int32ul
gid_t = Int32ul
time_t = Int32ul
long = Int64sl
mode_t = Int16ul
in_addr = Bytes(4)
in6_addr = Bytes(16)

sockaddr_in = Struct(
    "sin_family" / Default(Int16sl, AF_INET),
    "sin_port" / Int16ub,
    "sin_addr" / in_addr,
    "sin_zero" / Default(Bytes(8), b"\x00" * 8),
)

sockaddr_in6 = Struct(
    "sin6_family" / Default(Int16sl, AF_INET6),
    "sin6_port" / Int16ub,
    "sin6_flowinfo" / Default(Int32ub, 0),
    "sin6_addr" / in6_addr,
    "sin6_scope_id" / Default(Int32ub, 0),
)

sockaddr_un = Struct(
    "sun_len" / Default(Int8ul, 0),
    "sun_family" / Default(Int8ul, AF_UNIX),
    "sun_path" / PaddedString(UNIX_PATH_MAX, "utf8"),
)

sockaddr = Struct(
    Padding(1),
    "sa_family" / Int8ul,
)

st_flags = FlagsEnum(
    Int32ul,
    UF_NODUMP=1,
    UF_IMMUTABLE=2,
    UF_APPEND=4,
    SF_ARCHIVED=0x00010000,
    SF_IMMUTABLE=0x00020000,
    SF_APPEND=0x00040000,
)

block_descriptor = Struct("reserved" / Int64ul, "size" / Int64ul)
block_literal = Struct(
    "isa" / Int64ul,
    "flags" / Int32ul,
    "reserved" / Int32ul,
    "invoke" / Int64ul,
    "descriptor" / Int64ul,
)


class SymbolFormatField(FormatField, Generic[SymbolT_co]):
    """
    A Symbol wrapper for construct
    """

    if TYPE_CHECKING:

        def __new__(cls, client: "BaseCoreClient[SymbolT_co]") -> Self: ...

    def __init__(self, client: "BaseCoreClient[SymbolT_co]") -> None:
        super().__init__("<", "Q")  # pyright: ignore[reportCallIssue]
        self._client: BaseCoreClient[SymbolT_co] = client

    def _parse(self, stream, context, path) -> SymbolT_co:
        return self._client.symbol(FormatField._parse(self, stream, context, path))


async def parse_hostent(client: "BaseCoreClient[SymbolT_co]", ptr: SymbolT_co) -> Container:
    hostent = await ptr.parse.z(
        Struct(
            "h_name" / SymbolFormatField(client),
            "h_aliases" / SymbolFormatField(client),
            "h_addrtype" / Int32ul,
            "h_length" / Int32ul,
            "h_addr_list" / SymbolFormatField(client),
        )
    )
    hostent["h_name"] = await hostent.h_name.peek_str.z("utf-8")
    return hostent


async def parse_ifaddrs(client: "BaseCoreClient[SymbolT_co]", ptr: SymbolT_co) -> Container:
    ifaddrs = await ptr.parse.z(
        Struct(
            "ifa_next" / SymbolFormatField(client),
            "ifa_name" / SymbolFormatField(client),
            "ifa_flags" / Int32ul,
            Padding(4),
            "ifa_addr" / SymbolFormatField(client),
            "ifa_netmask" / SymbolFormatField(client),
            "ifa_dstaddr" / SymbolFormatField(client),
            "ifa_data" / SymbolFormatField(client),
        )
    )

    if ifaddrs.ifa_next != 0:
        ifaddrs["ifa_next"] = await parse_ifaddrs(client, ifaddrs.ifa_next)

    if ifaddrs.ifa_name != 0:
        ifaddrs["ifa_name"] = await ifaddrs.ifa_name.peek_str.z("utf-8")

    return ifaddrs


def Dl_info(client) -> Struct:
    return Struct(
        "_dli_fname" / SymbolFormatField(client),
        "dli_fname" / Pointer(this._dli_fname, CString("utf8")),
        "dli_fbase" / SymbolFormatField(client),
        "_dli_sname" / SymbolFormatField(client),
        "dli_sname" / Pointer(this._dli_sname, CString("utf8")),
        "dli_saddr" / SymbolFormatField(client),
    )
