import whatap.net as net
import whatap.agent.conf.configure as config
import whatap.agent.secure.security_master as secure
import threading, time, socket
from whatap.io.data_inputx import DataInputX
from whatap.io.data_outputx import DataOutputX
import whatap.util.logging_util as logging_util
from whatap.util.date_util import DateUtil as dateutil
from datetime import datetime, timedelta
import platform, subprocess

READ_MAX = 8 * 1024 * 1024
moduleLoaded = datetime.now()

class TcpSession :

    def __init__(self):
        self.client = None
        self.in_ = None
        self.dest = 0
        self.hosts = None
        self.started = 0
        self.lastReceived = datetime(year=1970, month=1, day=1)

    def isHostChanged(self):
        conf = config.GetConfig()
        hosts = conf.WhatapHost.split('/')
        if hosts is not None and len(hosts) > 0 :
            ismatch = False
            if self.hosts is not None and len(self.hosts) > 0 :
                for h1 in hosts :
                    for h2 in self.hosts :
                        if h1 == h2 :
                            ismatch = True

            if not ismatch :
                return True

        return False

    def open(self):
        try:
            return self.__open()
        except Exception, e:
            logging_util.error(str(e))
            self.Close()
            return False

    def __open(self):
        if self.isOpen() :
            return True

        pcode = secure.GetSecurityMaster().pcode
        if pcode == 0 :
            logging_util.error("[TcpSession] Pcode Error")
            return False

        conf = config.GetConfig()
        hosts = conf.WhatapHost.split('/')
        if hosts is None or len(hosts) == 0 :
            logging_util.error("[TcpSession] whatap host config Error")
            self.Close()
            return False
        if self.dest >= len(hosts) :
            self.dest = 0
        port = int(conf.WhatapPort)
        client = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
        client.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
        try:
            client.connect((hosts[self.dest], port))
        except Exception, e:
            logging_util.error("[TcpSession] tcp connect exception : ", e)
            self.dest += 1
            self.Close()
            return False

        self.hosts = hosts
        myIP = client.getsockname()[0]
        if myIP == 0 and 'hp-ux' == platform.system().lower(): # HPUX pa not support getsockname
            command = "netstat -an | grep 6600 | grep ESTABLISHED | head -n 1 | awk '{print $4}' | awk -F'.' '{print $1\".\"$2\".\"$3\".\"$4}'"

            try:
                netstat = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
                myIP = netstat.stdout.read().strip('\n')
            except subprocess.CalledProcessError as e:
                logging_util.error("netstat error", e)
                
        secure.GetSecurityMaster().DecideAgentOnameOid(myIP)


        keyReset = self.keyReset()

        client.sendall(keyReset)
        self.in_ = DataInputX(client)
        data = self.readKeyReset(self.in_)
        if data is None or len(data) < 1 :
            logging_util.error("[TcpSession] key read error")
            return False
        secure.UpdateNetCypherKey(data)
        self.client = client
        self.started = datetime.now()

        return True

    def isOpen(self):
        return self.client is not None

    def readKeyReset(self, in_):
        r = in_.readByte()
        r = in_.readByte()
        pcode = in_.readLong()
        oid = in_.readInt()
        r = in_.readInt()
        data = in_.readIntBytes(1024)
        if data is None:
            logging_util.error("[TcpSession] key reset error")
            return None

        secu = secure.GetSecurityMaster()

        #print 'readKeyReset ', pcode, secu.pcode, 'oid', oid, secu.oid
        if pcode != secu.pcode or oid != secu.oid :
            logging_util.error("[TcpSession] pcode, oid error")
            return None
        else:
            return data

    def keyReset(self):
        conf = config.GetConfig()
        secu = secure.GetSecurityMaster()
        secu.WaitForInit()
        dout = DataOutputX()

        msg = dout.writeText("hello").writeText(secu.oname).writeInt(secu.ip).toByteArray()
        if conf.CypherLevel > 0 :
            msg = secu.cypher.Encrypt(msg)
        dout = DataOutputX()
        dout.writeByte(net.NETSRC_AGENT_JAVA_EMBED)

        trkey = 0
        if conf.CypherLevel == 128 :
            dout.writeByte(net.NET_KEY_RESET)
        else:
            dout.writeByte(net.NET_KEY_EXTENSION)

            if conf.CypherLevel == 0 :
                trkey = 0
            else :
                b0 = 1
                b1 = conf.CypherLevel / 8
                trkey = DataOutputX.toInt([b0, b1, 0, 0], 0)

        #print 'keyReset', secu.pcode, secu.oid
        dout.writeLong(secu.pcode)
        dout.writeInt(secu.oid)
        dout.writeInt(trkey)
        dout.writeIntBytes(msg)

        return dout.toByteArray()

    def Send(self, code , b ):
        try:
            return self.__send(code, b)
        except:
            self.Close()

    def __send(self, code, b):
        if self.client is None :
            return False

        secu = secure.GetSecurityMaster()
        secuSession = secure.GetSecuritySession()
        out = DataOutputX()
        out.writeByte(net.NETSRC_AGENT_JAVA_EMBED)
        out.writeByte(code)
        out.writeLong(secu.pcode)
        out.writeInt(secu.oid)
        out.writeInt(secuSession.transferKey)
        if hasattr(b, 'getvalue'):
            out.writeIntBytes(b.getvalue())
        else:
            out.writeIntBytes(b)
        sendbuf = out.toByteArray()
        self.client.sendall(sendbuf)
        return True

    def Close(self):
        if self.client is not None:
            self.client.close()
        self.client = None

    def Read(self):
        if not self.isOpen():
            return empty

        read_start = datetime.now()
        step = 'src'
        code = None
        pcode = None
        oid = None
        transfer_key = None
        data_len = -1
        try:
            self.in_.readByte()
            step = 'code'
            code = self.in_.readByte()
            step = 'pcode'
            pcode = self.in_.readLong()
            step = 'oid'
            oid = self.in_.readInt()
            step = 'transferKey'
            transfer_key = self.in_.readInt()
            step = 'data'
            data = self.in_.readIntBytes(READ_MAX)
            data_len = -1 if data is None else len(data)
        except Exception, e:
            elapsed_ms = int((datetime.now() - read_start).total_seconds() * 1000)
            logging_util.error(
                "[TcpSession] Read failed step=%s elapsed=%dms code=%s pcode=%s oid=%s trkey=%s err=%s" %
                (step, elapsed_ms, code, pcode, oid, transfer_key, e))
            raise

        if data is None :
            logging_util.error("[TcpSession] Read got None data code=%s" % code)
            return empty

        secu = secure.GetSecurityMaster()
        if pcode != secu.pcode or oid != secu.oid :
            logging_util.error(
                "[TcpSession] Read pcode/oid mismatch recv(pcode=%s,oid=%s) expect(pcode=%s,oid=%s) code=%s len=%d" %
                (pcode, oid, secu.pcode, secu.oid, code, data_len))
            return empty

        self.lastReceived = datetime.now()
        return TcpReturn(code= code, data= data, transfer_key= transfer_key)

