#! /usr/bin/python3
import pdb
import yaml
import uuid
import ipaddress
from infra.common.logging import logger
import apollo.test.utils.pdsctl as pdsctl
import apollo.config.utils as utils
import learn_pb2 as learn_pb2

# String representation of constructed UUIDs has certain portions in big
# endian format. This messes up with how pdsctl dsplays and expects UUID
# strings. Do the massaging to make UUID strings uniform
def PdsUuidToUuidStr(pdsuuid):
    return str(uuid.UUID(bytes=pdsuuid.Uuid))

class EpMacRuntimeInfo(object):
    def __init__(self, yaml_str):
        data = yaml.load(yaml_str, Loader=yaml.Loader)
        data = data['macentry']['entryauto']
        self.MacStr = utils.Int2MacStr(data['key']['macaddr'])
        self.SubnetUuidStr = utils.List2UuidStr(data['key']['subnetid'])
        self.VnicUuidStr = utils.List2UuidStr(data['vnicid'])
        self.State = data['state']
        self.Ttl = data['ttl']

class EpMac:
    def __init__(self, localmapping):
        self.MacStr = str(localmapping.VNIC.MACAddr)
        self.SubnetUuidStr = PdsUuidToUuidStr(localmapping.VNIC.SUBNET.UUID)
        self.VnicUuidStr = PdsUuidToUuidStr(localmapping.VNIC.UUID)
        self.CliCmd = "learn mac"
        self.ShowCliArgs = "--mode auto --mac %s --subnet %s" % \
                (self.MacStr, self.SubnetUuidStr)
        self.ClearCliArgs = "--mac %s --subnet %s" % \
                (self.MacStr, self.SubnetUuidStr)
        self.RuntimeInfo = None

    def ValidateRuntimeInfo(self):
        if not self.RuntimeInfo:
            return False
        # cannot compare generated Id's deterministically
        #if self.VnicUuidStr != self.RuntimeInfo.VnicUuidStr:
        #    return False
        if self.RuntimeInfo.State != learn_pb2.EP_STATE_CREATED:
            return False
        return True

class EpIpRuntimeInfo(object):
    def __init__(self, yaml_str):
        data = yaml.load(yaml_str, Loader=yaml.Loader)
        data = data['ipentry']['entryauto']
        self.IpStr = utils.Int2IPAddrStr(data['key']['ipaddr']['v4orv6']['v4addr'])
        self.VpcUuidStr = utils.List2UuidStr(data['key']['vpcid'])
        self.MacStr = utils.Int2MacStr(data['macinfo']['macaddr'])
        self.SubnetUuidStr = utils.List2UuidStr(data['macinfo']['subnetid'])
        self.State = data['state']
        self.Ttl = data['ttl']

class EpIp:
    def __init__(self, localmapping):
        self.IpStr = str(localmapping.IP)
        self.VpcUuidStr = PdsUuidToUuidStr(localmapping.VNIC.SUBNET.VPC.UUID)
        self.MacStr = str(localmapping.VNIC.MACAddr)
        self.SubnetUuidStr = PdsUuidToUuidStr(localmapping.VNIC.SUBNET.UUID)
        self.CliCmd = "learn ip"
        self.ShowCliArgs = "--mode auto --ip %s --vpc %s" % \
                (self.IpStr, self.VpcUuidStr)
        self.ClearCliArgs = "--ip %s --vpc %s" % \
                (self.IpStr, self.VpcUuidStr)
        self.RuntimeInfo = None

    def ValidateRuntimeInfo(self):
        if not self.RuntimeInfo:
            return False
        if self.MacStr != self.RuntimeInfo.MacStr:
            return False
        if self.SubnetUuidStr != self.RuntimeInfo.SubnetUuidStr:
            return False
        if self.RuntimeInfo.State != learn_pb2.EP_STATE_CREATED:
            return False
        return True

# common functions
def PopulateRuntimeInfo(ep_mac_ip):
    ep_mac_ip.RuntimeInfo = None
    status_ok, output = pdsctl.ExecutePdsctlShowCommand(ep_mac_ip.CliCmd, \
                                                        ep_mac_ip.ShowCliArgs, True)
    if not status_ok:
        logger.error(" - ERROR: pdstcl show failed for cmd %s %s" % \
                     (ep_mac_ip.CliCmd, ep_mac_ip.ShowCliArgs))
        return False
    if 'API_STATUS_NOT_FOUND' in output:
        logger.info(" - INFO: entry not found %s" % ep_mac_ip.ShowCliArgs)
        return False
    if isinstance(ep_mac_ip, EpMac):
        ep_mac_ip.RuntimeInfo = EpMacRuntimeInfo(output.split('---')[0])
    else:
        ep_mac_ip.RuntimeInfo = EpIpRuntimeInfo(output.split('---')[0])
    return True

def ExistsOnDevice(ep_mac_ip):
    if (PopulateRuntimeInfo(ep_mac_ip)):
        return ep_mac_ip.ValidateRuntimeInfo()
    else:
        return False

def VerifyIPAgeRefreshed(tc, ep_ip):
    if not PopulateRuntimeInfo(ep_ip):
        return False
    age = tc.config.devicecfg.GetLearnAgeTimeout()
    # allow 10 second worst case test delay
    return ep_ip.RuntimeInfo.Ttl > (age - 10)

def ClearOnDevice(ep_mac_ip):
    status_ok, output = pdsctl.ExecutePdsctlClearCommand(ep_mac_ip.CliCmd, \
                                                         ep_mac_ip.ClearCliArgs)
    if not status_ok:
        logger.error(" - ERROR: pdstcl clear failed for cmd %s %s" % \
                     (ep_mac_ip.CliCmd, ep_mac_ip.ClearCliArgs))
        return False
    return True

def GetLearnStatistics():
    status_ok, output = pdsctl.ExecutePdsctlShowCommand("learn statistics")
    if not status_ok:
        logger.error(" - ERROR: pdstcl show failed for learn statistics")
        return None
    yaml_str = output.split('---')[0]
    data = yaml.load(yaml_str, Loader=yaml.Loader)
    if not isinstance(data, dict):
        return None
    return data

def ClearAllLearntMacs():
    status_ok, output = pdsctl.ExecutePdsctlClearCommand("learn mac")
    if not status_ok:
        logger.error(" - ERROR: pdstcl clearn failed for learn mac")
    return status_ok and "succeeded" in output

def ClearAllLearntIps():
    status_ok, output = pdsctl.ExecutePdsctlClearCommand("learn ip")
    if not status_ok:
        logger.error(" - ERROR: pdstcl clearn failed for learn ip")
    return status_ok and "succeeded" in output

def ClearLearnStatistics():
    status_ok, output = pdsctl.ExecutePdsctlClearCommand("learn statistics")
    if not status_ok:
        logger.error(" - ERROR: pdstcl clearn failed for learn statistics")
    return status_ok and "succeeded" in output
