
from pydantic import BaseModel, Field
from typing import Optional, Dict, List, Literal, ClassVar, Union, Any
from enum import Enum

from acex_devkit.models.external_value import ExternalValue
from acex_devkit.models.attribute_value import AttributeValue
from acex_devkit.models.container_entry import ContainerEntry
from acex_devkit.models.logging import (
    LoggingConfig,
    Console,
    RemoteServers,
    VtyLine,
    FileLogging,
    LoggingEvents
)
from acex_devkit.models.spanning_tree import SpanningTree
from acex_devkit.models.acl_model import Acl

class MetadataValueType(str, Enum):
    CONCRETE = "concrete"
    EXTERNALVALUE = "externalValue"
    REFERENCE = "reference"

class Metadata(BaseModel):
    type: Optional[str] = "str"
    value_source: MetadataValueType = MetadataValueType.CONCRETE 

class Reference(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("pointer",)
    pointer: str
    metadata: Metadata = Metadata(type="str", value_source="reference")

class ReferenceTo(Reference):
    pointer: str
    metadata: Optional[Dict] = {}

class ReferenceFrom(Reference):
    pointer: str
    metadata: Optional[Dict] = {}

class RenderedReference(BaseModel):
    from_ptr: str
    to_ptr: str

class SystemConfig(BaseModel):
    contact: Optional[AttributeValue[str]] = None
    domain_name: Optional[AttributeValue[str]] = None
    hostname: Optional[AttributeValue[str]] = None
    location: Optional[AttributeValue[str]] = None
    login_banner: Optional[AttributeValue[str]] = None
    motd_banner: Optional[AttributeValue[str]] = None

#class TripleA(BaseModel): ...
# Trying to avoid using "Logging" or "logging" as names for anything due to conflicts with standard lib.

class VtyLines(BaseModel):
    lines: Dict[str, VtyLine] = {}

class LogFiles(BaseModel):
    files: Dict[str, FileLogging] = {}

class LoggingComponents(BaseModel):
    config: LoggingConfig = LoggingConfig()
    console: Optional[Console] = None
    remote_servers: Optional[RemoteServers] = RemoteServers()
    events: Optional[LoggingEvents] = None
    vty: Optional[VtyLines] = VtyLines()
    files: Optional[LogFiles] = LogFiles()

class NtpConfig(BaseModel):
    enabled: AttributeValue[bool] = AttributeValue(value=False)

class NtpServer(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("address",)
    address: AttributeValue[str]
    port: Optional[AttributeValue[int]] = None
    version: Optional[AttributeValue[int]] = None
    association_typ: Optional[AttributeValue[str]] = None
    prefer: Optional[AttributeValue[bool]] = None
    source_interface: Optional[AttributeValue[str]] = None

class Ntp(BaseModel): 
    config: Optional[NtpConfig] = None
    servers: Optional[Dict[str, NtpServer]] = {}

class SshServer(BaseModel): 
    enable: Optional[AttributeValue[bool]] = None
    protocol_version: Optional[AttributeValue[int]] = AttributeValue(value=2)
    timeout: Optional[AttributeValue[int]] = None
    auth_retries: Optional[AttributeValue[int]] = None
    source_interface: Optional[Reference] = None

class AuthorizedKeyAlgorithms(str, Enum):
    SSH_ED25519 = "ssh-ed25519"
    ECDSA_NISTP256 = "ecdsa-sha2-nistp256"
    ECDSA_NISTP384 = "ecdsa-sha2-nistp384"
    ECDSA_NISTP521 = "ecdsa-sha2-nistp521"
    RSA_SHA2_256 = "rsa-sha2-256"
    RSA_SHA2_512 = "rsa-sha2-512"
    SK_SSH_ED25519 = "sk-ssh-ed25519@openssh.com"
    SK_ECDSA_NISTP256 = "sk-ecdsa-sha2-nistp256@openssh.com"
    SSH_RSA = "ssh-rsa"
    SSH_DSS = "ssh-dss"

class AuthorizedKey(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("public_key",)
    algorithm: Optional[AttributeValue[AuthorizedKeyAlgorithms]] = None
    public_key: Optional[AttributeValue[str]] = None

class Ssh(BaseModel):
    config: Optional[SshServer] = None
    host_keys: Optional[Dict[str, AuthorizedKey]] = {}

class LldpConfigAttributes(BaseModel):
    enabled: Optional[AttributeValue[bool]] = None
    transmit_interval: Optional[AttributeValue[int]] = None
    hold_time: Optional[AttributeValue[int]] = None
    system_name: Optional[AttributeValue[str]] = None  # The system name field shall contain an alpha-numeric string that indicates the system's administratively assigned name.
    suppress_tlv_advertisement: Optional[AttributeValue[List[str]]] = None # Comma-separated list of TLVs to suppress in advertisements. Values depend on device and vendor, but common ones include "system_name", "system_description", "system_capabilities", "management_address", etc.
    interfaces: Optional[Dict[str, Reference]] = {}

class CdpConfigAttributes(BaseModel):
    enabled: Optional[AttributeValue[bool]] = None
    transmit_interval: Optional[AttributeValue[int]] = None
    hold_time: Optional[AttributeValue[int]] = None
    advertise_v2: Optional[AttributeValue[bool]] = None # Whether to advertise CDP version 2
    interfaces: Optional[Dict[str, Reference]] = {}

#class Lldp(BaseModel):
#    config: Optional[LldpConfigAttributes] = LldpConfigAttributes()
#    #interfaces: Optional[Dict[str, LldpInterfaceConfigAttributes]] = {}
#    interfaces: Optional[Dict[str, Reference]] = {}

class Vlan(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("vlan_id",)
    name: AttributeValue[str]
    vlan_id: Optional[AttributeValue[int]] = None
    vlan_name: Optional[AttributeValue[str]] = None
    network_instance: Optional[AttributeValue[str]] = None

class Interface(ContainerEntry, BaseModel):
    "Base class for all interfaces"
    identity_fields: ClassVar[tuple[str, ...]] = ("index", "type")
    index: AttributeValue[int]
    name: AttributeValue[str]

    description: Optional[AttributeValue[str]] = None
    enabled: Optional[AttributeValue[bool]] = None
    ipv4: Optional[AttributeValue[str]] = None
    redirects: Optional[AttributeValue[bool]] = None # Regarding IP redirects. 
    proxy_arp: Optional[AttributeValue[bool]] = None # Cisco true = yes / false = no, Juniper true = unrestricted / false = restricted
    
    type: Literal[
        "ethernetCsmacd",
        "ieee8023adLag",
        "l3ipvlan",
        "softwareLoopback",
        "subinterface",
        "managementInterface"
        ] = "ethernetCsmacd"
    
    model_config = {
        "discriminator": "type"
    }
    

class EthernetCsmacdInterface(Interface):
    "Physical Interface"
    type: Literal["ethernetCsmacd"] = "ethernetCsmacd"

    # Egenskaper för fysiska interface
    stack_index: Optional[AttributeValue[int]] = None
    module_index: Optional[AttributeValue[int]] = None
    subinterfaces: list["SubInterface"] = Field(default_factory=list)
    speed: Optional[AttributeValue[int]] = None
    duplex: Optional[AttributeValue[str]] = None
    switchport: Optional[AttributeValue[bool]] = None
    switchport_mode: Optional[AttributeValue[Literal["access", "trunk"]]] = None
    trunk_allowed_vlans: Optional[AttributeValue[List[int]]] = None
    native_vlan: Optional[AttributeValue[int]] = None
    access_vlan: Optional[AttributeValue[int]] = None
    vlan_id: Optional[AttributeValue[int]] = None
    voice_vlan: Optional[AttributeValue[int]] = None
    mtu: Optional[AttributeValue[int]] = None # No default set as it differs between devices and vendors
    negotiation: Optional[AttributeValue[bool]] = None
    #lldp_enabled: Optional[AttributeValue[bool]] = None
    #cdp_enabled: Optional[AttributeValue[bool]] = None

    # LACP relaterade attribut
    aggregate_id: Optional[AttributeValue[int]] = None
    lacp_enabled: Optional[AttributeValue[bool]] = None
    lacp_mode: Optional[AttributeValue[Literal["active", "passive", "on", "auto"]]] = None
    lacp_port_priority: Optional[AttributeValue[int]] = None
    #lacp_system_id_mac: Optional[AttributeValue[str]] = None
    lacp_interval: Optional[AttributeValue[Literal["fast", "slow"]]] = None

    # Spanning-tree relaterade attribut
    stp_port_priority: Optional[AttributeValue[int]] = None
    stp_cost: Optional[AttributeValue[int]] = None
    stp_edge_port: Optional[AttributeValue[bool]] = None
    stp_bpdu_filter: Optional[AttributeValue[bool]] = None
    stp_bpdu_guard: Optional[AttributeValue[bool]] = None
    stp_loop_guard: Optional[AttributeValue[bool]] = None
    stp_root_guard: Optional[AttributeValue[bool]] = None
    stp_portfast: Optional[AttributeValue[bool]] = None
    stp_link_type: Optional[AttributeValue[Literal["point-to-point", "shared"]]] = None


class Ieee8023adLagInterface(Interface):
    "LAG Interface"
    type: Literal["ieee8023adLag"] = "ieee8023adLag"
    aggregate_id: Optional[AttributeValue[int]] = None
    members: Optional[AttributeValue[List[str]]] = None
    max_ports: Optional[AttributeValue[int]] = None
    switchport: Optional[AttributeValue[bool]] = None
    switchport_mode: Optional[AttributeValue[Literal["access", "trunk"]]] = None
    trunk_allowed_vlans: Optional[AttributeValue[List[int]]] = None
    native_vlan: Optional[AttributeValue[int]] = None
    mtu: Optional[AttributeValue[int]] = None # No default set as it differs between devices and vendors

class L3IpvlanInterface(Interface):
    "SVI Interface"
    type: Literal["l3ipvlan"] = "l3ipvlan"
    vlan_id: Optional[AttributeValue[int]] = None

class SoftwareLoopbackInterface(Interface):
    "Loopback Interface"
    type: Literal["softwareLoopback"] = "softwareLoopback"

    # Loopback har varken vlan, duplex eller speed
    vlan_id: Optional[AttributeValue[int]] = None
    ipv4: Optional[AttributeValue[str]] = None

class SubInterface(Interface):
    "Subinterface"
    type: Literal["subinterface"] = "subinterface"

    vlan_id: Optional[AttributeValue[int]] = None
    ipv4: Optional[AttributeValue[str]] = None

class ManagementInterface(Interface):
    "Management Interface"
    type: Literal["managementInterface"] = "managementInterface"

    # Mgmt har inte vlan
    vlan_id: Optional[AttributeValue[int]] = None

class StaticRouteNextHop(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("next_hop",)
    index: Optional[AttributeValue[int]] = None
    next_hop: AttributeValue[str] # can be an IP address or an interface. Reference will be handled in config component
    metric: Optional[AttributeValue[int]] = None
    static_route: Optional[AttributeValue[str]] = None # Reference to parent static route, used for easier access in config component
    network_instance: Optional[AttributeValue[str]] = None


class StaticRoute(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("prefix",)
    route_name: Optional[AttributeValue[str]] = None
    prefix: AttributeValue[str]
    next_hops: Optional[Dict[str, StaticRouteNextHop]] = {}
    network_instance: Optional[AttributeValue[str]] = None

class Protocols(BaseModel):
    static_routes: Optional[Dict[str, StaticRoute]] = {}
    # OSPF, BGP, etc. can be added here as needed

class RouteTarget(BaseModel):
    value: Optional[AttributeValue[str]] = None

class ImportExportPolicy(BaseModel):
    export_route_target: Optional[List[RouteTarget]] = None
    import_route_target: Optional[List[RouteTarget]] = None

class InterInstancePolicy(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ()
    import_export_policy: ImportExportPolicy

class NetworkInstance(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("name",)
    name: AttributeValue[str]
    description: Optional[AttributeValue[str]] = None
    vlans: Optional[Dict[str, Vlan]] = {}
    interfaces: Optional[Dict[str, Reference]] = {}
    inter_instance_policies: Optional[Dict[str, InterInstancePolicy]] = {}
    protocols: Optional[Protocols] = Protocols()

class LacpConfig(BaseModel):
    system_priority: Optional[AttributeValue[int]] = None
    system_id_mac: Optional[AttributeValue[str]] = None
    load_balance_algorithm: Optional[AttributeValue[list[Literal["src-mac", "dst-mac", "src-dst-mac", "src-ip", "dst-ip", "src-dst-ip", "src-port", "dst-port", "src-dst-port"]]]] = None

class Lacp(BaseModel):
    config: Optional[LacpConfig] = LacpConfig()
    interfaces: Optional[Dict[str, Interface]] = {}

# SNMP
class SnmpAccess(str, Enum):
	READ_ONLY = "READ_ONLY"
	READ_WRITE = "READ_WRITE"


class SnmpSecurityLevel(str, Enum):
	NO_AUTH_NO_PRIV = "NO_AUTH_NO_PRIV"
	AUTH_NO_PRIV = "AUTH_NO_PRIV"
	AUTH_PRIV = "AUTH_PRIV"


class SnmpAuthProtocol(str, Enum):
	MD5 = "MD5"
	SHA1 = "SHA"
	SHA224 = "SHA-224"
	SHA256 = "SHA-256"
	SHA384 = "SHA-384"
	SHA512 = "SHA-512"


class SnmpPrivProtocol(str, Enum):
	DES = "DES"
	TRIPLE_DES = "3DES"
	AES128 = "AES128"
	AES192 = "AES192"
	AES256 = "AES256"


class SnmpConfig(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ()
    enabled: AttributeValue[bool] = AttributeValue(value=False)
    engine_id: Optional[AttributeValue[str]] = None
    location: Optional[AttributeValue[str]] = None
    contact: Optional[AttributeValue[str]] = None


class SnmpCommunity(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("name",)
    name: AttributeValue[str]
    community: Optional[AttributeValue[str]] = None # Community string
    access: Optional[AttributeValue[SnmpAccess]] = AttributeValue(value=SnmpAccess.READ_ONLY)
    view: Optional[AttributeValue[str]] = None
    ipv4_acl: Optional[AttributeValue[str]] = None # Cisco and "liknande" vendors 
    ipv6_acl: Optional[AttributeValue[str]] = None
    source_interface: Optional[Reference] = None
    clients: Optional[AttributeValue[List[str]]] = None # Juniper specific


class SnmpUser(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("username",)
    username: AttributeValue[str]
    security_level: Optional[AttributeValue[SnmpSecurityLevel]] = AttributeValue(value=SnmpSecurityLevel.NO_AUTH_NO_PRIV)
    auth_protocol: Optional[AttributeValue[SnmpAuthProtocol]] = None
    auth_password: Optional[AttributeValue[str]] = None
    priv_protocol: Optional[AttributeValue[SnmpPrivProtocol]] = None
    priv_password: Optional[AttributeValue[str]] = None


class SnmpView(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("name",)
    name: AttributeValue[str]
    oid: AttributeValue[str]
    included: Optional[AttributeValue[bool]] = AttributeValue(value=True)


class SnmpServer(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("address",)
    name: Optional[AttributeValue[str]] = None
    address: Optional[AttributeValue[str]] = None
    port: Optional[AttributeValue[int]] = AttributeValue(value=162)
    enabled: Optional[AttributeValue[bool]] = AttributeValue(value=True)
    version: Optional[AttributeValue[Literal["v2c", "v3"]]] = None
    community: Optional[AttributeValue[str]] = None
    username: Optional[AttributeValue[str]] = None
    security_level: Optional[AttributeValue[SnmpSecurityLevel]] = None
    source_interface: Optional[Reference] = None
    network_instance: Optional[AttributeValue[str]] = None

# ----------------------------
# Enum-based trap groups
# ----------------------------
# En stor class med Enum	
class TrapEventOptions(str, Enum):
	VRF_UP = "vrf-up"
	VRF_DOWN = "vrf-down"
	VNET_TRUNK_UP = "vnet-trunk-up"
	VNET_TRUNK_DOWN = "vnet-trunk-down"
	ALL = "all"
	RfEvent = 'enabled'
	VlanMembershipEvent = 'enabled'
	ErrdisableEvent = 'enabled'
	CHANGE = "change"
	MOVE = "move"
	THRESHOLD = "threshold"
	AUTHENTICATION = "authentication"
	LINKDOWN = "linkdown"
	LINKUP = "linkup"
	COLDSTART = "coldstart"
	WARMSTART = "warmstart"
	FLOWMONEVENT = "trapflowmonevent"
	ENTITYPERFEVENT = "trapentityperfevent"
	PCALLHOMEEVENT_MESSAGE_SEND_FAIL = "trapcallhomeevent_MESSAGE_SEND_FAIL"
	CALLHOMEEVENT_SERVER_FAIL = "trapcallhomeevent_SERVER_FAIL"	
	TTYEVENT = "trapttyevent"
	EIGRPEVENT = "trapeigrpevent"
	OSPF_STATE_CHANGE = "ospf_state_change"
	OSPF_ERRORS = "ospf_errors"
	OSPF_RETRANSMIT = "ospf_retransmit"
	OSPF_LSA = "ospf_lsa"
	OSPF_CISCO_TRANS_CHANGE = "ospf_cisco_trans_change"
	OSPF_CISCO_SHAMLINK_INTERFACE = "ospf_cisco_shamlink_interface"
	OSPF_CISCO_SHAMLINK_NEIGHBOR = "ospf_cisco_shamlink_neighbor"
	BFD_EVENT = "bfd_event"
	CISCO_SMART_LICENSE_EVENT = "cisco_smart_license_event"
	AUTH_FRAMEWORK_SEC_VIOLATION = "auth_framework_sec_violation"
	REP_EVENT = "rep_event"
	MEMORY_BUFFERPEAK = "memory_bufferpeak"
	CONFIG_COPY = "config_copy"
	CONFIG = "config"
	CONFIG_CTID = "config_ctid"
	ENERGYWISE_EVENT = "energy_wise_event"
	FRU_CTRL_EVENT = "fru_ctrl_event"
	ENTITY_EVENT = "entity_event"
	FLASH_INSERTION = "flash_insertion"
	FLASH_REMOVAL = "flash_removal"
	FLASH_LOWSPACE = "flash_lowspace"
	POWER_ETHERNET_POLICE = "power_ethernet_police"
	POWER_ETHERNET_GROUP_THRESHOLD = "power_ethernet_group_threshold"
	CPU_THRESHOLD = "cpu_threshold"
	SYSLOG = "syslog"
	UDLD_LINK_FAIL_RPT = "udld_link_fail_rpt"
	UDLD_STATUS_CHANGE = "udld_status_change"
	VTP_EVENT = "vtp_event"
	VLAN_CREATE = "vlancreate"
	VLAN_DELETE = "vlandelete"
	PORT_SECURITY = "port_security"
	ENV_MON = "env_mon"
	STACKWISE = "stackwise"
	MVPN = "mvpn"
	PW_VC = "pw_vc"	
	IPSLA = "ipsla"
	DHCP = "dhcp"
	EVENT_MANAGER = "event_manager"
	IKE_POLICY_ADD = "ike_policy_add"
	IKE_POLICY_DELETE = "ike_policy_delete"
	IKE_TUNNEL_START = "ike_tunnel_start"
	IKE_TUNNEL_STOP = "ike_tunnel_stop"
	IPSEC_CRYPTOMAP_ADD = "ipsec_cryptomap_add"
	IPSEC_CRYPTOMAP_DELETE = "ipsec_cryptomap_delete"
	IPSEC_CRYPTOMAP_ATTACH = "ipsec_cryptomap_attach"
	IPSEC_CRYPTOMAP_DETACH = "ipsec_cryptomap_detach"
	IPSEC_TUNNEL_START = "ipsec_tunnel_start"
	IPSEC_TUNNEL_STOP = "ipsec_tunnel_stop"
	IPSEC_TOO_MANY_SAS = "ipsec_too_many_sas"
	OSPFV3_STATE_CHANGE = "ospfv3_state_change"
	OSPFV3_ERRORS = "ospfv3_errors"
	IP_MULTICAST = "ip_multicast"
	MSDP = "msdp"
	PIM_NEIGHBOR_CHANGE = "pim_neighbor_change"
	PIM_RP_MAPPING_CHANGE = "pim_rp_mapping_change"
	INVALID_PIM_MESSAGE = "invalid_pim_message"
	BRIDGE_NEWROOT = "bridge_newroot"
	BRIDGE_TOPOLOGYCHANGE = "bridge_topologychange"
	STPX_INCONSISTENCY = "stpx_inconsistency"
	STPX_ROOT_INCONSISTENCY = "stpx_root_inconsistency"
	STPX_LOOP_INCONSISTENCY = "stpx_loop_inconsistency"
	BGP_CBG2 = "bgp_cbg2"
	HSRP = "hsrp"
	ISIS = "isis"
	CEF_RESOURCE_FAILURE = "cef_resource_failure"
	CEF_PEER_STATE_CHANGE = "cef_peer_state_change"
	CEF_PEER_FIB_STATE_CHANGE = "cef_peer_fib_state_change"
	CEF_INCONSISTENCY = "cef_inconsistency"
	LISP = "lisp"
	NHRP_NHS = "nhrp_nhs"
	NHRP_NHC = "nhrp_nhc"
	NHRP_NHP = "nhrp_nhp"
	NHRP_QUOTA_EXCEEDED = "nhrp_quota_exceeded"
	LOCAL_AUTH = "local_auth"
	ENTITY_DIAG_BOOT_UP_FAIL = "entity_diag_boot_up_fail"
	ENTITY_DIAG_HM_TEST_RECOVER = "entity_diag_hm_test_recover"
	ENTITY_DIAG_HM_THRESH_REACHED = "entity_diag_hm_thresh_reached"
	ENTITY_DIAG_SCHEDULED_TEST_FAIL = "entity_diag_scheduled_test_fail"
	MPLS_RFC_LDP = "mpls_rfc_ldp"
	MPLS_LDP = "mpls_ldp"
	MPLS_RFC_TRAFFIC_ENG = "mpls_rfc_traffic_eng"
	MPLS_TRAFFIC_ENG = "mpls_traffic_eng"
	MPLS_FAST_REROUTE_PROTECTED = "mpls_fast_reroute_protected"
	MPLS_VPN = "mpls_vpn"
	MPLS_RFC_VPN = "mpls_rfc_vpn"
	BULKSTAT_COLLECTION = "bulkstat_collection"
	BULKSTAT_TRANSFER = "bulkstat_transfer"

class TrapEvent(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("event_name",)
    name: Optional[AttributeValue[str]] = None
    event_name: Optional[AttributeValue[TrapEventOptions]] = None

#class SnmpTrap(BaseModel): ...

class Snmp(BaseModel):
    config: Optional[Dict[str, SnmpConfig]] = {}
    communities: Optional[Dict[str, SnmpCommunity]] = {}
    users: Optional[Dict[str, SnmpUser]] = {}
    trap_servers: Optional[Dict[str, SnmpServer]] = {}
    trap_events: Optional[Dict[str, TrapEvent]] = {}
    views: Optional[Dict[str, SnmpView]] = {}

# AAA
class aaaBaseClass(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ()  # key is identity (e.g. "default", "console")
    name: Optional[AttributeValue[str]] = None

class aaaTacacsAttributes(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("address",)
    port: Optional[AttributeValue[int]] = None
    secret_key: Optional[AttributeValue[str]] = None
    secret_key_hashed: Optional[AttributeValue[str]] = None
    address: Optional[AttributeValue[str]] = None
    timeout: Optional[AttributeValue[int]] = None
    source_interface: Optional[Reference] = None
    server_group: Optional[AttributeValue[str]] = None

class aaaRadiusAttributes(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("address",)
    auth_port: Optional[AttributeValue[int]] = None
    acct_port: Optional[AttributeValue[int]] = None
    secret_key: Optional[AttributeValue[str]] = None
    secret_key_hashed: Optional[AttributeValue[str]] = None
    address: Optional[AttributeValue[str]] = None
    timeout: Optional[AttributeValue[int]] = None
    source_interface: Optional[Reference] = None
    retransmit_attempts: Optional[AttributeValue[int]] = None
    server_group: Optional[AttributeValue[str]] = None

class aaaServerGroupAttributes(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ()  # key is the group name
    """
    Define a AAA server group that can contain multiple TACACS+ and/or RADIUS servers.

    Type is used to tell future renderers what kind of server group this is.
    Example:
    type = 'tacacs' or type = 'radius'

    The tacacs and radius attributes expect a reference to the aaaTacacs and aaaRadius models respectively.

    Example in config map:
    enable = True
    type = 'tacacs'
    tacacs = [tacacs_server1, tacacs_server2]
    radius = radius_server1

    Cisco example:
    aaa group server tacacs+ TACACS-GROUP
     server name tacacs_server1
     server name tacacs_server2
    """
    enable: Optional[AttributeValue[bool]] = None
    type: Optional[AttributeValue[Literal['tacacs','radius']]] = None
    tacacs: Optional[Dict[str, aaaTacacsAttributes]] = {}
    radius: Optional[Dict[str, aaaRadiusAttributes]] = {}

# Authentication Models
class aaaAuthenticationMethods(aaaBaseClass):
    """
    Define the authentiation methods used by AAA. If you define a server group using the "aaaServerGroup" model, 
    you can reference it here by its name, but only as a string.

    Example in config map:
    method = ['TACACS_GROUP','LOCAL']

    Cisco example:
    aaa authentication login default group TACACS-GROUP-NEW local
    """
    #method: Optional[List[str]] = None # Ex. ['TACACS_GROUP','LOCAL', 'default', 'enable'] - TACACS_GROUP is reference to server group
    method: Optional[AttributeValue[str]] = None 
    # Method could handle a reference to a server group in the future. For now we only use strings. Important for users to know this.

    # Cisco example:
    # aaa authentication login default group TACACS-GROUP-NEW local
    # aaa authentication enable default group TACACS-GROUP-NEW enable

class authenticationUser(aaaBaseClass):
    identity_fields: ClassVar[tuple[str, ...]] = ("username",)
    username: Optional[AttributeValue[str]] = None
    password: Optional[AttributeValue[str]] = None
    password_hahsed: Optional[AttributeValue[str]] = None
    ssh_key: Optional[AttributeValue[str]] = None
    role: Optional[AttributeValue[str]] = None

class aaaAuthenticationUsers(aaaBaseClass):
    username: Optional[Dict[str, authenticationUser]] = {}

class adminUser(aaaBaseClass):
    admin_password: Optional[AttributeValue[str]] = None
    admin_password_hashed: Optional[AttributeValue[str]] = None

class aaaAuthenticationAdminUsers(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ()
    config: Optional[Dict[str, adminUser]] = {}

class aaaAuthentication(BaseModel):
    config: Optional[Dict[str, aaaAuthenticationMethods]] = {}
    admin_user: Optional[Dict[str, aaaAuthenticationAdminUsers]] = {}
    users: Optional[Dict[str, aaaAuthenticationUsers]] = {}

# Authorization Models
class aaaAuthorizationMethods(aaaBaseClass):
    """
    Define the authorization methods used by AAA. If you define a server group using the "aaaServerGroup" model, 
    you can reference it here by its name, but only as a string.

    Example in config map:
    method = ['TACACS_GROUP','LOCAL']

    Cisco example:
    aaa authorization login default group TACACS-GROUP-NEW local
    """
    #method: Optional[List[str]] = None # Ex. ['TACACS_GROUP','LOCAL']
    method: Optional[AttributeValue[str]] = None

class aaaAuthorizationEvents(aaaBaseClass):
    """
    Define authorization events. 

    Cisco example:
    aaa authorization config-commands
    aaa authorization console
    """
    #events: Optional[List[str]] = Field(default_factory=list) # Ex. ['config-commands','console']
    #event: Optional[AttributeValue[List[str]]] = None
    event: Optional[AttributeValue[str]] = None

class aaaAuthorization(BaseModel):
    config: Optional[Dict[str, aaaAuthorizationMethods]] = {}
    events: Optional[Dict[str, aaaAuthorizationEvents]] = {}

# Accounting Models
class aaaAccountingMethods(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ()
    """
    Define the accounting methods used by AAA. If you define a server group using the "aaaServerGroup" model,
    you can reference it here by its name, but only as a string.

    Example in config map:
    method = ['TACACS_GROUP','LOCAL']

    Cisco example:
    aaa accounting login default group TACACS-GROUP-NEW local
    """
    #method: Optional[List[str]] = None # Ex. ['TACACS_GROUP','LOCAL']
    method: Optional[AttributeValue[str]] = None

class aaaAccountingEvents(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ()
    """
    Define accounting events.

    Cisco example:
    aaa accounting send stop-record authentication failure
    """
    #events: Optional[List[str]] = Field(default_factory=list) # Ex. ['send','stop-record','authentication', 'failure']
    event: Optional[AttributeValue[str]] = None

class aaaAccounting(BaseModel):
    config: Optional[Dict[str, aaaAccountingMethods]] = {}
    events: Optional[Dict[str, aaaAccountingEvents]] = {}

class aaaGlobalAttributes(BaseModel):
    enabled: Optional[AttributeValue[bool]] = False # default False

class TripleA(BaseModel):
    config: aaaGlobalAttributes = aaaGlobalAttributes()
    server_groups: Optional[Dict[str, aaaServerGroupAttributes]] = {}
    authentication: aaaAuthentication = aaaAuthentication()
    authorization: aaaAuthorization = aaaAuthorization()
    accounting: aaaAccounting = aaaAccounting()

class VTPAttributes(BaseModel):
    domain_name: Optional[AttributeValue[str]] = None
    mode: Optional[AttributeValue[Literal["server", "client", "transparent", 'off']]] = None
    primary_server: Optional[AttributeValue[bool]] = False # Cisco proprietary
    version: Optional[AttributeValue[Literal[1, 2, 3]]] = None
    password: Optional[AttributeValue[str]] = None
    password_hashed: Optional[AttributeValue[str]] = None

class VTP(BaseModel):
    config: VTPAttributes = VTPAttributes()

class DHCPSnoopingAttributes(BaseModel):
    enabled: Optional[AttributeValue[bool]] = None
    vlans: Optional[Dict[str, Reference]] = {} # VLANs where DHCP snooping is enabled, key is VLAN ID, value is reference to VLAN
    trust_interfaces: Optional[Dict[str, Reference]] = {} # Interfaces that are trusted for DHCP snooping, key is interface name, value is reference to interface
    option82: Optional[AttributeValue[bool]] = None # Whether DHCP snooping option 82 is enabled

class DhcpRelayServerAttributes(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("address",)
    address: Optional[AttributeValue[str]] = None
    network_instance: Optional[AttributeValue[str]] = None
    interfaces: Optional[Dict[str, Reference]] = {}

class DhcpRelay(BaseModel):
    relay_servers: Optional[Dict[str, DhcpRelayServerAttributes]] = {}

class Dhcp(BaseModel): 
    snooping: Optional[DHCPSnoopingAttributes] = DHCPSnoopingAttributes()
    relay: Optional[DhcpRelay] = DhcpRelay()

class Services(BaseModel):
    name: Optional[AttributeValue[str]] = None
    http: Optional[AttributeValue[bool]] = None # for webgui access
    https: Optional[AttributeValue[bool]] = None # for webgui access

class NetflowFormat(str, Enum):
    IPFIX = "IPFIX"
    NETFLOW_V9 = "NetFlow v9"
    NETFLOW_V5 = "NetFlow v5"

class NetflowRecordIpv4Match(BaseModel):
    # Leaf-level ipv4 matches (Cisco style "match ipv4 <field>"). True = match this field, False = No. None = Ignore the field
    netflow_record: Optional[AttributeValue[str]] = None # Reference to parent record, used for easier access in config component
    dscp: Optional[AttributeValue[bool]] = None
    fragmentation: Optional[AttributeValue[bool]] = None
    header_length: Optional[AttributeValue[bool]] = None
    id: Optional[AttributeValue[bool]] = None
    length: Optional[AttributeValue[bool]] = None
    option: Optional[AttributeValue[bool]] = None
    precedence: Optional[AttributeValue[bool]] = None
    protocol: Optional[AttributeValue[bool]] = None
    section: Optional[AttributeValue[str]] = None
    tos: Optional[AttributeValue[bool]] = None
    total_length: Optional[AttributeValue[bool]] = None
    ttl: Optional[AttributeValue[bool]] = None
    version: Optional[AttributeValue[bool]] = None

class NetflowRecordAttributes(BaseModel): # Cisco flow record
    match_ipv4: Optional[NetflowRecordIpv4Match] = None
    #match_ipv4: Optional[Dict[str, NetflowRecordIpv4Match]] = {}
    application_name: Optional[AttributeValue[bool]] = None

    # Escape hatch for vendor-specific match knobs not yet modeled
    match_vendor_specific: Optional[AttributeValue[Dict[str, Any]]] = None # keeping a flexible option if needed. Not advertised to users.

    collect_timestamp_absolute_first: Optional[AttributeValue[bool]] = None
    collect_timestamp_absolute_last: Optional[AttributeValue[bool]] = None

class NetflowCollectorAttributes(BaseModel): # Cisco flow monitor
    netflow_record: Optional[AttributeValue[str]] = None # Reference to record, used for easier access in config component
    netflow_exporter: Optional[AttributeValue[str]] = None # Reference to exporter, used for easier access in config component
    cache_inactive: Optional[AttributeValue[int]] = None
    cache_active: Optional[AttributeValue[int]] = None
    interfaces: Optional[Dict[str, Reference]] = {} # allow for disabling netflow on specific interfaces

class NetflowExporterOptions(BaseModel):
    # Mostly timeouts atm
    interface_table_timeout: Optional[AttributeValue[int]] = None
    vrf_table_timeout: Optional[AttributeValue[int]] = None
    sampler_table: Optional[AttributeValue[bool]] = None
    application_table_timeout: Optional[AttributeValue[int]] = None
    application_attributes_timeout: Optional[AttributeValue[int]] = None
    netflow_exporter: Optional[AttributeValue[str]] = None # Reference to parent exporter, used for easier access in config component

class NetflowExporterAttributes(ContainerEntry, BaseModel): # Cisco flow exporter
    identity_fields: ClassVar[tuple[str, ...]] = ("address",)
    address: Optional[AttributeValue[str]] = None
    port: Optional[AttributeValue[int]] = None
    netflow_format: Optional[AttributeValue[str]] = None
    source_interface: Optional[Reference] = None
    network_instance: Optional[AttributeValue[str]] = None
    options: Optional[NetflowExporterOptions] = None

class NetflowGlobalConfigAttributes(BaseModel):
    enabled: Optional[AttributeValue[bool]] = None
    version: Optional[AttributeValue[int]] = None

class Netflow(BaseModel):
    config: Optional[NetflowGlobalConfigAttributes] = None
    records: Optional[Dict[str, NetflowRecordAttributes]] = {}
    exporters: Optional[Dict[str, NetflowExporterAttributes]] = {}
    collectors: Optional[Dict[str, NetflowCollectorAttributes]] = {}

class SflowMonitoringAttributes(BaseModel): ...

class SflowForwardingAttributes(BaseModel): ... # collector?

class SflowCollectorAttributes(ContainerEntry, BaseModel):
    identity_fields: ClassVar[tuple[str, ...]] = ("address",)
    address: Optional[AttributeValue[str]] = None # Destination IP address of the sFlow collector
    port: Optional[AttributeValue[int]] = None # UDP
    source_address: Optional[AttributeValue[str]] = None 
    network_instance: Optional[AttributeValue[str]] = None # VRF
    interfaces: Optional[Dict[str, Reference]] = {} # allow for disabling sflow on specific interfaces

class SfloGlobalConfigAttributes(BaseModel):
    enabled: Optional[AttributeValue[bool]] = None
    #version: Optional[AttributeValue[int]] = None
    dscp: Optional[AttributeValue[int]] = None # range: 0..63 
    sample_size: Optional[AttributeValue[int]] = None # Sets the maximum number of bytes to be copied from a sampled packet (content within one specific sample of a packet).
    polling_interval: Optional[AttributeValue[int]] = None # seconds
    ingress_sampling_rate: Optional[AttributeValue[int]] = None # sampling rate is 1/N packets. An implementation may implement the sampling rate as a statistical average, rather than a strict periodic sampling.
    egress_sampling_rate: Optional[AttributeValue[int]] = None  # sampling rate is 1/N packets. An implementation may implement the sampling rate as a statistical average, rather than a strict periodic sampling.

class Sflow(BaseModel):
    #enabled: Optional[AttributeValue[bool]] = None
    #version: Optional[AttributeValue[int]] = None
    #dscp: Optional[AttributeValue[int]] = None # range: 0..63 
    #sample_size: Optional[AttributeValue[int]] = None
    #polling_interval: Optional[AttributeValue[int]] = None
    #ingress_sampling_rate: Optional[AttributeValue[int]] = None
    #egress_sampling_rate: Optional[AttributeValue[int]] = None
    collector: Optional[Dict[str, SflowCollectorAttributes]] = {}
    # Similar structure to Netflow can be implemented here for sFlow-specific attributes

class Sampling(BaseModel):
    netflow: Optional[Netflow] = Netflow()
    #sflow: Optional[Sflow] = Sflow() # Sflow can be added in the future, similar structure to Netflow

class System(BaseModel):
    config: SystemConfig = SystemConfig()
    aaa: Optional[TripleA] = TripleA()
    logging: Optional[LoggingComponents] = LoggingComponents() # Trying to avoid using "Logging" or "logging" as names for anything due to conflicts with standard lib.
    ntp: Optional[Ntp] = Ntp()
    ssh: Optional[Ssh] = Ssh()
    snmp: Optional[Snmp] = Snmp()
    vtp: Optional[VTP] = VTP()
    dhcp: Optional[Dhcp] = Dhcp()
    services: Optional[Services] = Services()

# For different types of interfaces that are fine for response model:
InterfaceType = Union[
    EthernetCsmacdInterface,
    Ieee8023adLagInterface,
    L3IpvlanInterface,
    SoftwareLoopbackInterface,
    SubInterface,
    ManagementInterface,
]

class ComposedConfiguration(BaseModel):
    system: Optional[System] = System()
    acl: Optional[Acl] = Acl()
    lldp: Optional[LldpConfigAttributes] = LldpConfigAttributes()
    cdp: Optional[CdpConfigAttributes] = CdpConfigAttributes()
    lacp: Optional[Lacp] = Lacp()
    interfaces: Dict[str, InterfaceType] = {}
    network_instances: Dict[str, NetworkInstance] = {"global": NetworkInstance(name="global")}
    stp: Optional[SpanningTree] = SpanningTree()
    sampling: Optional[Sampling] = Sampling()

"""
GUIDELINES FOR COMPOSED CONFIGURATION:

1. All values must always be typed as AttributeValue.
2. Containers must always be defined as hierarchical pydantic types, no dicts as placeholders.
3. Component collections must use a typed container class (e.g. RemoteServers, VtyLines) with an inner
   Dict[str, BaseModel] field — the key identifies the component. Raw Dict fields are not allowed at
   the container level.
4. Default values for Optional containers are always an empty instance of the container class, e.g.
   Optional[RemoteServers] = RemoteServers(). Never default to None or a raw empty dict for collections.
"""