#!/usr/bin/env python3
"""gitea — git.saqut.com (Gitea) CLI (MWSE otonom ajanı için).

- Cloudflare 1010'u aşmak için tarayıcı User-Agent'ı kullanır (düz curl takılır).
- Kimlik: önce ortam değişkenleri (GITEA_HOST/GITEA_USER/GITEA_TOKEN|GITEA_PASS/GITEA_REPO),
  yoksa repo kökündeki .gitea-auth.json (gitignore'lu).
- Varsayılan repo: saqut/MWSE (.gitea-auth.json içindeki "repo" ile değiştirilebilir).

Örnekler:
  ./tools/gitea issue list --state open
  ./tools/gitea issue view 22
  ./tools/gitea issue comment 22 --body "WSTS portu başladı"
  ./tools/gitea issue close 21 --comment "tamamlandı, testler yeşil"
  ./tools/gitea issue label 33 --add bug --remove docs
  ./tools/gitea milestone list
  ./tools/gitea wiki list ; ./tools/gitea wiki view Home
  ./tools/gitea pr create --title "Go engine 0.1.0" --head go-rewrite --base stable --body-file PR.md
  ./tools/gitea pr list
"""
import os, sys, json, base64, argparse, urllib.request, urllib.error, urllib.parse

UA = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
HOST = USER = AUTHHDR = REPO = None

def _find_auth():
    here = os.path.dirname(os.path.abspath(__file__))
    for d in (os.getcwd(), here, os.path.dirname(here)):
        p = os.path.join(d, ".gitea-auth.json")
        if os.path.isfile(p):
            return p
    return None

def load_auth():
    global HOST, USER, AUTHHDR, REPO
    host = os.environ.get("GITEA_HOST"); user = os.environ.get("GITEA_USER")
    token = os.environ.get("GITEA_TOKEN"); pw = os.environ.get("GITEA_PASS")
    repo = os.environ.get("GITEA_REPO")
    if not (host and user and (token or pw)):
        f = _find_auth()
        if f:
            try:
                d = json.load(open(f, encoding="utf-8"))
            except Exception as e:
                sys.exit(f"HATA: {f} okunamadı: {e}")
            host = host or d.get("host"); user = user or d.get("user")
            token = token or d.get("token"); pw = pw or d.get("password")
            repo = repo or d.get("repo")
    if not (host and user and (token or pw)):
        sys.exit("HATA: kimlik yok. Repo kökünde .gitea-auth.json oluştur "
                 "(host,user,password|token) ya da GITEA_* ortam değişkenlerini ver.")
    HOST = host.rstrip("/")
    USER = user
    AUTHHDR = ("token " + token) if token else ("Basic " + base64.b64encode(f"{user}:{pw}".encode()).decode())
    REPO = repo or "saqut/MWSE"

def api(method, path, body=None):
    data = json.dumps(body).encode() if body is not None else None
    req = urllib.request.Request(HOST + "/api/v1" + path, data=data, method=method)
    req.add_header("User-Agent", UA)
    req.add_header("Authorization", AUTHHDR)
    req.add_header("Accept", "application/json")
    if data is not None:
        req.add_header("Content-Type", "application/json")
    try:
        with urllib.request.urlopen(req, timeout=40) as r:
            t = r.read().decode()
            return r.status, (json.loads(t) if t.strip() else {})
    except urllib.error.HTTPError as e:
        t = e.read().decode()
        try: t = json.loads(t)
        except Exception: pass
        return e.code, t
    except Exception as e:
        return "ERR", str(e)

def R(p): return f"/repos/{REPO}{p}"
def die(s, r): sys.exit(f"API hata {s}: {r}")
def ok(s): return s in (200, 201, 204)

def body_text(a):
    if getattr(a, "body_file", None):
        return open(a.body_file, encoding="utf-8").read()
    return a.body or ""

def labels_map():
    s, r = api("GET", R("/labels?limit=100"))
    if not ok(s): die(s, r)
    return {x["name"]: x["id"] for x in r}

def milestone_id(title):
    s, r = api("GET", R("/milestones?state=all&limit=100"))
    if not ok(s): die(s, r)
    for m in r:
        if m["title"] == title: return m["id"]
    sys.exit(f"milestone bulunamadı: {title}")

