# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

from libmozdata import socorro
import re
from . import java, tools, utils
from .logger import logger


# Mercurial URI
HG_PAT = re.compile("hg:hg.mozilla.org[^:]*:([^:]*):([a-z0-9]+)")


def get_crash_data(uuid):
    """Get the crash data from Socorro"""
    data = socorro.ProcessedCrash.get_processed(uuid)
    return data[uuid]


def get_crash(uuid, buildid, channel, mindate, chgset, filelog, interesting_chgsets):
    """Get the a crash with its uuid"""
    logger.info("Get {} for analyzis".format(uuid))
    data = get_crash_data(uuid)
    return get_crash_info(
        data, uuid, buildid, channel, mindate, chgset, filelog, interesting_chgsets
    )


def get_crash_by_uuid(uuid, mindate, filelog):
    """Get the a crash with its uuid"""
    logger.info("Get {} for analyzis".format(uuid))
    data = get_crash_data(uuid)
    buildid = data["build"]
    bid = utils.get_build_date(buildid)
    channel = data["release_channel"]
    interesting_chgsets = set()
    chgset = tools.get_changeset(bid, channel, data["product"])
    res = get_crash_info(
        data, uuid, bid, channel, mindate, chgset, filelog, interesting_chgsets
    )
    return res, channel, interesting_chgsets


def get_crash_info(
    data, uuid, buildid, channel, mindate, chgset, filelog, interesting_chgsets
):
    """Inspect the crash stack (Java's one too if present)"""
    res = {}
    java_st = data.get("java_stack_trace")
    jframes, files = java.inspect_java_stacktrace(java_st, chgset)

    if jframes:
        files = filelog(files, mindate, buildid, channel)
        if amend(jframes, files, interesting_chgsets):
            res["java"] = {"frames": jframes, "hash": get_simplified_hash(jframes)}
    else:
        if "json_dump" not in data:
            return None
        frames, files = inspect_stacktrace(data, chgset)
        if frames:
            files = filelog(files, mindate, buildid, channel)
            if amend(frames, files, interesting_chgsets):
                res["nonjava"] = {"frames": frames, "hash": get_simplified_hash(frames)}

    return res


def get_simplified_hash(frames):
    """Get a hash from the frames we have in the crash stack"""
    res = ""
    for frame in frames:
        if frame["line"] != -1:
            res += str(frame["stackpos"]) + "\n"
            res += frame["filename"] + "\n"
            res += str(frame["line"]) + "\n"
    if res != "":
        return utils.hash(res)
    return ""


def get_path_node(uri):
    """Get the file path and the hg node"""
    name = node = ""
    if uri:
        m = HG_PAT.match(uri)
        if m:
            name = m.group(1)
            node = utils.short_rev(m.group(2))
    return name, node


def inspect_stacktrace(data, build_node):
    """Inspect the stack from the data and the check that the hg node
    from the build is the same that the one we have in stack data
    (the nodes could be different when the crash was occuring during an update)"""
    res = []
    files = set()
    dump = data["json_dump"]
    max_frames = 50
    if "threads" in dump:
        N = dump["crash_info"].get("crashing_thread")
        if N is not None:
            frames = dump["threads"][N]["frames"]
            frames = frames[0:max_frames]
            for n, frame in enumerate(frames):
                uri = frame.get("file")
                filename, node = get_path_node(uri)
                if node:
                    if node != build_node:
                        return [], set()
                    files.add(filename)
                fun = frame.get("function", "")
                line = frame.get("line", -1)
                module = frame.get("module", "")
                res.append(
                    {
                        "original": uri,
                        "filename": filename,
                        "changesets": [],
                        "module": module,
                        "function": fun,
                        "line": line,
                        "node": node,
                        "internal": node != "",
                        "stackpos": n,
                    }
                )
    return res, files


def amend(frames, files, interesting_chgsets):
    """Amend frame info"""
    interesting = False
    if files:
        for frame in frames:
            filename = frame["filename"]
            if filename in files:
                chgsets = files[filename]
                interesting_chgsets |= set(chgsets)
                frame["changesets"] = chgsets
                interesting = True
    return interesting
