#!/usr/bin/env python3
"""Pretty-print a Connect IQ ERA report.

Usage:
    <sdk>/bin/era -a <app-uuid> | python3 era_report.py [--json]

Reads the raw JSON array `era` writes to stdout, sorts the groups by how many
users hit them, and resolves the `products` part numbers to device names using
the locally installed Connect IQ device definitions.
"""
import glob
import json
import os
import sys

DEVICES = os.path.expanduser(
    "~/Library/Application Support/Garmin/ConnectIQ/Devices")


def part_number_map():
    """part number -> {device display names}. One number can cover rebadges."""
    out = {}
    for path in glob.glob(os.path.join(DEVICES, "*", "compiler.json")):
        try:
            with open(path) as fh:
                dev = json.load(fh)
        except (OSError, ValueError):
            continue
        name = dev.get("displayName") or os.path.basename(os.path.dirname(path))
        numbers = [dev.get("worldWidePartNumber"), dev.get("hardwarePartNumber")]
        for entry in dev.get("partNumbers") or []:
            if isinstance(entry, dict) and entry.get("number"):
                numbers.append(entry["number"])
        for number in numbers:
            if number:
                out.setdefault(number, set()).add(name)
    return out


def short_path(path):
    """Absolute build-machine path -> repo-relative, best effort."""
    for marker in ("/source/", "/Shared/"):
        if marker in path:
            head, _, tail = path.partition(marker)
            return os.path.basename(head) + marker + tail if marker == "/source/" \
                else "Shared/" + tail
    return path


def main():
    raw = sys.stdin.read().strip()
    if not raw:
        print("no input (era printed nothing - check the app id and login)")
        return 1
    try:
        groups = json.loads(raw)
    except ValueError:
        print("not JSON, era said:\n" + raw[:500])
        return 1
    if "--json" in sys.argv:
        json.dump(groups, sys.stdout, indent=2)
        return 0
    if not groups:
        print("No crash reports for this app.")
        return 0

    parts = part_number_map()
    groups.sort(key=lambda g: g.get("occurrenceCount", 0), reverse=True)
    total = sum(g.get("occurrenceCount", 0) for g in groups)
    print("%d group(s), %d occurrence(s) total\n" % (len(groups), total))

    for group in groups:
        print("=" * 72)
        print("%s  x%d" % (group.get("errorName", "?"),
                           group.get("occurrenceCount", 0)))
        if group.get("details"):
            print("  details : %s" % group["details"])
        print("  version : %s (internal %s)" % (
            ", ".join(group.get("externalAppVersions") or ["?"]),
            ", ".join(str(v) for v in group.get("appVersions") or ["?"])))
        print("  window  : %s -> %s" % (group.get("firstOccurrence", "?")[:16],
                                        group.get("lastOccurrence", "?")[:16]))
        if group.get("languageCodes"):
            print("  langs   : %s" % ", ".join(group["languageCodes"]))
        for product in group.get("products") or []:
            number = product.get("product", "?")
            names = ", ".join(sorted(parts.get(number, {"UNKNOWN " + number})))
            print("  device  : %-34s fw %s" % (
                names[:34], ", ".join(product.get("firmwareVersions") or ["?"])))
        print("  backtrace (order is approximate under -O 3p):")
        for frame in group.get("translatedBacktrace") or []:
            print("      %s:%s  %s" % (short_path(frame.get("file", "?")),
                                       frame.get("line", "?"),
                                       frame.get("symbol", "?")))
        print()
    return 0


if __name__ == "__main__":
    sys.exit(main())
