import logging
import re

from jnpr.junos import jxml as JXML
from jnpr.junos.exception import RpcError
from jnpr.junos.facts.swver import version_info

logger = logging.getLogger("jnpr.junos.facts.get_software_information")


def _get_software_information(device):
    # See if device understands "invoke-on all-routing-engines"
    try:
        return device.rpc.cli(
            "show version invoke-on all-routing-engines", format="xml", normalize=True
        )
    except RpcError as err:
        # See if device runs on a linux kernel
        if device.facts["_is_linux"]:
            sw_info_all = device.rpc.get_software_information(
                normalize=True, node="all"
            )
            sw_info_re0 = device.rpc.get_software_information(
                normalize=True, node="re0"
            )
            sw_info_re1 = device.rpc.get_software_information(
                normalize=True, node="re1"
            )
            re0_hostname = sw_info_re0.findtext("./host-name")
            re1_hostname = sw_info_re1.findtext("./host-name")
            for current_hostname in sw_info_all.findall(
                "./multi-routing-engine-result/software-information/host-name"
            ):
                if current_hostname.text == re0_hostname:
                    current_hostname.getparent().getparent().append(
                        JXML("<re-name>re0</re-name>")
                    )
                elif current_hostname.text == re1_hostname:
                    current_hostname.getparent().getparent().append(
                        JXML("<re-name>re1</re-name>")
                    )
            return sw_info_all
        # See if device is VC Capable
        if device.facts["vc_capable"] is True:
            try:
                return device.rpc.cli(
                    "show version all-members", format="xml", normalize=True
                )
            except Exception:
                pass
        # check if rpc-reply got 2 child element, one rpc-error and another
        # software information
        elif (
            hasattr(err, "rpc_error")
            and err.rpc_error is not None
            and "Could not connect to " in err.rpc_error.get("message")
        ):
            logger.debug(err.rpc_error.get("message"))
            # getparent as rpc-reply got software-information in 2nd element
            # and dev.cli return just 1st element.
            rsp = err.xml.getparent()
            rsp = JXML.remove_namespaces(rsp)
            if rsp.xpath(".//software-information"):
                return rsp
        try:
            # JDM for Junos Node Slicing
            return device.rpc.get_software_information(
                all_servers=True, format="xml", normalize=True
            )
        except Exception:
            pass
        try:
            sw_info = device.rpc.get_software_information(normalize=True)
        except Exception:
            sw_info = True
        try:
            if sw_info is True:
                # Possibly an NFX which requires 'local' and 'detail' args.
                sw_info = device.rpc.get_software_information(
                    local=True, detail=True, normalize=True
                )
            return sw_info
        except Exception:
            pass


def provides_facts():
    """
    Returns a dictionary keyed on the facts provided by this module. The value
    of each key is the doc string describing the fact.
    """
    return {
        "junos_info": "A two-level dictionary providing Junos software "
        "version information for each RE in the system. "
        "The first-level key is the name of the RE. The "
        "second level key is 'text' for the version as a "
        "string and 'object' for the version as a "
        "version_info object.",
        "hostname": "A string containing the hostname of the current Routing Engine.",
        "hostname_info": "A dictionary keyed on Routing Engine name. The "
        "value of each key is the hostname of the "
        "Routing Engine.",
        "model": "An uppercase string containing the model of the chassis "
        "in which the current Routing Engine resides.",
        "model_info": "A dictionary keyed on Routing Engine name. The "
        "value of each key is an uppercase string "
        "containing the model of the chassis in which the "
        "Routing Engine resides.",
        "version": "A string containing the Junos version of the current "
        "Routing Engine.",
        "version_info": "The Junos version of the current Routing Engine "
        "as a version_info object.",
        "version_RE0": "A string containing the Junos version of the "
        "RE in slot 0. (Assuming the system contains an "
        "RE0.)",
        "version_RE1": "A string containing the Junos version of the "
        "RE in slot 1. (Assuming the system contains an "
        "RE1)",
    }


