#!/usr/bin/env python3
"""Tiny Gitea CLI for common local automation.

Defaults are tailored for this host:
  GITEA_URL=http://192.168.33.22:3300
  GITEA_USER=gemma

Credentials are read in this order:
  1. GITEA_TOKEN (sent as token <value>)
  2. GITEA_PASSWORD (HTTP basic auth with GITEA_USER)
  3. /root/PASSWORD.md line like: gitea <password>
"""
from __future__ import annotations

import argparse
import base64
import json
import os
import re
import sys
from pathlib import Path
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen

DEFAULT_URL = "http://192.168.33.22:3300"
DEFAULT_USER = "gemma"
PASSWORD_FILE = Path(os.environ.get("GITEA_PASSWORD_FILE", "/root/PASSWORD.md"))


def load_password() -> str | None:
    if os.environ.get("GITEA_PASSWORD"):
        return os.environ["GITEA_PASSWORD"]
    try:
        text = PASSWORD_FILE.read_text(encoding="utf-8")
    except OSError:
        return None
    for line in text.splitlines():
        m = re.match(r"^\s*gitea\s+(.+?)\s*$", line, flags=re.I)
        if m:
            return m.group(1).strip()
    return None


class Gitea:
    def __init__(self, base_url: str, user: str, token: str | None, password: str | None):
        self.base_url = base_url.rstrip("/")
        self.user = user
        self.token = token
        self.password = password

    def request(self, method: str, path: str, body: dict[str, Any] | None = None, query: dict[str, Any] | None = None) -> Any:
        qs = f"?{urlencode({k: v for k, v in (query or {}).items() if v is not None})}" if query else ""
        url = f"{self.base_url}/api/v1{path}{qs}"
        data = None
        headers = {"Accept": "application/json", "User-Agent": "local-gitea-cli/0.1"}
        if body is not None:
            data = json.dumps(body).encode("utf-8")
            headers["Content-Type"] = "application/json"
        if self.token:
            headers["Authorization"] = f"token {self.token}"
        elif self.password:
            raw = f"{self.user}:{self.password}".encode("utf-8")
            headers["Authorization"] = "Basic " + base64.b64encode(raw).decode("ascii")
        req = Request(url, data=data, headers=headers, method=method.upper())
        try:
            with urlopen(req, timeout=20) as res:
                payload = res.read().decode("utf-8")
                return json.loads(payload) if payload else None
        except HTTPError as e:
            detail = e.read().decode("utf-8", "replace")
            try:
                parsed = json.loads(detail)
                detail = parsed.get("message") or parsed.get("error") or detail
            except Exception:
                pass
            raise SystemExit(f"Gitea API error {e.code} {e.reason}: {detail}") from e
        except URLError as e:
            raise SystemExit(f"Gitea connection error: {e.reason}") from e


def print_table(rows: list[dict[str, Any]], cols: list[tuple[str, str]]) -> None:
    if not rows:
        print("No repositories.")
        return
    values = [[str(row.get(key, "")) for key, _ in cols] for row in rows]
    widths = [max(len(title), *(len(v[i]) for v in values)) for i, (_, title) in enumerate(cols)]
    print("  ".join(title.ljust(widths[i]) for i, (_, title) in enumerate(cols)))
    print("  ".join("-" * widths[i] for i in range(len(cols))))
    for vals in values:
        print("  ".join(vals[i].ljust(widths[i]) for i in range(len(cols))))


def cmd_version(api: Gitea, _args: argparse.Namespace) -> None:
    print(json.dumps(api.request("GET", "/version"), indent=2))


def cmd_list(api: Gitea, args: argparse.Namespace) -> None:
    repos: list[dict[str, Any]] = []
    page = 1
    while True:
        path = f"/orgs/{args.org}/repos" if args.org else "/user/repos"
        chunk = api.request("GET", path, query={"page": page, "limit": args.limit})
        if not chunk:
            break
        repos.extend(chunk)
        if len(chunk) < args.limit:
            break
        page += 1
    if args.json:
        print(json.dumps(repos, indent=2))
        return
    rows = []
    for r in repos:
        rows.append({
            "name": r.get("full_name") or r.get("name"),
            "private": "yes" if r.get("private") else "no",
            "default": r.get("default_branch") or "",
            "ssh": r.get("ssh_url") or "",
            "html": r.get("html_url") or "",
        })
    print_table(rows, [("name", "REPO"), ("private", "PRIVATE"), ("default", "BRANCH"), ("ssh", "SSH"), ("html", "URL")])


