# Copyright 2016 Splunk, Inc.
# SPDX-FileCopyrightText: 2020 2020
#
# SPDX-License-Identifier: Apache-2.0

"""
This module contains splunk server info related functionalities.
"""

import json

from . import splunk_rest_client as rest_client
from . import utils
from splunklib import binding
from .utils import retry

__all__ = ["ServerInfo"]


class ServerInfoException(Exception):
    pass


class ServerInfo(object):
    """This class is a wrapper of splunk server info.

    :param session_key: Splunk access token.
    :type session_key: ``string``
    :param scheme: (optional) The access scheme, default is None.
    :type scheme: ``string``
    :param host: (optional) The host name, default is None.
    :type host: ``string``
    :param port: (optional) The port number, default is None.
    :type port: ``integer``
    :param context: Other configurations for Splunk rest client.
    :type context: ``dict``
    """

    SHC_MEMBER_ENDPOINT = "/services/shcluster/member/members"
    SHC_CAPTAIN_INFO_ENDPOINT = "/services/shcluster/captain/info"

    def __init__(self, session_key, scheme=None, host=None, port=None, **context):
        self._rest_client = rest_client.SplunkRestClient(
            session_key, "-", scheme=scheme, host=host, port=port, **context
        )

    @retry(exceptions=[binding.HTTPError])
    def _server_info(self):
        return self._rest_client.info

    @property
    def server_name(self):
        """Get server name.

        :returns: Server name.
        :rtype: ``string``
        """

        return self._server_info()["serverName"]

    @property
    def guid(self):
        """Get guid for the server.

        :returns: GUID.
        :rtype: ``string``
        """

        return self._server_info()["guid"]

    @property
    def version(self):
        """Get splunk server version.

        :returns: Splunk server version.
        :rtype: ``string``
        """

        return self._server_info()["version"]

    def is_captain(self):
        """Check if this server is SHC captain. Note during a rolling start
           of SH members, the captain may be changed from machine to machine.
           To avoid the race condition, client may need do necessary sleep and
           then poll is_captain_ready() == True and then check is_captain().
           See is_captain_ready() for more details

        :returns: True if this server is SHC captain else False.
        :rtype: ``bool``
        """

        return "shc_captain" in self._server_info()["server_roles"]

    def is_cloud_instance(self):
        """Check if this server is a cloud instance.

        :returns: True if this server is a cloud instance else False.
        :rtype: ``bool``
        """

        try:
            return self._server_info()["instance_type"] == "cloud"
        except KeyError:
            return False

    def is_search_head(self):
        """Check if this server is a search head.

        :returns: True if this server is a search head else False.
        :rtype: ``bool``
        """

        server_info = self._server_info()
        for sh in ("search_head", "cluster_search_head"):
            if sh in server_info["server_roles"]:
                return True

        return False

    def is_shc_member(self):
        """Check if this server is a SHC member.

        :returns: True if this server is a SHC member else False.
        :rtype: ``bool``
        """

        server_info = self._server_info()
        for sh in ("shc_member", "shc_captain"):
            if sh in server_info["server_roles"]:
                return True

        return False

    @retry(exceptions=[binding.HTTPError])
    def get_shc_members(self):
        """Get SHC members.

        :returns: List of SHC members [(label, peer_scheme_host_port) ...]
        :rtype: ``list``

        :raises ServerInfoException: If this server has no SHC members.
        """

        try:
            content = self._rest_client.get(
                self.SHC_MEMBER_ENDPOINT, output_mode="json"
            ).body.read()
        except binding.HTTPError as e:
            if e.status != 404 and e.status != 503:
                raise

            raise ServerInfoException(
                "This server is not a SHC member and has no SHC members."
            )

        members = []
        for member in json.loads(content)["entry"]:
            content = member["content"]
            members.append((content["label"], content["peer_scheme_host_port"]))

        return members

    @retry(exceptions=[binding.HTTPError])
    def is_captain_ready(self):
        """Check if captain is ready.

        Client usually first polls this function until captain is ready
        and then call is_captain to detect current captain machine

        :returns: True/False
        :rtype: ``bool``

        Usage::

            >>> serverinfo = solnlib.server_info.ServerInfo(session_key)
            >>> while 1:
            >>>    if serverinfo.is_captain_ready():
            >>>        break
            >>>    time.sleep(2)
            >>>
            >>> # If do_stuff can only be executed in SH captain
            >>> if serverinfo.is_captain():
            >>>    do_stuff()
        """

        cap_info = self.captain_info()
        return utils.is_true(cap_info["service_ready_flag"]) and utils.is_false(
            cap_info["maintenance_mode"]
        )

    @retry(exceptions=[binding.HTTPError])
    def captain_info(self):
        """Check if captain is ready.

        :returns: captain info, like {
            "elected_captain": 1463195590,
            "id": "9CA04BAD-0C24-4703-8A88-E20345833508",
            "initialized_flag": true,
            "label": "my-shc04-sh",
            "maintenance_mode": false,
            "mgmt_uri": "https://my-shc04-sh:8089",
            "min_peers_joined_flag": true,
            "peer_scheme_host_port": "https://my-shc04-sh2:8089",
            "rolling_restart_flag": false,
            "service_ready_flag": true}
        :rtype: ``dict``

        :raises ServerInfoException: If there is SHC is not enabled.
        """

        try:
            content = self._rest_client.get(
                self.SHC_CAPTAIN_INFO_ENDPOINT, output_mode="json"
            ).body.read()
        except binding.HTTPError as e:
            if e.status == 503 and "not available" in str(e):
                raise ServerInfoException(str(e))
            raise

        return json.loads(content)["entry"][0]["content"]