# ---- issue ----
def c_issue_list(a):
    q = f"?state={a.state}&type=issues&limit={a.limit}"
    if a.labels: q += "&labels=" + urllib.parse.quote(a.labels)
    if a.milestone: q += "&milestones=" + urllib.parse.quote(a.milestone)
    s, r = api("GET", R("/issues" + q))
    if not ok(s): die(s, r)
    if a.json: print(json.dumps(r, ensure_ascii=False, indent=2)); return
    for i in r:
        ms = (i.get("milestone") or {}).get("title", "-")
        lbl = ",".join(l["name"] for l in i.get("labels", []))
        print(f"#{i['number']:<3} [{i['state']:<6}] ({ms:<6}) {i['title']}" + (f"  [{lbl}]" if lbl else ""))
    print(f"-- {len(r)} issue", file=sys.stderr)

def c_issue_view(a):
    s, i = api("GET", R(f"/issues/{a.num}"))
    if not ok(s): die(s, i)
    ms = (i.get("milestone") or {}).get("title", "-")
    lbl = ",".join(l["name"] for l in i.get("labels", []))
    print(f"#{i['number']} [{i['state']}]  milestone={ms}  labels={lbl}\n{i['title']}\n{'-'*60}\n{i.get('body') or ''}")
    s, c = api("GET", R(f"/issues/{a.num}/comments"))
    if ok(s) and c:
        print("\n--- yorumlar ---")
        for cm in c: print(f"@{cm['user']['login']}: {cm['body']}")

def c_issue_create(a):
    b = {"title": a.title, "body": body_text(a)}
    if a.milestone: b["milestone"] = milestone_id(a.milestone)
    if a.labels:
        m = labels_map(); b["labels"] = [m[x] for x in a.labels.split(",") if x in m]
    s, r = api("POST", R("/issues"), b)
    print(f"oluşturuldu #{r['number']}" if ok(s) else f"hata {s}: {r}")

def c_issue_close(a):
    if a.comment: api("POST", R(f"/issues/{a.num}/comments"), {"body": a.comment})
    s, r = api("PATCH", R(f"/issues/{a.num}"), {"state": "closed"})
    print(f"kapatıldı #{a.num}" if ok(s) else f"hata {s}: {r}")

def c_issue_reopen(a):
    s, r = api("PATCH", R(f"/issues/{a.num}"), {"state": "open"})
    print(f"açıldı #{a.num}" if ok(s) else f"hata {s}: {r}")

def c_issue_comment(a):
    s, r = api("POST", R(f"/issues/{a.num}/comments"), {"body": body_text(a)})
    print("yorum eklendi" if ok(s) else f"hata {s}: {r}")

def c_issue_edit(a):
    b = {}
    if a.title: b["title"] = a.title
    if a.body or a.body_file: b["body"] = body_text(a)
    if a.milestone: b["milestone"] = milestone_id(a.milestone)
    s, r = api("PATCH", R(f"/issues/{a.num}"), b)
    print(f"güncellendi #{a.num}" if ok(s) else f"hata {s}: {r}")

def c_issue_label(a):
    m = labels_map()
    if a.add:
        ids = [m[x] for x in a.add.split(",") if x in m]
        s, r = api("POST", R(f"/issues/{a.num}/labels"), {"labels": ids})
        print(f"eklendi: {a.add}" if ok(s) else f"hata {s}: {r}")
    if a.remove:
        for x in a.remove.split(","):
            if x in m: api("DELETE", R(f"/issues/{a.num}/labels/{m[x]}"))
        print(f"çıkarıldı: {a.remove}")

# ---- label / milestone ----
def c_label_list(a):
    s, r = api("GET", R("/labels?limit=100"))
    if not ok(s): die(s, r)
    for l in r: print(f"{l['id']:<4} {l['name']:<16} #{l['color']}")

def c_milestone_list(a):
    s, r = api("GET", R("/milestones?state=all&limit=100"))
    if not ok(s): die(s, r)
    for m in r:
        print(f"{m['title']:<8} açık={m['open_issues']:<3} kapalı={m['closed_issues']:<3} {m.get('description','')[:60]}")

# ---- wiki ----
def c_wiki_list(a):
    s, r = api("GET", R("/wiki/pages?limit=100"))
    if not ok(s): die(s, r)
    for p in r: print(p["title"])

def c_wiki_view(a):
    s, r = api("GET", R("/wiki/page/" + urllib.parse.quote(a.page)))
    if not ok(s): die(s, r)
    print(base64.b64decode(r["content_base64"]).decode("utf-8", "replace"))