def cmd_create(api: Gitea, args: argparse.Namespace) -> None:
    body: dict[str, Any] = {
        "name": args.name,
        "private": args.private,
        "auto_init": args.init,
    }
    if args.description:
        body["description"] = args.description
    if args.default_branch:
        body["default_branch"] = args.default_branch
    if args.gitignores:
        body["gitignores"] = args.gitignores
    if args.license:
        body["license"] = args.license
    if args.readme:
        body["readme"] = args.readme
    if args.dry_run:
        target = f"/orgs/{args.org}/repos" if args.org else "/user/repos"
        print(f"DRY RUN POST {api.base_url}/api/v1{target}")
        print(json.dumps(body, indent=2))
        return
    path = f"/orgs/{args.org}/repos" if args.org else "/user/repos"
    repo = api.request("POST", path, body=body)
    print(f"Created: {repo.get('full_name') or repo.get('name')}")
    print(f"URL:     {repo.get('html_url')}")
    print(f"SSH:     {repo.get('ssh_url')}")


def cmd_get(api: Gitea, args: argparse.Namespace) -> None:
    owner, name = args.repo.split("/", 1) if "/" in args.repo else (api.user, args.repo)
    repo = api.request("GET", f"/repos/{owner}/{name}")
    if args.json:
        print(json.dumps(repo, indent=2))
    else:
        print(f"Name:        {repo.get('full_name')}")
        print(f"Private:     {repo.get('private')}")
        print(f"Branch:      {repo.get('default_branch')}")
        print(f"URL:         {repo.get('html_url')}")
        print(f"SSH:         {repo.get('ssh_url')}")
        print(f"Description: {repo.get('description') or ''}")


def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(description="Small local Gitea CLI: list/create repos")
    p.add_argument("--url", default=os.environ.get("GITEA_URL", DEFAULT_URL), help="Gitea base URL")
    p.add_argument("--user", default=os.environ.get("GITEA_USER", DEFAULT_USER), help="Gitea username")
    p.add_argument("--token", default=os.environ.get("GITEA_TOKEN"), help="Gitea API token")
    sub = p.add_subparsers(dest="cmd", required=True)

    sp = sub.add_parser("version", help="Show server version")
    sp.set_defaults(fn=cmd_version)

    sp = sub.add_parser("list", aliases=["ls"], help="List repositories for authenticated user or org")
    sp.add_argument("--org", help="List repositories in an organization")
    sp.add_argument("--limit", type=int, default=50, help="Page size")
    sp.add_argument("--json", action="store_true", help="Print raw JSON")
    sp.set_defaults(fn=cmd_list)

    sp = sub.add_parser("create", help="Create a user or org repository")
    sp.add_argument("name", help="Repository name")
    sp.add_argument("--org", help="Create under organization instead of authenticated user")
    sp.add_argument("--description", "-d", help="Repository description")
    sp.add_argument("--private", action=argparse.BooleanOptionalAction, default=True, help="Create private repo, default true")
    sp.add_argument("--init", action=argparse.BooleanOptionalAction, default=True, help="Auto-initialize repo, default true")
    sp.add_argument("--default-branch", default="main")
    sp.add_argument("--gitignores", help="Gitea gitignore template")
    sp.add_argument("--license", help="Gitea license template")
    sp.add_argument("--readme", default="Default", help="Gitea readme template")
    sp.add_argument("--dry-run", action="store_true", help="Print request without creating")
    sp.set_defaults(fn=cmd_create)

    sp = sub.add_parser("get", help="Show one repo; accepts name or owner/name")
    sp.add_argument("repo")
    sp.add_argument("--json", action="store_true")
    sp.set_defaults(fn=cmd_get)
    return p


def main() -> None:
    args = build_parser().parse_args()
    api = Gitea(args.url, args.user, args.token, load_password())
    args.fn(api, args)


if __name__ == "__main__":
    main()