def get_facts(device):
    """
    Gathers facts from the <get-software-information/> RPC.
    """
    junos_info = None
    hostname = None
    hostname_info = None
    model = None
    model_info = None
    version = None
    ver_info = None
    version_RE0 = None
    version_RE1 = None

    rsp = _get_software_information(device)

    if rsp.tag == "software-information":
        si_rsp = [rsp]
    else:
        si_rsp = rsp.findall(".//software-information")

    for re_sw_info in si_rsp:
        re_name = re_sw_info.findtext("../re-name", "re0")
        re_model = re_sw_info.findtext("./product-model")
        re_hostname = re_sw_info.findtext("./host-name")
        # First try the <junos-version> tag present in >= 15.1
        re_version = re_sw_info.findtext("./junos-version")
        if re_version is None:
            # For < 15.1, get version from the "junos" package.
            try:
                re_pkg_info = re_sw_info.findtext(
                    './package-information[name="junos"]/comment'
                )
                if re_pkg_info is not None:
                    re_version = re.findall(r"\[(.*)\]", re_pkg_info)[0]
                else:
                    # Junos Node Slicing JDM case
                    re_pkg_info = re_sw_info.findtext(
                        './package-information[name="JUNOS version"]/comment'
                    )
                    if re_pkg_info is not None:
                        # In this case, re_pkg_info might look like this:
                        # JUNOS version : 17.4-20170703_dev_common.0-secure
                        # Match everything from last space until the end.
                        re_version = re.findall(r".*\s+(.*)", re_pkg_info)[0]
                    else:
                        # NFX JDM case
                        re_pkg_info = re_sw_info.findtext(
                            './version-information[component="MGD"]/release'
                        )
                        re_version = re.findall(r"(.*\d+)", re_pkg_info)[0]
            except Exception:
                re_version = None
        if model_info is None and re_model is not None:
            model_info = {}
        if re_model is not None:
            model_info[re_name] = re_model.upper()
        if hostname_info is None and re_hostname is not None:
            hostname_info = {}
        if re_hostname is not None:
            hostname_info[re_name] = re_hostname
        if junos_info is None and re_version is not None:
            junos_info = {}
        if re_version is not None:
            junos_info[re_name] = {
                "text": re_version,
                "object": version_info(re_version),
            }

        # Check to see if re_name is the RE we are currently connected to.
        # There are at least five cases to handle.
        this_re = False
        # 1) this device doesn't support the current_re fact and there's only
        #    one RE.
        if device.facts["current_re"] is None and len(si_rsp) == 1:
            this_re = True
        # 2) re_name is in the current_re fact. The easy case.
        elif re_name in device.facts["current_re"]:
            this_re = True
        # 3) Some single-RE devices (discovered on EX2200 running 11.4R1)
        # don't include 'reX' in the current_re list. Check for this
        # condition and still set default hostname, model, and version
        elif (
            re_name == "re0"
            and "re1" not in device.facts["current_re"]
            and "master" in device.facts["current_re"]
        ):
            this_re = True
        # 4) For an lcc in a TX(P) re_name is 're0' or 're1', but the
        # current_re fact is ['lcc1-re0', 'member1-re0', ...]. Check to see
        # if any iri_name endswith the re_name.
        if this_re is False:
            for iri_name in device.facts["current_re"]:
                if iri_name.endswith("-" + re_name):
                    this_re = True
                    break
        # 5) For an MX configured with Node Virtualization then, re_name is
        #    bsys-reX, but the iri_name is still just reX.
        if this_re is False:
            for iri_name in device.facts["current_re"]:
                if re_name == "bsys-" + iri_name:
                    this_re = True
                    break
        # Set hostname, model, and version facts if we've found the RE to
        # which we are currently connected.
        if this_re is True:
            if hostname is None:
                hostname = re_hostname
            if model is None:
                model = re_model.upper()
            if version is None:
                version = re_version

    if version is None:
        version = "0.0I0.0"
    ver_info = version_info(version)
    if junos_info is not None:
        if "re0" in junos_info:
            version_RE0 = junos_info["re0"]["text"]
        elif "node0" in junos_info:
            version_RE0 = junos_info["node0"]["text"]
        elif "bsys-re0" in junos_info:
            if len(junos_info) > 4:
                version_RE0 = junos_info["bsys-re0"]["text"]
            else:
                for key in junos_info.keys():
                    if key.startswith("gnf") and key.endswith("re0"):
                        version_RE0 = junos_info[key]["text"]
        elif "server0" in junos_info:
            version_RE0 = junos_info["server0"]["text"]
        if "re1" in junos_info:
            version_RE1 = junos_info["re1"]["text"]
        elif "node1" in junos_info:
            version_RE1 = junos_info["node1"]["text"]
        elif "bsys-re1" in junos_info:
            if len(junos_info) > 4:
                version_RE1 = junos_info["bsys-re1"]["text"]
            else:
                for key in junos_info.keys():
                    if key.startswith("gnf") and key.endswith("re1"):
                        version_RE1 = junos_info[key]["text"]
        elif "server1" in junos_info:
            version_RE1 = junos_info["server1"]["text"]

    return {
        "junos_info": junos_info,
        "hostname": hostname,
        "hostname_info": hostname_info,
        "model": model,
        "model_info": model_info,
        "version": version,
        "version_info": ver_info,
        "version_RE0": version_RE0,
        "version_RE1": version_RE1,
    }