class TcpReturn:
    def __init__(self, code=None, data=None, transfer_key = None):
        self.code = code
        self.data = data
        self.transferKey = transfer_key

def connect(s):
    while True:
        while not s.open():
            logging_util.error("[TcpSession] Session Open Error")
            time.sleep(3)
        now = dateutil.now()
        if not s.Send(net.NET_TIME_SYNC, DataOutputX.toBytesLong(now)):
            logging_util.error("[TcpSession] TimeSync Error")
            s.Close()
            time.sleep(3)
            continue
        break

def WaitForConnection():
    while True:
        tcp = GetTcpSession()
        if tcp.isOpen() :
            break

        time.sleep(1)

empty = TcpReturn()
sessionLock = threading.Lock()
session = None
def GetTcpSession():
    global session
    sessionLock.acquire()
    try:
        if session and session.isOpen() and not session.isHostChanged():
            return session
        if session :
            logging_util.info("[TcpSession] Tcp Session Reconnect")
            if not session.isOpen():
                logging_util.error("[TcpSession] Session Closed")
            if session.isHostChanged():
                logging_util.info("[TcpSession] Whatap Config Host Option Changed")
            session.Close()
        session = TcpSession()
        connect(session)
        logging_util.info("[TcpSession] Connection : ", session.hosts)
        return session
    finally:
        sessionLock.release()

def isSessionOK():
    ret = True

    now = datetime.now()
    if now-moduleLoaded > timedelta(minutes=config.GetConfig().HealthCheckCoolTime) :
        if not session:
            return False

        ret = session.isOpen()
        ret = ret and (session.started is not 0)

        if session.started and now-session.started > timedelta(minutes=config.GetConfig().HealthCheckCoolTime):
            ret = ret and (now-session.lastReceived < timedelta(seconds=config.GetConfig().TimeSyncInterval*10))

    return ret

def net_test(connect_timeout=5, response_timeout=3):
    secu = secure.GetSecurityMaster()
    secu.WaitForInitFor(30)

    if secu.pcode == 0:
        return "invalid license error"

    conf = config.GetConfig()
    hosts = conf.WhatapHost.split('/')
    if hosts is None or len(hosts) == 0:
        return "whatap host config error"

    port = int(conf.WhatapPort)
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

    connected = False
    for host in hosts:
        try:
            client.connect((host.strip(), port))
            connected = True
            break
        except Exception, e:
            logging_util.error("[NetTest] connect failed to %s:%s - %s" % (host, port, e))
            continue

    if not connected:
        client.close()
        return "connection failed - host: %s, port: %s" % (conf.WhatapHost, port)

    test_session = TcpSession()
    test_session.client = client
    try:
        test_session.hosts = hosts
        myIP = client.getsockname()[0]
        secu.DecideAgentOnameOid(myIP)

        keyReset = test_session.keyReset()
        client.sendall(keyReset)
        test_session.in_ = DataInputX(client)
        data = test_session.readKeyReset(test_session.in_)
        if data is None or len(data) < 1:
            return "key reset failed"
        secure.UpdateNetCypherKey(data)

        now = dateutil.now()
        if not test_session.Send(net.NET_TIME_SYNC, DataOutputX.toBytesLong(now)):
            return "send failed - host: %s, port: %s" % (conf.WhatapHost, port)

        is_response_received = [False]

        def read_response():
            try:
                client.settimeout(response_timeout)
                out = test_session.Read()
                if out and out.code == net.NET_TIME_SYNC:
                    is_response_received[0] = True
            except Exception, e:
                logging_util.error("[NetTest] read response error: %s" % e)

        reader = threading.Thread(target=read_response)
        reader.setDaemon(True)
        reader.start()
        reader.join(response_timeout + 1)

        if not is_response_received[0]:
            return "received no response - host: %s, port: %s" % (conf.WhatapHost, port)

        return None

    except Exception, e:
        return "net test error: %s" % str(e)
    finally:
        test_session.Close()
