#
# Copyright (c) 2009-2015, Mendix bv
# All Rights Reserved.
#
# http://www.mendix.com/
#

from base64 import b64encode
import socket
from .log import logger

try:
    import httplib2
except ImportError:
    logger.critical(
        "Failed to import httplib2. This module is needed by "
        "m2ee. Please povide it on the python library path"
    )
    raise

# Use json if available. If not (python 2.5) we need to import
# the simplejson module instead, which has to be available.
try:
    import json
except ImportError:
    try:
        import simplejson as json
    except ImportError as ie:
        logger.critical(
            "Failed to import json as well as simplejson. If "
            "using python 2.5, you need to provide the simplejson "
            "module in your python library path."
        )
        raise


class M2EEClient:
    def __init__(self, url, password):
        self._url = url
        self._headers = {
            "Content-Type": "application/json",
            "X-M2EE-Authentication": b64encode(
                password.encode("utf-8")
            ).decode("ascii"),
        }

    def request(self, action, params=None, timeout=None):
        body = {"action": action}
        if params:
            body["params"] = params
        body = json.dumps(body)
        h = httplib2.Http(
            timeout=timeout, proxy_info=None
        )  # httplib does not like os.fork
        logger.trace("M2EE request body: %s" % body)
        (response_headers, response_body) = h.request(
            self._url, "POST", body, headers=self._headers
        )
        if response_headers["status"] == "200":
            logger.trace("M2EE response: %s" % response_body)
            return M2EEResponse(
                action, json.loads(response_body.decode("utf-8"))
            )
        else:
            logger.error(
                "non-200 http status code: %s %s"
                % (response_headers, response_body)
            )

    def ping(self, timeout=5):
        try:
            response = self.request("echo", {"echo": "ping"}, timeout)
            if response.get_result() == 0:
                return True
        except AttributeError as e:
            # httplib 0.6 throws AttributeError: 'NoneType' object has no
            # attribute 'makefile' in case of a connection refused :-|
            logger.trace("Got %s: %s" % (type(e), e))
        except (socket.error, socket.timeout) as e:
            logger.trace("Got %s: %s" % (type(e), e))
        except Exception as e:
            logger.error("Got %s: %s" % (type(e), e))
            import traceback

            logger.error(traceback.format_exc())
        return False

    def echo(self, params=None):
        myparams = {"echo": "ping"}
        if params is not None:
            myparams.update(params)
        return self.request("echo", myparams, timeout=10)

    def get_critical_log_messages(self):
        echo_feedback = self.echo().get_feedback()
        if echo_feedback["echo"] != "pong":
            errors = echo_feedback["errors"]
            # default to 3.0 format [{"message":"Hello,
            # world!","timestamp":1315316488958,"cause":""}, ...]
            if type(errors[0]) != dict:
                return errors
            from datetime import datetime

            result = []
            for error in errors:
                errorline = []
                if "message" in error and error["message"] != "":
                    errorline.append("- %s" % error["message"])
                if "cause" in error and error["cause"] != "":
                    errorline.append("- Caused by: %s" % error["cause"])
                if len(errorline) == 0:
                    errorline.append("- [No message or cause was logged]")
                errorline.insert(
                    0,
                    datetime.fromtimestamp(error["timestamp"] / 1000).strftime(
                        "%Y-%m-%d %H:%M:%S"
                    ),
                )
                result.append(" ".join(errorline))
            return result
        return []

    def shutdown(self, timeout=5):
        # currently, the exception thrown is a feature, because the shutdown
        # action gets interrupted while executing
        try:
            self.request("shutdown", timeout=timeout)
        except Exception:
            return True
        return False

    def close_stdio(self):
        return self.request("close_stdio")

    def runtime_status(self):
        return self.request("runtime_status", timeout=10)

    def runtime_statistics(self):
        return self.request("runtime_statistics", timeout=10)

    def server_statistics(self):
        return self.request("server_statistics", timeout=10)

    def create_log_subscriber(self, params):
        return self.request("create_log_subscriber", params)

    def start_logging(self):
        return self.request("start_logging")

    def update_configuration(self, params):
        return self.request("update_configuration", params)

    def update_custom_configuration(self, params):
        return self.request("update_custom_configuration", params)

    def update_appcontainer_configuration(self, params):
        return self.request("update_appcontainer_configuration", params)

    def start(self, params=None):
        return self.request("start", params)

    def get_ddl_commands(self, params=None):
        return self.request("get_ddl_commands", params)

    def execute_ddl_commands(self, params=None):
        return self.request("execute_ddl_commands", params)

    def update_admin_user(self, params):
        return self.request("update_admin_user", params)

    def create_admin_user(self, params):
        return self.request("create_admin_user", params)

    def get_logged_in_user_names(self, params=None):
        return self.request("get_logged_in_user_names", params)

    def set_jetty_options(self, params=None):
        return self.request("set_jetty_options", params)

    def add_mime_type(self, params):
        return self.request("add_mime_type", params)

    def about(self):
        return self.request("about")

    def get_profiler_logs(self):
        return self.request("get_profiler_logs")

    def start_profiler(
        self, minimum_duration_to_log=None, flush_interval=None
    ):
        params = {}
        if minimum_duration_to_log is not None:
            params["minimum_duration_to_log"] = minimum_duration_to_log

        if flush_interval is not None:
            params["flush_interval"] = flush_interval

        return self.request("start_profiler", params)

    def stop_profiler(self):
        return self.request("stop_profiler")

    def set_log_level(self, params):
        return self.request("set_log_level", params)

    def get_log_settings(self, params):
        return self.request("get_log_settings", params)

    def check_health(self, params=None):
        return self.request("check_health", params)

    def get_current_runtime_requests(self):
        return self.request("get_current_runtime_requests")

    def interrupt_request(self, params):
        return self.request("interrupt_request", params)

    def get_all_thread_stack_traces(self):
        return self.request("get_all_thread_stack_traces")

    def get_license_information(self):
        return self.request("get_license_information")

    def set_license(self, params):
        return self.request("set_license", params)

    def connect_xmpp(self, params):
        return self.request("connect_xmpp", params)

    def disconnect_xmpp(self):
        return self.request("disconnect_xmpp")

    def create_runtime(self, params):
        return self.request("createruntime", params)

    def enable_debugger(self, params):
        return self.request("enable_debugger", params)

    def disable_debugger(self):
        return self.request("disable_debugger")

    def get_debugger_status(self):
        return self.request("get_debugger_status")

    def cache_statistics(self):
        return self.request("cache_statistics")


