#!/anaconda3/envs/xxx/bin python3.7
# -*- coding: utf-8 -*-
# ---
# @Software: PyCharm
# @File: main.py
# @Author: oupeng
# @Time: 9月 23, 2021
# ---
import json
import time
import os
from fabric import Connection,SerialGroup
from loguru import logger
from requests import session
from copy import deepcopy
import csv
import pytest
import traceback

from jinja2 import Environment, FileSystemLoader


# class setEnvs():
#     f=open('.env','r')
#     for one in f.readlines():
#         k,v=one.split('=')
#         os.environ[k]=v


class RunnerResult(object):
    '''
    this just for generate result for the remote executor
    '''
    def __init__(self):
        
        self.testResTemp={"testTitle":"","chaosCommand":"","chaosId":"","checkCommand":"","expectResponse":"","realResponse":"","checkStatus":""}
        self.testResList=[]
    
    def clearTemp(self):
        self.testResTemp={"testTitle":"","chaosCommand":"","chaosId":"","checkCommand":"","expectResponse":"","realResponse":"","checkStatus":""}
    
    def clearResList(self):
        self.testResList=[]

    def printResList(self):
        for one in self.testResList:
            print('####################')
            for k,v in one.items():
                print(f"{k}-->{v}")
    
    def printResultTemp(self):
        for k,v in self.testResTemp.items():
                print(f"{k}-->{v}")

    def generaterReport(self):
        pass


