import errno, os, os.path, pwd, threading
from whatap.agent.conf.configuration import Configuration
from whatap.agent.conf.agentless_item import AgentlessItem
from whatap.util.hash_util import HashUtil as hashutil
import whatap.util.logging_util as logging_util

TCPCheck       = "tcp.check."
LogFileWatch   = "log.file."
FileCheckWatch = "log.filecheck."
EventLogWatch  = "log.event."
Agentless     = "agentless."
Tag           = "tag.agent"
ScriptPath    = "ext"
FileCount     = "file.count."
ProcGroup     = "procgroup_"

WHATAP_HOME='WHATAP_HOME'
entrypoint_path = ''

def GetConfigPath():
    return os.path.join(os.environ.get('WHATAP_HOME','/usr/whatap/infra/conf'), os.environ.get('WHATAP_CONFIG', 'whatap.conf'))

def GetTempConfigPath():
    return os.path.join(os.environ.get('WHATAP_HOME','/usr/whatap/infra/conf'), os.environ.get('WHATAP_CONFIG', 'whatap.conf.temp'))

def try_cast_value(value):
    if value.lower() in ('true', 'false'):
        return value.lower() == 'true'

    try:
        return int(value)
    except ValueError:
        pass

    try:
        return float(value)
    except ValueError:
        pass

    return value

class Configure(object):
    dev = False
    net_udp_port = 6600
    last_modified = 0

    #@classmethod
    def init(self):
        for key, value in Configuration.items():
            if isinstance(value, tuple):
                key, value = value
            setattr(self, key, value)
        
        return self.load()
    
    #@classmethod
    def load(self):
        try:
            #print "config path:",os.path.join(os.environ[home], os.environ.get('WHATAP_CONFIG','whatap.conf'))
            self.setProperty('WhatapHome', os.environ.get('WHATAP_HOME', '/usr/whtap/infra/conf'))
            f= open(GetConfigPath(), 'r')
            for line in f.readlines():
                line_strip = line.strip()
                if not line_strip or line_strip.startswith('#'):
                    continue
                try:
                    key, value = line.split('=')
                    key = key.strip()
                    value = value.strip()
                    if key in Configuration and isinstance(Configuration.get(key), tuple):
                        key = Configuration.get(key)[0]
                    self.setProperty(key, value)
                    self.applyAgentless()
                except Exception, e:
                    logging_util.debugStack(e)
                    # import traceback, sys
                    # print traceback.format_exc(sys.exc_info())
                    continue
            f.close()
            self.last_modified = os.path.getmtime(GetConfigPath())
        except Exception, e:
            logging_util.debugStack(e)

            return False
        else:
            return True

    #@classmethod
    def getProperty(self, key, value=None):
        if hasattr(self, key):
            return getattr(self, key)
        else:
            return value
    
    #@classmethod
    def setProperty(self, name, value):
        value = try_cast_value(value)
        if hasattr(self, name):
            if isinstance(getattr(self, name), bool) and not isinstance(value, bool):
                value = False
        
        setattr(self, name, value)
    
    def getStringSet(self, key, default_value, deli):
        l = list()
        value = self.getProperty(key, default_value)
        if value:
            for v in value.split(deli):
                l.append(v)
        return l

    #@classmethod
    def searchKey(self, keyPrefix):
        keyValues = {}

        for key in self.__dict__.keys():
            if key.startswith(keyPrefix):
                keyValues[key] = self.getProperty(key)

        return keyValues

    #@classmethod
    def getItemDict(self):
        m= {name: attr for name, attr in self.__dict__.items()
         if not name.startswith("__")
         and not callable(attr)
         and not type(attr) is staticmethod
         and not type(attr) is classmethod}

        return m

    def populateAllKeyValues(self, h2):
        f = open(GetConfigPath(), 'r')
        for line in f.readlines():
            line_strip = line.strip()
            if not line_strip or line_strip.startswith('#'):
                continue
            try:
                key, value = line.split('=')
                key = key.strip()
                value = value.strip()
                h2(key, value)
            except Exception, e:
                continue
        f.close()

    def applyAgentless(self):
        if self.AgentlessInterval < 5000:
            self.AgentlessInterval = 5000

        m = self.searchKey(Agentless)
        # logging_util.debug("applyAgentless m:", m)
        pw_lookup = {x.pw_name: x for x in pwd.getpwall()}
        agentlessItems = {}
        for k, v in m.items():
            item = v.split(",")
            # logging_util.debug("applyAgentless step 0.1 ", len(item), entrypoint_path, ScriptPath, item[1])
            if len(item) == 3:
                fullpath = os.path.join(entrypoint_path, ScriptPath, item[1])
            else:
                continue
            # logging_util.debug("applyAgentless step 2", item[0], fullpath, item[2])
            if validateScript(item[0], item[1], int(item[2])):
                # logging_util.debug("applyAgentless step 3")
                username = item[0].strip()
                # logging_util.debug("applyAgentless step 4")
                if username in pw_lookup:
                    pw = pw_lookup[username]
                    # logging_util.debug("applyAgentless step 5")
                    agentlessItems[k] = AgentlessItem(user=username, script=item[1], uid=pw.pw_uid, gid=pw.pw_gid, hash = item[2])

        self.AgentlessItems = agentlessItems

