from enum import StrEnum, auto
from typing import Any, Optional, Union, TYPE_CHECKING
from dataclasses import dataclass, fields

if TYPE_CHECKING:
    from typing import Self


class AuthType(StrEnum):
    AUTHTYPE_UNSPECIFIED = auto()
    MACHINE = auto()
    PHYSICAL = auto()
    SSO = auto()
    TACACS = auto()
    VPN = auto()

class AuthenticationStatus(StrEnum):
    ACTIVE = auto()
    DELETED = auto()
    NO_ACTIVE_CREDENTIALS = auto()
    SUSPENDED = auto()
    UNKNOWN_AUTHENTICATION_STATUS = auto()

class Mechanism(StrEnum):
    BADGE_READER = auto()
    BATCH = auto()
    CACHED_INTERACTIVE = auto()
    CACHED_REMOTE_INTERACTIVE = auto()
    CACHED_UNLOCK = auto()
    HARDWARE_KEY = auto()
    INTERACTIVE = auto()
    LOCAL = auto()
    MECHANISM_OTHER = auto()
    MECHANISM_UNSPECIFIED = auto()
    NETWORK = auto()
    NETWORK_CLEAR_TEXT = auto()
    NEW_CREDENTIALS = auto()
    OTP = auto()
    REMOTE = auto()
    REMOTE_INTERACTIVE = auto()
    SERVICE = auto()
    UNLOCK = auto()
    USERNAME_PASSWORD = auto()

@dataclass
class Type:
    Bool: Optional[bool] = None
    Email: Optional[bool] = None
    Enum: Optional[type[StrEnum]] = None
    Float: Optional[bool] = None
    Int: Optional[bool] = None
    Ip: Optional[bool] = None
    Map: Optional["Type"] = None
    Repeated: Optional[bool] = False
    String: Optional[bool] = None
    Timestamp: Optional[bool] = None
    Unknown: Optional[bool] = None

    def __getattr__(self, item):
        if item in self.__annotations__:
            return getattr(self, item)
        raise AttributeError(f"Type object has no attribute '{item}'")

    def describe(self):
        return {
            "type": (
                self.Enum.__name__
                if self.Enum
                else next(
                    (
                        f.name
                        for f in fields(self)
                        if getattr(self, f.name) and f.name != "Repeated"
                    ),
                    "Unknown",
                )
            ),
            "outertype": next((t for t in ("Map", "Enum") if getattr(self, t)), None),
            "repeated": self.Repeated or False,
        }


class User:
    group_identifiers = Type(String=True, Repeated=True)
    userid = Type(String=True)
    auth_mechanism = Type(Enum=Mechanism)


class Noun:
    user = User


class Severity(StrEnum):
    CRITICAL = auto()
    """Critical severity."""
    HIGH = auto()
    """High severity."""
    LOW = auto()
    """Low severity."""
    MEDIUM = auto()
    """Medium severity."""
    UNKNOWN_SEVERITY = auto()
    """The default severity level."""


class Vulnerability:
    about = Noun
    cve_description = Type(String=True)
    cve_id = Type(String=True)
    cvss_base_score = Type(Float=True)
    description = Type(String=True)
    first_found = Type(Timestamp=True)
    last_found = Type(Timestamp=True)
    name = Type(String=True)
    scan_end_time = Type(Timestamp=True)
    scan_start_time = Type(Timestamp=True)
    severity = Type(Enum=Severity)
    severity_details = Type(String=True)
    vendor = Type(String=True)
    vendor_knowledge_base_article_id = Type(String=True)
    vendor_vulnerability_id = Type(String=True)


class Repeated:
    Noun = Noun
    Vulnerability = Vulnerability


class Authentication:
    auth_details = Type(String=True)
    """The vendor defined details of the authentication."""
    mechanism = Type(Enum=Mechanism, Repeated=True)
    """The authentication mechanism."""
    type = Type(Enum=AuthType)
    """The type of authentication."""


class Vulnerabilities:
    vulnerabilities = Repeated.Vulnerability


class Extension:
    auth = Authentication
    """An authentication extension."""
    vulns = Vulnerabilities
    """A vulnerability extension."""


class Metadata:
    base_labels = Type(Unknown=True)
    collected_timestamp = Type(Timestamp=True)