class RemoteRunner(object):
    '''
    run command in remote host
    '''
    def __init__(self, host, port, user, passwd):
        '''

        :param host:
        :param port:
        :param user:
        :param passwd:
        result will be store in instance res variables
        '''
        self.host = host
        self.port = port
        self.user = user
        self.passwd = {"password": passwd}
        self.conn = Connection(host=self.host, user=self.user, port=self.port, connect_kwargs=self.passwd)
        self.res=""
        self.uid=None
        self.containerList=[]
        self.commandList=None
        self.testResult=RunnerResult()
        


    def __enter__(self):
        return self
        

    def __exit__(self,exc_type,exc_val,exc_tb):
        self.conn.close()

    def upload_file(self, localFilePath, remoteFilePath):
        '''
        :param localFilePath:
        :param remoteFilePath:
        :return:
        '''
        # logger.info(f"localFilePath: {localFilePath}")
        # logger.info(f"remoteFilePath: {remoteFilePath}")
        logger.info(f"localFilePath: {localFilePath}, remoteFilePath: {remoteFilePath}")
        self.conn.put(local=localFilePath, remote=remoteFilePath)
        return self

    def download_file(self, localFilePath, remoteFilePath):
        '''

        :param localFilePath:
        :param remoteFilePath:
        :return:
        '''
        logger.info(f"localFilePath: {localFilePath}, remoteFilePath: {remoteFilePath}")
        # logger.info(f"remoteFilePath: {remoteFilePath}")
        self.conn.get(remote=remoteFilePath, local=localFilePath)
        return self


    def get_chaos_id(self):
        if "success" in self.res.stdout.strip():
            id=json.loads(self.res.stdout.strip())['result']
            print(f"chaos id: < {id} >")
            self.uid=id
            
        return self

    def exec_cmd(self, cmd):
        '''
        :param cmd: command in running on the host
        :return:
        '''
        try:
            logger.info(f"exec cmd: < {cmd} >")
            self.commandList=cmd
            res = self.conn.run(cmd)
            # logger.info(f'cmd stdout is: {res.stdout.strip()}')
            # logger.error(f'cmd stdout is: {res.stderr.strip()}')
            self.res=res
            self.get_chaos_id()
            return self
        except Exception as e:
            logger.warning(e)

    def exec_on_multi(self,*hosts,cmd):
        '''

        :param hosts: host list,contain lots of host
        :param cmd: command in running on the host
        :return:
        '''
        logger.info(f"hosts: <{hosts}>")
        logger.info(f"multi cmd: <{cmd}>")
        self.commandList=cmd
        res=SerialGroup(hosts).run(cmd)
        # logger.info(f'cmd stdout is: {res.stdout.strip()}')
        # logger.error(f'cmd stdout is: {res.stderr.strip()}')
        self.res=res
        self.get_chaos_id()
        return self

    def exec_check_by_cmd(self,cmd,expect):
        '''

        :param cmd: command
        :param expect: command expect str
        :return:
        '''
        logger.info(f"check cmd is: < {cmd} >")
        self.exec_cmd(cmd)
        stdout = self.res.stdout.strip()
        logger.info(f"checking: result is: < {stdout} >, expect is: < {expect} >")
        # assert expect in stdout
        if expect in stdout or expect < stdout:
            return True
        else:
            
            return False

    def exec_check_contain(self,expect):
        '''
        :param expect: expect str
        :return:
        '''
        stdout=self.res.stdout.strip()
        logger.info(f"checking: result is: < {stdout} >, expect is: < {expect} >")
        # assert expect in stdout
        if expect in stdout:
            # logger.info(f"expect: {expect}")
            # logger.info(f"stdout: {stdout}")
            
            return True
        
        return False

    def exec_check_equal(self,expect):
        '''

        :param expect: expect str, exactly same
        :return:
        '''
        stdout=self.res.stdout.strip()
        logger.info(f"checking: result is: < {stdout} >, expect is: < {expect} >")
        # assert expect == stdout
        if expect == stdout:
            # logger.info(f"expect: {expect}")
            # logger.info(f"stdout: {stdout}")
            
            return True
        
        return False



    def exec_check_request_response(self,httpObj,expect="",*kwargs):
        '''
        check service ok, using response code by contain
        :param url:
        :param method:
        :param data:
        :param json:
        :param expect:
        :param kwargs:
        :return:
        '''
        '''
             url_str=http://mobile.abc.org/sfsf~post~urlparams~jsonparms~headers
             http://http://192.168.xxx.xxx:21368/api-dev/terminus-security/account/login~post~ ~{"loginName":"$loginName","password":"$loginPass","businessCode":"terminus","loginType":"ACCOUNT","ticket":"7c9db59cdc189b5d56ba21bf20e355fcc30c0049","rightCode":"dfdf"}~{"Content-Type": "application/json;charset=UTF-8"}
            '''
        # logger.info(type(httpObj))
        # logger.info(httpObj)
        logger.info(f'url is {httpObj["url"]}')
        logger.info(f'method is {httpObj["method"]}')
        logger.info(f'params is {httpObj["params"]}')
        logger.info(f'jsonstr is {httpObj["json"]}')
        logger.info(f'headers is {httpObj["header"]}')
        # requestFuncStr=f"session().{method}(url='{url}',data={params},json={jsonstr})"
        start_time=time.time()
        resp = session().request(url=httpObj['url'], method=httpObj['method'], params=httpObj['params'], data=httpObj['json'], headers=httpObj['header'])
        logger.info(f"http response: < {resp.text} >")
        logger.info(f"expect string is: < {expect} >")
        end_time=time.time()
        time_spend=end_time-start_time
        logger.info(f"request spend: < {time_spend} > ")


        # print(requestFuncStr)
        if resp.status_code != 200:
            
            return False
        else:
            if expect in resp.text:
                
                return True
                # assert True
            else: 
                
                return False
                # assert False

    def prepare_blade(self):
        '''
        :return: blade binaries will be placed in /opt/chaos/chaosblade-1.2.0/, and add blade in path
        '''
        self.exec_cmd("cd /opt;rm -rf chaos*")
        self.upload_file("chaosblade.tar.gz", "/opt")
        self.exec_cmd('cd /opt; mkdir chaos;tar -xzvf chaosblade.tar.gz -C ./chaos;')
        self.exec_cmd('echo "export PATH=$PATH:/opt/chaos/chaosblade-1.2.0/">>~/.bashrc')
        self.exec_cmd('source ~/.bashrc')
        return self

    def check_blade_exist(self):
        if self.exec_cmd("echo $PATH|grep chaosblade"):
            return True
        else:
            return False


    def get_docker_id(self):
        '''
        get docker id
        :return:
        '''
        res=self.conn.run("docker ps|awk '{if (NR>2) {print $1}}'")
        self.containerList=deepcopy(res.stdout.strip().split('\n'))
        logger.info(f"container list is: < {self.containerList} >")
        return self
        # print(type(self.containerList))
        # return self.containerList

    def run_docker_cmd_by_dockerName(self,cmd,uniq_docker_name):
        '''
        run docker command by name
        :param cmd:
        :param uniq_docker_name:
        :return:
        '''
        docker_id=self.conn.run("docker ps|grep "+uniq_docker_name+"|awk '{print $1}'").stdout.strip()
        logger.info(f"docker_id is:< {docker_id}>")
        logger.info(f"docker command :< {cmd} --container-id {docker_id} > will be execute")
        self.commandList=f"{cmd} --container-id {docker_id}"
        self.res=self.conn.run(f"{cmd} --container-id {docker_id}")
        self.get_chaos_id()
        return self

    def run_docker_cmd_by_dockerId(self,cmd,docker_id):
        '''
        run docker command by id
        :param cmd:
        :param docker_id:
        :return:
        '''
        # docker_id=self.conn.run("docker ps|grep "+uniq_docker_name+"|awk '{print $1}'").stdout.strip()
        logger.info(f"docker command: <{cmd} --container-id {docker_id} > will be execute")
        self.commandList=f"{cmd} --container-id {docker_id}"
        self.res=self.conn.run(f"{cmd} --container-id {docker_id}")
        self.get_chaos_id()
        return self

    def run_docker_cmd(self,cmd):
        '''
        run docker commmand directly by writed case
        :param cmd:
        :return:
        '''
        # docker_id=self.conn.run("docker ps|grep "+uniq_docker_name+"|awk '{print $1}'").stdout.strip()
        logger.info(f"docker command: < {cmd} > will be execute")
        self.commandList=f"{cmd}"
        self.res=self.conn.run(f"{cmd}")
        self.get_chaos_id()
        return self

    def destroy_chaos(self):
        logger.info(f"id:< {self.uid} > will destroy")
        self.conn.run(f"blade destroy {self.uid}")




