'''
'''
#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you under the Apache License, Version 2.0 (the
#  "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import json
import socket
import ssl

from autest.api import AddWhenFunction
import hosts.output as host
from ports import get_port

import trlib.ipconstants as IPConstants
from trlib import Transaction, Request, Response, Session

DEFAULT_LOOKUP_KEY = '{PATH}'


def addMethod(self, testName, request_header, functionName):
    return


# creates the full request or response block using headers and message data


def httpObject(self, header, data):
    r = dict()
    r["timestamp"] = ""
    r["headers"] = header
    r["body"] = data
    return r


def getHeaderFieldVal(request_header, field):
    requestline = request_header["headers"].split("\r\n")[0]
    requestline = requestline.split(" ")[1]
    field = field + ':'
    valField = request_header["headers"].split(field, 1)
    val = ""
    if len(valField) > 1:
        field_v = valField[1].split("\r\n", 1)
        if len(field_v) > 0:
            val = field_v[0].strip()
    return val


# addResponse adds customized response with respect to request_header. request_header and response_header are both dictionaries
def addResponse(self, filename, request_header, response_header):
    client_request = Request.fromRequestLine(
        request_header["headers"], "" if "body" not in request_header else request_header["body"],
        None if "options" not in request_header else request_header["options"])

    server_response = Response.fromRequestLine(
        response_header["headers"], "" if "body" not in response_header else response_header["body"],
        None if "options" not in response_header else response_header["options"])

    # timestamp field is left None because that needs to be revised for better implementation
    txn = Transaction(client_request, None, server_response, None, None, None)

    absFilepath = os.path.join(self.Variables.DataDir, filename)
    addTransactionToSession(txn, absFilepath)
    return


# adds transaction in json format to the specified file
def addTransactionToSession(txn, JFile):
    jsondata = None
    if not os.path.exists(os.path.dirname(JFile)):
        os.makedirs(os.path.dirname(JFile))
    if os.path.exists(JFile):
        jf = open(JFile, 'r')
        jsondata = json.load(jf)

    # hard coding only 1 session per file
    # since for the purpose of testing, we don't need multiple sessions in a file
    if jsondata is None:
        jsondata = {}
        jsondata["sessions"] = []

        jsondata["sessions"].append(Session(JFile.split("/")[-1], None, None, [txn]).toJSON())
        jsondata["meta"] = {}
        jsondata["meta"]["version"] = "1.0"
    else:
        # hardcoding 0 because for testing we only have 1 session
        jsondata["sessions"][0]["transactions"].append(txn.toJSON())

    with open(JFile, 'w+') as jf:
        jf.write(json.dumps(jsondata))


def addSessionFromFiles(self, session_dir):
    self.Setup.Copy(session_dir, self.Variables.DataDir)


# make headers with the key and values provided
def makeHeader(self, requestString, **kwargs):
    headerStr = requestString + '\r\n'
    for k, v in kwargs.iteritems():
        headerStr += k + ': ' + v + '\r\n'
    headerStr = headerStr + '\r\n'
    return headerStr


def uServerUpAndRunning(serverHost, port, isSsl, isIPv6, request, clientcert='', clientkey=''):
    if isIPv6:
        plain_sock = socket.socket(socket.AF_INET6)
    else:
        plain_sock = socket.socket(socket.AF_INET)

    if isSsl:
        context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
        context.check_hostname = False
        context.verify_mode = ssl.CERT_NONE
        if clientcert != '' or clientkey != '':
            context.load_cert_chain(certfile=clientcert, keyfile=clientkey)
        sock = context.wrap_socket(plain_sock)
    else:
        sock = plain_sock

    try:
        sock.connect((serverHost, port))
    except ConnectionRefusedError:
        host.WriteDebug(['uServerUpAndRunning', 'when'], "Connection refused: {0}:{1}".format(serverHost, port))
        return False
    except ssl.SSLError as e:
        host.WriteDebug(['uServerUpAndRunning', 'when'], "SSL connection error: {0}:{1}:{2}".format(serverHost, port, e))
        return False

    sock.sendall(request.encode())
    decoded_output = ''
    while True:
        host.WriteDebug("??")
        output = sock.recv(4096)  # suggested bufsize from docs.python.org
        host.WriteDebug("!!")
        if len(output) <= 0:
            break
        else:
            decoded_output += output.decode()
    sock.close()
    sock = None

    expected_response = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 4\r\n\r\nimok"
    if decoded_output == expected_response:
        return True

    host.WriteError('\n'.join(['Got invalid response from microserver:', '----', decoded_output, '----']))


AddWhenFunction(uServerUpAndRunning)


def MakeOriginServer(
        obj,
        name,
        port=None,
        s_port=None,
        ip='INADDR_LOOPBACK',
        delay=None,
        ssl=False,
        lookup_key=DEFAULT_LOOKUP_KEY,
        clientcert='',
        clientkey='',
        both=False,
        options={}):
    data_dir = os.path.join(obj.RunDirectory, name)
    p = obj.Processes.Process(name)

    ipaddr = IPConstants.getIP(ip)

    command = "microserver --data-dir {0} --ip_address {1} --lookupkey '{2}'".format(data_dir, ipaddr, lookup_key)

    if delay:
        command += " --delay {0}".format(delay)

    if both or ssl:
        if not s_port:
            s_port = get_port(p, "SSL_Port")

        command += " --both" if both else " --ssl"
        key = clientkey if clientkey else os.path.join(obj.Variables["AtsTestToolsDir"], "microserver", "ssl", "server.pem")
        cert = clientcert if clientcert else os.path.join(obj.Variables["AtsTestToolsDir"], "microserver", "ssl", "server.crt")
        command += " --key {0}".format(key)
        command += " --cert {0}".format(cert)
        command += " --s_port {0}".format(s_port)

    # this might break if user specifies both both and ssl
    if not ssl:  # in both or HTTP only mode
        if not port:
            port = get_port(p, "Port")

        command += " --port {0}".format(port)

    for flag, value in options.items():
        command += " {} {}".format(flag, value if value else '')

    p.Command = command
    p.Setup.MakeDir(data_dir)
    p.Variables.DataDir = data_dir
    p.Variables.lookup_key = lookup_key
    AddMethodToInstance(p, addResponse)
    AddMethodToInstance(p, addTransactionToSession)
    AddMethodToInstance(p, addSessionFromFiles)

    custom_lookup_header = ''
    keys = lookup_key.split("}")

    for key in keys:
        if key not in ['{PATH', '{URL', '{HOST', '{%Host']:
            k = key.replace("{%", "")

            if len(k) > 0:
                custom_lookup_header += '{0}: healthcheck\r\n'.format(k)

    healthcheck_request = {
        "headers": "GET /ruok HTTP/1.1\r\nHost: {0}\r\n{1}\r\n".format(ipaddr, custom_lookup_header),
        "timestamp": "1469733493.993",
        "body": ""
    }

    # Set up health check.
    addResponse(
        p, "healthcheck.json", healthcheck_request, {
            "headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n",
            "timestamp": "1469733493.993",
            "body": "imok",
            "options": {
                "skipHooks": None
            }
        })

    p.Ready = When.uServerUpAndRunning(
        ipaddr,
        s_port if ssl else port,
        ssl,
        IPConstants.isIPv6(ip),
        healthcheck_request["headers"],
        clientcert=clientcert,
        clientkey=clientkey)
    p.ReturnCode = Any(None, 0)

    return p


ExtendTest(MakeOriginServer, name="MakeOriginServer")
ExtendTest(MakeOriginServer, name="MakeOrigin")