def generateHash(user, script):
    return hashutil.hashFromString('{}/{}'.format(user, script))

def validateScript(user, script, hashstr):

    validated = hashstr == generateHash(user, script)
    # logging_util.debug("validateScript:", user, script, hashstr, generateHash(user, script), validated)
    return validated

conf= None
configLock = threading.Lock()
def GetConfig():
    configLock.acquire()
    global conf
    if not conf:
        confThisTime = Configure()
        confThisTime.init()
        conf = confThisTime
    configLock.release()

    return conf

def reload():
    global conf
    configpath = GetConfigPath()
    if os.path.exists(configpath):
        last_modified = os.path.getmtime(configpath)
        if last_modified > conf.last_modified:
            newconfig = Configure()
            newconfig.init()
            conf = newconfig

def readConfigSimple(filepath):
    simple= {}
    f= open(filepath)
    try:
        for l in f.readlines():
            if l and l.find('=') >=0:
                k = l[:l.find('=')]
                v = l[l.find('=')+1:]
                simple[k] =v
        return simple
    except Exception as e:
        logging_util.error("Fail read file : ", filepath)
        return None
    finally:
        f.close()

def clearKeys(m, prefix):
    keys = []
    for k in m.keys():
        if k.startswith(prefix):
            keys.append(k)
    for k in keys:
        del m[k]

def ClearProcgroupKeys():
    configpath = GetConfigPath()
    tempconfigpath = GetTempConfigPath()
    propmap = readConfigSimple(configpath)
    if not propmap:
        return
    if not any(k.startswith(ProcGroup) for k in propmap.keys()):
        return
    clearKeys(propmap, ProcGroup)
    isException = False
    with open(tempconfigpath, 'w') as f:
        orderedkeys = ["license", "whatap.server.host", "createdtime"]
        for k in orderedkeys:
            if k not in propmap:
                continue
            v = propmap[k]
            line = "%s=%s\n" % (k, v)
            f.write(line)
            del propmap[k]
        keys = propmap.keys()
        keys.sort()
        for k in keys:
            v = propmap[k]
            if not v or (isinstance(v, (str, unicode)) and not v.strip()):
                continue
            if isinstance(k, str):
                k = k.decode("utf-8", "replace")
            if isinstance(v, str):
                v = v.decode("utf-8", "replace")
            line = u"%s=%s\n" % (k, v)
            line = line.encode("utf-8")
            try:
                f.write(line)
            except Exception, e:
                isException = True
                logging_util.error("configuration error: {}".format(e))
                break
        f.flush()
        os.fsync(f.fileno())
    if isException:
        try:
            os.remove(tempconfigpath)
        except OSError:
            pass
    else:
        safeReplace(tempconfigpath, configpath)
        reload()