class Event:
    about = Repeated.Noun
    """
    Represents entities referenced by the event that are not otherwise described in principal, src, target, intermediary or observer. For example, it could be used to track email file attachments, domains/URLs/IPs embedded within an email body, and DLLs that are loaded during a PROCESS_LAUNCH event.
    """
    # TODO: additional
    extensions = Extension
    """
    All other first-class, event-specific metadata goes in this message. Don't place protocol metadata in Extensions; put it in Network.
    """
    intermediary = Repeated.Noun
    """
    Represents details on one or more intermediate entities processing activity described in the event. This includes device details about a proxy server or SMTP relay server. If an active event (that has a principal and possibly target) passes through any intermediaries, they're added here. Intermediaries can impact the overall action, for example blocking or modifying an ongoing request. A rule of thumb here is that 'principal', 'target', and description of the initial action should be the same regardless of the intermediary or its action. A successful network connection from A->B should look the same in principal/target/intermediary as one blocked by firewall C: principal: A, target: B (intermediary: C).
    """
    metadata = Metadata
    """Event metadata such as timestamp, source product, etc."""
    # TODO: network = Network
    observer = Noun
    """
    Represents an observer entity (for example, a packet sniffer or network-based vulnerability scanner), which is not a direct intermediary, but which observes and reports on the event in question.
    """
    principal = Noun
    """
    Represents the acting entity that originates the activity described in the event. The principal must include at least one machine detail (hostname, MACs, IPs, port, product-specific identifiers like an EDR asset ID) or user detail (for example, username), and optionally include process details. It must NOT include any of the following fields: email, files, registry keys, or values.
    """
    # TODO: security_result = Repeated.ScecurityResult
    src = Noun
    """
    Represents a source entity being acted upon by the participant along with the device or process context for the source object (the machine where the source object resides). For example, if user U copies file A on machine X to file B on machine Y, both file A and machine X would be specified in the src portion of the UDM event.
    """
    target = Noun
    """
    Represents a target entity being referenced by the event or an object on the target entity. For example, in a firewall connection from device A to device B, A is described as the principal and B is described as the target. For a process injection by process C into target process D, process C is described as the principal and process D is described as the target.
    """


class Entity:
    pass


class UDM:
    event = Event
    """A UDM event."""
    entity = Entity


class Node:
    def __init__(self, cls, path=None):
        self._cls = cls
        self._path = path or []

    def __getattr__(self, item):
        attr = getattr(self._cls, item)
        path = self._path + [item]
        if isinstance(attr, Type):
            return Leaf(attr, path)
        elif isinstance(attr, type):
            return Node(attr(), path)
        else:
            return Node(attr, path)

    def describe(self):
        return {".".join(self._path): {"type": self._cls.__class__.__name__}}


class Leaf:
    def __init__(self, type_obj: Type, path):
        self._type_obj = type_obj
        self._path = path

    def describe(self):
        descriptions = {}
        current = UDM._cls
        for i in range(len(self._path)):
            key = ".".join(self._path[: i + 1])
            attr = getattr(current, self._path[i])
            if isinstance(attr, Type):
                descriptions[key] = attr.describe()
            elif isinstance(attr, type):
                descriptions[key] = {"type": attr.__name__}
                current = attr()
            else:
                descriptions[key] = {"type": attr.__class__.__name__}
                current = attr
        return descriptions


UDM = Node(UDM)

for x in [
    UDM.event.about.user.group_identifiers,
    UDM.event.principal.user.userid,
    UDM.event.principal.user.auth_mechanism,
    UDM.event.extensions.auth.mechanism,
]:
    print(x.describe())


the above is a representation of the chronicle udm. it outputs the following:
{'event': {'type': 'Event'}, 'event.about': {'type': 'Noun'}, 'event.about.user': {'type': 'User'}, 'event.about.user.group_identifiers': {'type': 'String', 'outertype': None, 'repeated': True}}
{'event': {'type': 'Event'}, 'event.principal': {'type': 'Noun'}, 'event.principal.user': {'type': 'User'}, 'event.principal.user.userid': {'type': 'String', 'outertype': None, 'repeated': False}}
{'event': {'type': 'Event'}, 'event.principal': {'type': 'Noun'}, 'event.principal.user': {'type': 'User'}, 'event.principal.user.auth_mechanism': {'type': 'Mechanism', 'outertype': 'Enum', 'repeated': False}}
{'event': {'type': 'Event'}, 'event.extensions': {'type': 'Extension'}, 'event.extensions.auth': {'type': 'Authentication'}, 'event.extensions.auth.mechanism': {'type': 'Mechanism', 'outertype': 'Enum', 'repeated': True}}

now, the only thing that is missin gis the fact that about, which is defiend as about = Repeated.Noun, and NOT just Noun like principal, is not showing repeated: True.

in a pythonic manner fix that