# (c) cavaliba.com - ipam - common.py
# IPV4 only


import ipaddress

from app_data.data import Instance
from app_data.models import DataEAV


def is_ipv4(data):
    try:
        ipaddress.ip_address(data)
        return True
    except:
        return False


def is_ipv4_subnet(data):
    try:
        ipaddress.ip_network(data)
        return True
    except:
        return False

def align_to_subnet(ipmask_str):
    ''' 
    align ip/mask to subnet boundaries
    IN:  string   10.1.1.1/24
    OUT: string   10.1.1.0/24

    Example usage
    result = align_to_subnet("10.1.1.1/24")
    print(result)  # Output: 10.1.1.0/24
    '''
    iface = ipaddress.ip_interface(ipmask_str)
    network_str = f"{iface.network.network_address}/{iface.network.prefixlen}"
    return network_str



# --------------------------------------------------------------------
# IPAM - IP
# --------------------------------------------------------------------


class IpamIP:

    def __init__(self, data=None, set_subnet=False):

        # data is 'A.B.C.D' or 'A.B.C.D/E' or ipaddress.ip_adress object

        self.ip = None        # string / no mask
        self.ipobj = None     # ipaddress object

        self.version = None

        self.db_objects = None    # list of DB object related  to this IP

        self.is_private = None
        #self.is_rfc1918 = None
        self.is_global = None
        self.is_public = None      # == is_global
        self.is_multicast = None
        self.is_unspecified = None 
        self.is_reserved = None
        self.is_loopback = None

        #self.fqdn = None

        self.subnet = None          # first subnet : IpamSubnet objects
        self.parent_subnet = []     # hierarchy (without first) ; small to largest : IpamSubnet objects
        self.child_subnet = []      #  first rank


        if not data:
            return
        
        # check valid IP
        try:
            ipobj = ipaddress.ip_address(data)
        except:
            return

        self.ipobj = ipobj
        self.ip = str(ipobj)
        self.version = ipobj.version
        self.is_private = ipobj.is_private 
        self.is_multicast = ipobj.is_multicast
        self.is_global = ipobj.is_global
        self.is_public = ipobj.is_global
        self.is_unspecified = ipobj.is_unspecified
        self.is_reserved = ipobj.is_reserved
        self.is_loopback = ipobj.is_loopback


        if set_subnet:
            self.set_subnet()



    def __str__(self):
        return str(self.ip)


    def print(self):

        print("IP: ",self)
        print(f"- private: {self.is_private}")
        print(f"- subnet : {self.subnet}")
        print("Parent subnets:")
        for i in self.parent_subnet:
            i.print()
        print("Child subnets:")
        for i in self.child_subnet:
            i.print()


    def set_subnet(self):

        try:
            subnet = ipaddress.ip_network(self.ipobj, strict=False)
        except:
            return

        #  parent subnets
        # --------------
        done = False
        current = subnet
        while not done:
            
            # get subnet IpamSubnet 
            subobj = IpamSubnet(current)

            # if exists in db (has instance), add to list
            if subobj.instance:
                # first ?
                if not self.subnet:
                    self.subnet = subobj
                else:
                    # hierarchy
                    self.parent_subnet.append(subobj)


            # climb one step
            if current.prefixlen > 0:
                current = current.supernet(prefixlen_diff=1)
            else:
                done = True


        # child subnets
        #  -------------
        if self.subnet:
            self.child_subnet = self.recurse_child(self.subnet.subobj, maxdepth=3)



    def recurse_child(self, subnet, maxdepth):
        
        reply = []

        if subnet.prefixlen == 32:
            return reply

        if maxdepth <=0:
            return reply

        # first level ; may not exist, hence recurse
        subnets = subnet.subnets(prefixlen_diff=1)
        for s in subnets:
            subobj = IpamSubnet(s)
            if subobj.instance:
                reply.append(subobj)
            else:
                r2 = self.recurse_child(s, maxdepth-1)
                if len(r2) > 0:
                    reply += r2
        
        return reply






    def get_related_objects(self):
        # query DataEAV
        # - format="ipv4"
        # - value = self.ip
        return DataEAV.objects.filter(
            format = 'ipv4',
            value = self.ip,
        )

        # reply = []
        # for eav in eavobjects:
        #     item = {}
        #     item['iid'] = eav.iid
        #     item['classname'] = eav.classname
        #     item['keyname'] = eav.keyname
        #     item['displayname'] = eav.displayname



# --------------------------------------------------------------------
# IPAM - Subnet
# --------------------------------------------------------------------

class IpamSubnet:


    def __init__(self, data=None, set_subnet=False):

        self.subnet= None        # string repr 'A.B.C.D/E'
        self.subobj = None       # ipaddress object
    
        self.instance = None     # Schema Instance() ; if None, doesn't exist in DB
        self.description = ''

        self.netmask = None      # from ipaddress
        self.prefixlen = None
        self.broadcast = None
        self.size = None
        self.first = None 
        self.last = None

        self.gateway = None      # from DB / Instance()
        self.vlan = None
        self.site = None
        # dhcp, nac, ...


        self.parent_subnet = []     # hierarchy (without first) ; small to largest : IpamSubnet objects
        self.child_subnet = []      #  first rank


        try:
            subnet = ipaddress.ip_network(data)
        except:
            return

        self.subnet = str(subnet)
        self.subobj = subnet

        self.netmask = subnet.netmask
        self.prefixlen = subnet.prefixlen
        self.broadcast = subnet.broadcast_address
        self.size = subnet.num_addresses
        self.first = str(subnet[0])
        self.last = str(subnet[-1])

        keyname = str(subnet)
        instance = Instance.from_keyname(classname='ipam_subnet', keyname=keyname)
        if not instance:
            return
        if instance.is_bound:
            self.instance = instance
            try:
                self.description = instance.fields['description'].get_first_value()
            except:
                pass
        else:
            self.instance = None
            return

        if set_subnet:
            self.set_subnet()


    def __str__(self):
        return str(self.subnet)


    def print(self):
        print("SUBNET: ",self)


    def set_subnet(self):

        
        #  parent subnets
        # --------------
        done = True
        if self.subobj.prefixlen > 0:
            current = self.subobj.supernet(prefixlen_diff=1)
            done = False

        while not done:

            # get subnet IpamSubnet 
            subobj = IpamSubnet(current)

            # if exists in db (has instance), add to list
            if subobj.instance:
                self.parent_subnet.append(subobj)

            # climb one step
            if current.prefixlen > 0:
                current = current.supernet(prefixlen_diff=1)
            else:
                done = True


        # child subnets
        #  -------------
        self.child_subnet = self.recurse_child(self.subobj, maxdepth=3)



    def recurse_child(self, subobj, maxdepth):
        
        reply = []

        if subobj.prefixlen == 32:
            return reply

        if maxdepth <=0:
            return reply

        # first level ; may not exist, hence recurse
        subnets = subobj.subnets(prefixlen_diff=1)
        for s in subnets:
            subobj = IpamSubnet(s)
            if subobj.instance:
                reply.append(subobj)
            else:
                r2 = self.recurse_child(s, maxdepth-1)
                if len(r2) > 0:
                    reply += r2
        
        return reply




class IpamVLAN:
    def __init__(self, data=None):
        pass