def safeReplace(source, target):
    try:
        os.rename(source, target)

        dir_name = os.path.dirname(target)
        flags = getattr(os, 'O_DIRECTORY', 0) | os.O_RDONLY
        dir_fd = os.open(dir_name, flags)
        try:
            try:
                os.fsync(dir_fd)
            except OSError as e:
                if e.errno not in (errno.EBADF, errno.EINVAL, errno.EPERM):
                    raise
                logging_util.debug("dir fysnc unsupported %s : %s", dir_name, e)
        finally:
            os.close(dir_fd)
    except Exception as e:
        logging_util.error("Failed to replace file: {} -> {}. Error: {}".format(source, target, e))
        try:
            os.remove(source)
        except OSError:
            pass
        return False
    return True

def SetValues(keyValues):
    #logging_util.debug( 'control_handler.runControl step -1')
    configpath = GetConfigPath()
    tempconfigpath = GetTempConfigPath()
    isException = False

    propmap = readConfigSimple(configpath)
    if not propmap :
        return
    #logging_util.debug('control_handler.runControl step -2')
    for key in keyValues.keys():
        if key.startswith(TCPCheck):
            clearKeys(propmap, TCPCheck)
        elif key.startswith(LogFileWatch) or key.startswith(EventLogWatch):
            clearKeys(propmap, LogFileWatch)
            clearKeys(propmap, EventLogWatch)
        elif key.startswith(FileCheckWatch):
            clearKeys(propmap, FileCheckWatch)
        elif key.startswith(Agentless):
            clearKeys(propmap, Agentless)

    for k, v in keyValues.items():
        if k.startswith(ProcGroup) and (not v or (isinstance(v, (str, unicode)) and not v.strip())):
            if k in propmap:
                del propmap[k]
        else:
            propmap[k] = v
    #logging_util.debug('control_handler.runControl step -3')

    with open(tempconfigpath, 'w') as f:
        orderedkeys= ["license", "whatap.server.host", "createdtime"]
        for k in orderedkeys:
            v= propmap[k]
            line = "%s=%s\n" %(k, v)
            f.write(line)
            del propmap[k]
        keys = propmap.keys()
        keys.sort()
        for k in keys:
            v= propmap[k]
            if not v or (isinstance(v, (str, unicode)) and not v.strip()):
                continue
            if isinstance(k, str):
                k = k.decode("utf-8", "replace")
            if isinstance(v, str):
                v = v.decode("utf-8", "replace")
            line = u"%s=%s\n" %(k, v)
            line = line.encode("utf-8")
            try:
                f.write(line)
            except Exception, e:
                isException = True
                logging_util.error("configuration error: {}".format(e))
                break
    
        f.flush()
        os.fsync(f.fileno())

    reloadCheck = True
    if isException:
        os.remove(tempconfigpath)
        reloadCheck = False
    else:
        reloadCheck = safeReplace(tempconfigpath, configpath)

    if reloadCheck:
        reload()
    #GetConfig().init()
    #logging_util.debug('control_handler.runControl step -4')

def updateScripts(user):
    searchDir = os.path.join(entrypoint_path, ScriptPath)
    # logging_util.debug("configure updateScripts searchDir:", searchDir)

    if not os.path.exists(searchDir):
        return
    m = {}
    # logging_util.debug("configure updateScripts step 2:")
    for (path, dir, files) in os.walk(searchDir):
        # logging_util.debug("configure updateScripts step 3:", (path, dir, files))
        for fname in files:
            fullpath = os.path.join(path, fname)
            # logging_util.debug("configure updateScripts step 4:", fullpath, os.path.isfile(fullpath ),
            #                    os.access(fullpath , os.X_OK))

            if os.path.isfile(fullpath) and os.access(fullpath, os.X_OK):
                script = fullpath[len(entrypoint_path) + 1 + len(ScriptPath):]
                script = script.lstrip('/')
                hashstr = generateHash(user, script)
                key = "{}{}".format( Agentless, hashstr)
                val = "{},{},{}".format( user, script, hashstr)
                m[key] = val

    if m:
        # logging_util.debug("configure updateScripts",m)
        SetValues(m)
