import xml.etree.ElementTree as ET
import subprocess
import time
import os
import sys

from collections import Counter


def get_buildstatus(project: str) -> ET.Element:
    for _ in range(5):
        try:
            output = subprocess.check_output(["osc", "pr", "--xml", project])
            return ET.fromstring(output)
        except subprocess.CalledProcessError:
            # Don't retry immediatly just in case is a network error
            time.sleep(5)
            continue
    print("Failed to get buildstatus from OBS")


def do_wait(project: str, commit: str) -> ET.Element:
    last_state = None
    waiting_states = (
        "blocked",
        "scheduled",
        "dispatching",
        "building",
        "signing",
        "finished",
    )
    while True:
        time.sleep(5)
        status = get_buildstatus(project)

        # Just in case the buildstatus fails
        if status is None:
            print("Could not retrieve status, retrying...")
            continue

        if last_state == status.get("state"):
            continue
        else:
            last_state = status.get("state")

        scminfo = {e.text for e in status.findall(".//scminfo")}
        if len(scminfo) != 1 or scminfo.pop() != commit:
            print("Waiting for OBS to sync with SCM")
            continue

        if not all(
            [
                e.get("state") == "published"  # Only consider if all packages are published
                and e.get("dirty") is None  # Exclude states needing recalculation
                and e.get("code") not in waiting_states  # Exclude transient/waiting states
                for e in status.findall("./result")
            ] + [ e.get("code") not in waiting_states for e in status.findall("./status") ]
        ):
            print("Waiting for OBS to finish building")
            continue

        return status


def print_results(status: ET.Element) -> bool:
    results = {}
    failed = []
    for e in status.findall("./result"):
        repo = results.get(e.get("repository"), {})
        repo[e.get("arch")] = e
        results[e.get("repository")] = repo

    for repo in results.keys():
        print(f"{repo}:")
        depth = 1
        for arch in results[repo].keys():
            counts = Counter()
            if repo != "charts":
                print(f"\t{arch}:")
                depth = 2
            for package in results[repo][arch].findall("./status"):
                if package.get("code") in ["excluded", "disabled"]:
                    continue
                if package.get("code") in ["failed", "unresolvable", "broken"]:
                    details = package.findtext("details")
                    if details:
                        failed.append(f"{package.get('package')} ({arch}): {details}")
                    else:
                        failed.append(f"{package.get('package')} ({arch})")
                counts[package.get("code")] += 1
            for code, count in counts.items():
                print("\t" * depth, f"{code}: {count}")

    failed.sort()
    if failed:
        print("\nPackages failing: ")
    for fail in failed:
        print("\t", fail)
    return len(failed)


def main():
    project = os.environ.get("OBS_PROJECT")
    sha = os.environ.get("GIT_SHA")
    print(f"Waiting for OBS to build {project} for commit {sha}")
    status = do_wait(project, sha)
    sys.exit(print_results(status))


if __name__ == "__main__":
    main()