def c_wiki_edit(a):
    content = open(a.content_file, encoding="utf-8").read()
    cb = base64.b64encode(content.encode()).decode()
    s, _ = api("GET", R("/wiki/page/" + urllib.parse.quote(a.page)))
    if ok(s):
        s, r = api("PATCH", R("/wiki/page/" + urllib.parse.quote(a.page)),
                   {"title": a.page, "content_base64": cb, "message": a.message})
    else:
        s, r = api("POST", R("/wiki/new"), {"title": a.page, "content_base64": cb, "message": a.message})
    print("wiki yazıldı" if ok(s) else f"hata {s}: {r}")

# ---- pr ----
def c_pr_list(a):
    s, r = api("GET", R(f"/pulls?state={a.state}&limit=50"))
    if not ok(s): die(s, r)
    for p in r:
        print(f"#{p['number']} [{p['state']}] {p['head']['ref']}→{p['base']['ref']}  {p['title']}")

def c_pr_view(a):
    s, p = api("GET", R(f"/pulls/{a.num}"))
    if not ok(s): die(s, p)
    print(f"#{p['number']} [{p['state']}] {p['head']['ref']}→{p['base']['ref']}\n{p['title']}\n{'-'*60}\n{p.get('body') or ''}")

def c_pr_create(a):
    s, r = api("POST", R("/pulls"), {"title": a.title, "body": body_text(a), "head": a.head, "base": a.base})
    print(f"PR #{r['number']} oluşturuldu" if ok(s) else f"hata {s}: {r}")

def build_parser():
    p = argparse.ArgumentParser(prog="gitea", description="git.saqut.com CLI")
    sub = p.add_subparsers(dest="grp", required=True)

    gi = sub.add_parser("issue").add_subparsers(dest="act", required=True)
    x = gi.add_parser("list"); x.add_argument("--state", default="open", choices=["open","closed","all"])
    x.add_argument("--labels"); x.add_argument("--milestone"); x.add_argument("--limit", default=50); x.add_argument("--json", action="store_true"); x.set_defaults(f=c_issue_list)
    x = gi.add_parser("view"); x.add_argument("num"); x.set_defaults(f=c_issue_view)
    x = gi.add_parser("create"); x.add_argument("--title", required=True); x.add_argument("--body"); x.add_argument("--body-file"); x.add_argument("--milestone"); x.add_argument("--labels"); x.set_defaults(f=c_issue_create)
    x = gi.add_parser("edit"); x.add_argument("num"); x.add_argument("--title"); x.add_argument("--body"); x.add_argument("--body-file"); x.add_argument("--milestone"); x.set_defaults(f=c_issue_edit)
    x = gi.add_parser("close"); x.add_argument("num"); x.add_argument("--comment"); x.set_defaults(f=c_issue_close)
    x = gi.add_parser("reopen"); x.add_argument("num"); x.set_defaults(f=c_issue_reopen)
    x = gi.add_parser("comment"); x.add_argument("num"); x.add_argument("--body"); x.add_argument("--body-file"); x.set_defaults(f=c_issue_comment)
    x = gi.add_parser("label"); x.add_argument("num"); x.add_argument("--add"); x.add_argument("--remove"); x.set_defaults(f=c_issue_label)

    gl = sub.add_parser("label").add_subparsers(dest="act", required=True)
    gl.add_parser("list").set_defaults(f=c_label_list)

    gm = sub.add_parser("milestone").add_subparsers(dest="act", required=True)
    gm.add_parser("list").set_defaults(f=c_milestone_list)

    gw = sub.add_parser("wiki").add_subparsers(dest="act", required=True)
    gw.add_parser("list").set_defaults(f=c_wiki_list)
    x = gw.add_parser("view"); x.add_argument("page"); x.set_defaults(f=c_wiki_view)
    x = gw.add_parser("edit"); x.add_argument("page"); x.add_argument("--content-file", required=True); x.add_argument("--message", default="wiki güncelleme"); x.set_defaults(f=c_wiki_edit)

    gp = sub.add_parser("pr").add_subparsers(dest="act", required=True)
    x = gp.add_parser("list"); x.add_argument("--state", default="open", choices=["open","closed","all"]); x.set_defaults(f=c_pr_list)
    x = gp.add_parser("view"); x.add_argument("num"); x.set_defaults(f=c_pr_view)
    x = gp.add_parser("create"); x.add_argument("--title", required=True); x.add_argument("--body"); x.add_argument("--body-file"); x.add_argument("--head", required=True); x.add_argument("--base", default="stable"); x.set_defaults(f=c_pr_create)
    return p

def main():
    args = build_parser().parse_args()
    load_auth()
    args.f(args)

if __name__ == "__main__":
    main()