class M2EEResponse:

    ERR_REQUEST_NULL = -1
    ERR_CONTENT_TYPE = -2
    ERR_HTTP_METHOD = -3
    ERR_FORBIDDEN = -4
    ERR_ACTION_NOT_FOUND = -5
    ERR_READ_REQUEST = -6
    ERR_WRITE_REQUEST = -7

    def __init__(self, action, json):
        self._action = action
        self._json = json
        self._result = self._json["result"]
        self._feedback = self._json.get("feedback", {})
        self._message = self._json.get("message", None)
        self._cause = self._json.get("cause", None)
        self._stacktrace = self._json.get("stacktrace", None)

    def get_result(self):
        return self._result

    def get_feedback(self):
        return self._feedback

    def get_message(self):
        return self._message

    def get_cause(self):
        return self._cause

    def get_stacktrace(self):
        return self._stacktrace

    def has_error(self):
        return self._result != 0

    def display_error(self):
        if self.has_error():
            logger.error(self.get_error())
            if self._stacktrace:
                logger.debug(self._stacktrace)

    def get_error(self):
        error = "Executing %s did not succeed: result: %s, message: %s" % (
            self._action,
            self._json["result"],
            self._json["message"],
        )
        if self._json.get("cause", None) is not None:
            error = "%s, caused by: %s" % (error, self._json["cause"])
        return error

    def __str__(self):
        return str({"action": self._action, "json": self._json})