#   Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
#   Licensed under the Universal Permissive License v 1.0 as shown at https://opensource.org/licenses/UPL.
#
import base64
import json
import logging
import socket
import sys
import time

ALX_SOCKET = "/var/lib/oracle-cloud-agent/plugins/oci-alx/alx-osms.sock"
BASE_PATH = "/20210615/"
UPDATE_PATH = BASE_PATH + "updates/"
YUM_PATH = UPDATE_PATH + "yum/"
KSPLICE_PATH = UPDATE_PATH + "ksplice/"

POST_TEMPLATE = "POST %s HTTP/1.1\r\nHOST: 127.0.0.1\r\nAccept: *\r\nContent-Type: application/json\r\nContent-Length: " \
                "%d\r\n\r\n%s"

HTTP_OK = "HTTP/1.1 200 OK"
SUCCESS = "SUCCESS"
FAILURE = "FAILURE"


def create_yum_body(status, exit_code, tid, time_started, time_ended, message=None):
    """
    Creates a json style dictionary for yum update body
    :param status: update status of value "SUCCESS/FAILURE/WARNING"
    :param exit_code: yum exit code
    :param tid: yum transaction id in type int if no error occurs. If error happens, value is -1
    :param time_started: update start time timestamp in floating point
    :param time_ended: update end time timestamp in floating point
    :param message: Error message string in base64 encoding if any error occurs
    :return: json style dictionary containing request body for /updates/yum POST request
    """
    body = {
        "status": status,
        "exitCode": exit_code,
        "tid": tid,
        "timeStarted": time_started,
        "timeEnded": time_ended
    }
    # message is only set if error occurs
    try:
        if message:
            body["message"] = base64encode(message)
    except Exception as e:
        logging.error(u"Failed to encode alx message to base64 format: %s" % e)
    return body


def create_ksplice_body(status, exit_code, output, time_started, time_ended):
    """
    Creates a json style dictionary for ksplice update body
    :param status: update status of value "SUCCESS/FAILURE/WARNING"
    :param exit_code: ksplice exit code
    :param output: ksplice command output
    :param time_started: update start time timestamp in floating point
    :param time_ended: update end time timestamp in floating point
    :return: json style dictionary containing request body for /updates/ksplice POST request
    """
    body = {
        "status": status,
        "exitCode": exit_code,
        "timeStarted": time_started,
        "timeEnded": time_ended
    }
    try:
        body["output"] = base64encode(output)
    except Exception as e:
        logging.error(u"Failed to encode alx message to base64 format: %s" % e)
    return body


def post_request(path, body):
    """
    Sends post request to the alx socket using the given body
    :param path: API request path in alx socket
    :param body: POST body of type dictionary
    :return: socket response string or None if error happens
    """
    try:
        body_str = json.dumps(body)
        content_length = len(body_str)
        payload = (POST_TEMPLATE % (path, content_length, body_str))
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        sock.settimeout(600.0)
        sock.connect(ALX_SOCKET)
        logging.debug("ALX socket %s connected" % ALX_SOCKET)
        logging.debug("Sending POST request to %s of length %d: %s" % (path, content_length, body))
        sock.sendall(payload.encode())
        response = sock.recv(4096)
        sock.close()
        return response
    except socket.error as msg:
        logging.error(u"Failed to connect to alx socket: %s" % msg)
    except Exception as e:
        logging.error(u"Failed to send payload to alx socket: %s" % e)


def send_yum_updates(status, exit_code, tid, time_started, time_ended, message=None):
    # send request
    body = create_yum_body(status, exit_code, tid, time_started, time_ended, message)
    http_response = str(post_request(YUM_PATH, body))

    # log in debug mode or error occurs
    if not http_response:
        logging.error("Did not receive yum post response from alx")
    elif HTTP_OK not in http_response:
        logging.error("Received yum post response of length %d, %s" % (len(http_response), http_response))
    else:
        logging.info("Yum tid %d sent to alx" % tid)
        logging.debug("Received yum post response of length %d, %s" % (len(http_response), http_response))


def send_failed_yum_result(result, time_started):
    try:
        exit_code = result[0]
        if isinstance(exit_code, tuple):
            exit_code = exit_code[0]
        send_yum_updates(FAILURE, exit_code, -1, time_started, time.time(), result[1])
    except Exception as e:
        logging.error(u"Failed to send payload to alx socket: %s" % e)


def send_ksplice_updates(status, exit_code, output, time_started, time_ended):
    # send request
    body = create_ksplice_body(status, exit_code, output, time_started, time_ended)
    http_response = str(post_request(KSPLICE_PATH, body))

    # log in debug mode or error occurs
    if not http_response:
        logging.error("Did not receive ksplice post response from alx")
    elif HTTP_OK not in http_response:
        logging.error("Received ksplice post response of length %d, %s" % (len(http_response), http_response))
    else:
        logging.info("ksplice update outputs sent to alx")
        logging.debug("Received ksplice post response of length %d, %s" % (len(http_response), http_response))


def base64encode(string):
    if sys.version_info > (3, 0):
        return base64.b64encode(string.encode("utf-8")).decode()
    else:
        if isinstance(string, unicode):
            return base64.b64encode(string.encode("utf-8"))
        return base64.b64encode(string)