def running(wait=1,jf=None):
    
    resList=[]
    # logger.info(f'\n' * 5)
    # logger.info("\n开始"+f'--' * 50)
    # f=open("config.json",encoding='utf-8')
    # jf=json.load(f)
    for one in jf['data']:
        # logger.info(f'\n\n\n\n@@@读取到的任务：@@@\n:{one["description"]}')
        try:
            if one['flag'] == "true":
                
                logger.info(f'\n+++++++将执行的任务：+++++++ :\n{one["description"]}')
                host=one['host']
                port=int(one['port'])
                user=one['user']
                passwd=one['passwd']

                r = RemoteRunner(host, port, user, passwd)
                if not r.check_blade_exist():
                    r.prepare_blade()

                r.testResult.testResTemp['testTitle']=one["description"]
                
                if one['httpCheckCommand'] == "":
                    if one['dockerName'] != "":
                        # r = RemoteRunner(host=host, port=port, user=user, passwd=passwd)
                        r.run_docker_cmd_by_dockerName(one['execCommand'], one['dockerName']).exec_check_contain(
                            one['execCommandExpect'])
                        r.testResult.testResTemp['realResponse']=r.res
                        r.testResult.testResTemp['chaosCommand']=r.commandList
                        r.testResult.testResTemp['chaosId']=r.uid
                        time.sleep(wait)
                        r.testResult.testResTemp["checkStatus"]=r.exec_check_by_cmd(one['checkCommand'], one['checkExpect'])
                        r.testResult.testResTemp['checkCommand']=one['checkCommand']
                        r.testResult.testResTemp['expectResponse']=one['checkExpect']
                        time.sleep(wait)
                        r.destroy_chaos()
                    else:
                        if "docker" in one['execCommand']:
                            # r = RemoteRunner(host=host, port=port, user=user, passwd=passwd)
                            r.run_docker_cmd(one['execCommand']).exec_check_contain(one['execCommandExpect'])
                            r.testResult.testResTemp['realResponse']=r.res
                            time.sleep(wait)
                            r.testResult.testResTemp['chaosCommand']=r.commandList
                            r.testResult.testResTemp['chaosId']=r.uid
                            r.testResult.testResTemp["checkStatus"]=r.exec_check_by_cmd(one['checkCommand'], one['checkExpect'])
                            r.testResult.testResTemp['realResponse']=r.res
                            r.testResult.testResTemp['checkCommand']=one['checkCommand']
                            r.testResult.testResTemp['expectResponse']=one['checkExpect']
                            r.testResult.testResTemp['realResponse']=r.res
                            time.sleep(wait)
                            r.destroy_chaos()
                        else:
                            # r = RemoteRunner(host=host, port=port, user=user,passwd=passwd)
                            r.exec_cmd(one['execCommand']).exec_check_contain(one['execCommandExpect'])
                            # r.testResult.testResTemp['realResponse']=r.res
                            time.sleep(wait)
                            r.testResult.testResTemp['chaosCommand']=r.commandList
                            r.testResult.testResTemp['chaosId']=r.uid
                            r.testResult.testResTemp["checkStatus"]=r.exec_check_by_cmd(one['checkCommand'], one['checkExpect'])
                            r.testResult.testResTemp['realResponse']=r.res
                            r.testResult.testResTemp['checkCommand']=one['checkCommand']
                            r.testResult.testResTemp['expectResponse']=one['checkExpect']
                            time.sleep(wait)
                            r.destroy_chaos()
                else:
                    
                    if one['dockerName'] != "":
                        # r = RemoteRunner(host=host, port=port, user=user, passwd=passwd)
                        r.testResult.testResTemp["checkStatus"]=r.run_docker_cmd_by_dockerName(one['execCommand'], one['dockerName']).exec_check_request_response(one['httpCheckCommand'],one['httpExpect'])
                        r.testResult.testResTemp['realResponse']=r.res
                        r.testResult.testResTemp['chaosCommand']=r.commandList
                        r.testResult.testResTemp['chaosId']=r.uid
                        r.testResult.testResTemp['checkCommand']=one['httpCheckCommand']
                        r.testResult.testResTemp['expectResponse']=one['httpExpect']
                        time.sleep(wait)
                        # r.exec_check_by_cmd(one['checkCommand'], one['checkExpect'])
                        time.sleep(wait)
                        r.destroy_chaos()
                    else:
                        if "docker" in one['execCommand']:
                            # r = RemoteRunner(host=host, port=port, user=user,passwd=passwd)
                            r.testResult.testResTemp["checkStatus"]=r.run_docker_cmd(one['execCommand']).exec_check_request_response(one['httpCheckCommand'],one['httpExpect'])
                            r.testResult.testResTemp['realResponse']=r.res
                            r.testResult.testResTemp['chaosCommand']=r.commandList
                            r.testResult.testResTemp['chaosId']=r.uid
                            r.testResult.testResTemp['checkCommand']=one['httpCheckCommand']
                            r.testResult.testResTemp['expectResponse']=one['httpExpect']
                            time.sleep(wait)
                            # r.exec_check_by_cmd(one['checkCommand'], one['checkExpect'])
                            time.sleep(wait)
                            r.destroy_chaos()
                        else:
                            # r = RemoteRunner(host=one['host'], port=port, user=user,passwd=passwd)
                            r.testResult.testResTemp["checkStatus"]=r.exec_cmd(one['execCommand']).exec_check_request_response(one['httpCheckCommand'],one['httpExpect'])
                            r.testResult.testResTemp['realResponse']=r.res
                            r.testResult.testResTemp['chaosCommand']=r.commandList
                            r.testResult.testResTemp['chaosId']=r.uid
                            r.testResult.testResTemp['checkCommand']=one['httpCheckCommand']
                            r.testResult.testResTemp['expectResponse']=one['httpExpect']
                            time.sleep(wait)
                            # r.exec_check_by_cmd(one['checkCommand'], one['checkExpect'])
                            time.sleep(wait)
                            r.destroy_chaos()
                    # requestStringParser(url_str=one['httpCheck'],loginRequire="no",checkResponse=one['httpExpect'])
                resList.append(r.testResult.testResTemp)
        except Exception as e:
            r.testResult.testResTemp['testTitle']=one["description"]
            r.testResult.testResTemp['chaosCommand']=one["execCommand"]
            r.testResult.testResTemp["realResponse"]=traceback.format_exc()
            r.testResult.testResTemp["checkStatus"]=False
            resList.append(r.testResult.testResTemp)
                # r.testResult.printResultTemp()
                # r.testResult.clearTemp()
        r.testResult.printResultTemp()
        r.testResult.clearTemp()
    logger.info("\n全部完成："+f'--' * 50)
    return resList

def generateHtmlReport(resList):
    from datetime import datetime
    passCounter=0
    failCounter=0
    total=0
    for one in resList:
        if one["checkStatus"]==True:
            passCounter+=1
        else:
            failCounter+=1
        total+=1

    exeTime=datetime.strftime(datetime.now(),"%y-%m-%d %H:%M:%S")

    env = Environment(loader=FileSystemLoader('./bootstrap'))
    template = env.get_template('template.html')
    with open("testResult.html", 'w+', encoding='utf-8') as f:
        out = template.render(resList=resList,exeTime=exeTime,passCounter=passCounter,failCounter=failCounter,total=total)
        f.write(out)
    return os.path.abspath('.')+os.path.sep+'testResult.html'



if __name__ == "__main__":
    generateHtmlReport(running(jf=json.load(open("config.json",encoding='utf-8'))))
