#!/usr/bin/env python3
# SPDX-License-Identifier: AGPL-3.0-only
"""Faux `gh` du banc de test — n'appelle jamais GitHub ni le vrai gh.

État des PR dans le fichier JSON `AMEESH_FAKE_GH_STATE` :
    {"prs": {"owner/repo#12": {"state": "OPEN", "mergeCommit": null}}}

Commandes comprises :
    gh pr merge <n> --repo <owner/repo> --merge|--squash|--rebase [...]
    gh pr view <n> --repo <owner/repo> --json state,mergeCommit
    gh pr list …, gh api …   plan de travail (L29) : voir tests/fake_github.py

`AMEESH_FAKE_GH_MODE` règle la fusion :
    ok (défaut)  fusionne, code 0 ;
    refuse       ne fusionne pas, code 1 (refus explicite de gh : « is not
                 mergeable », contrôle préalable, rien n'est envoyé) ;
    drop         ne fusionne pas, code 1 (coupure réseau simulée, sans effet) ;
    lose         fusionne, puis code 1 (coupure après l'effet) ;
    hang         fusionne, puis dort `AMEESH_FAKE_GH_SLEEP` secondes (défaut 30) ;
    queue        file de fusion (merge queue) : la PR est mise en file et reste
                 OPEN, code 0 (« will be added to the merge queue ») ;
    queue-lose   la PR est mise en file (reste OPEN), puis la réponse se perd :
                 code 1.
`AMEESH_FAKE_GH_VIEW` règle la lecture :
    absent       `pr view` échoue (PR introuvable) ;
    land         la file de fusion aboutit : une PR en file passe MERGED (avec
                 son commit de fusion) avant d'être lue.
Chaque appel est journalisé (une ligne JSON) dans `AMEESH_FAKE_GH_LOG`.
"""
import fcntl
import hashlib
import json
import os
import sys
import time


def log(argv):
    path = os.environ.get("AMEESH_FAKE_GH_LOG")
    if path:
        with open(path, "a", encoding="utf-8") as fh:
            fh.write(json.dumps({"argv": argv, "mode": os.environ.get("AMEESH_FAKE_GH_MODE", "ok")})
                     + "\n")


def load(path):
    try:
        with open(path, encoding="utf-8") as fh:
            return json.load(fh)
    except (OSError, ValueError):
        return {"prs": {}}


def save(path, state):
    tmp = path + ".tmp"
    with open(tmp, "w", encoding="utf-8") as fh:
        json.dump(state, fh)
    os.replace(tmp, path)


def land(pr, key):
    """La PR est fusionnée : MERGED, commit de fusion, compteur de fusions."""
    oid = hashlib.sha1(("%s-%s" % (key, time.time())).encode()).hexdigest()
    pr["state"] = "MERGED"
    pr["mergeCommit"] = {"oid": oid}
    pr["merges"] = int(pr.get("merges", 0)) + 1
    pr.pop("queued", None)


def option(argv, name):
    if name in argv:
        index = argv.index(name)
        if index + 1 < len(argv):
            return argv[index + 1]
    return None


def main():
    argv = sys.argv[1:]
    log(argv)
    path = os.environ.get("AMEESH_FAKE_GH_STATE")
    if not path:
        print("faux gh : AMEESH_FAKE_GH_STATE absent", file=sys.stderr)
        return 2
    if argv[:1] == ["api"] or argv[:2] == ["pr", "list"]:
        # plan de travail (L29) : émulation de `gh api` et `gh pr list`
        sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
        import fake_github
        stdin = sys.stdin.read() if "--input" in argv else None
        with open(path + ".lock", "a") as lock:
            fcntl.flock(lock, fcntl.LOCK_EX)
            state = load(path)
            code, out, err = fake_github.handle(state, argv, stdin)
            save(path, state)
        sys.stdout.write(out)
        sys.stderr.write(err)
        return code
    if len(argv) < 3 or argv[0] != "pr" or argv[1] not in ("merge", "view"):
        print("faux gh : commande non prise en charge : %s" % " ".join(argv), file=sys.stderr)
        return 2
    number, repo = argv[2], option(argv, "--repo")
    key = "%s#%s" % (repo, number)
    with open(path + ".lock", "a") as lock:
        fcntl.flock(lock, fcntl.LOCK_EX)
        state = load(path)
        pr = state.get("prs", {}).get(key)
        if argv[1] == "view":
            view = os.environ.get("AMEESH_FAKE_GH_VIEW")
            if pr is None or view == "absent":
                print("GraphQL: Could not resolve to a PullRequest with the number of %s."
                      % number, file=sys.stderr)
                return 1
            if view == "land" and pr.get("queued") and pr["state"] == "OPEN":
                land(pr, key)
                save(path, state)
            print(json.dumps({"state": pr["state"], "mergeCommit": pr.get("mergeCommit")}))
            return 0
        mode = os.environ.get("AMEESH_FAKE_GH_MODE", "ok")
        if pr is None:
            print("GraphQL: Could not resolve to a PullRequest with the number of %s."
                  % number, file=sys.stderr)
            return 1
        if pr["state"] != "OPEN":
            print("Pull request %s is not open (%s)" % (key, pr["state"]), file=sys.stderr)
            return 1
        if mode == "refuse":
            print("X Pull request %s is not mergeable: the merge commit cannot be cleanly "
                  "created." % key, file=sys.stderr)
            return 1
        if mode == "drop":
            print("Post \"https://api.github.com/graphql\": connection reset by peer", file=sys.stderr)
            return 1
        if mode in ("queue", "queue-lose"):
            pr["queued"] = True
            save(path, state)
        else:
            land(pr, key)
            save(path, state)
    if mode == "queue":
        print("✓ Pull request %s will be added to the merge queue for main when ready" % key)
        return 0
    if mode in ("lose", "queue-lose"):
        print("Post \"https://api.github.com/graphql\": unexpected EOF", file=sys.stderr)
        return 1
    if mode == "hang":
        time.sleep(float(os.environ.get("AMEESH_FAKE_GH_SLEEP", "30")))
    print("✓ Merged pull request %s" % key)
    return 0


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