#!/usr/bin/env python3
"""Create a label set on a Gitea repo and apply labels to issues.

Auth follows the project convention: credentials are pulled from the macOS
keychain via `git credential fill` (HTTPS), then sent as Basic auth.

Modes (default = create labels then apply the one-off MAPPING, preserving the
original behavior):

    ./manage_labels.py                     # create labels + apply MAPPING
    ./manage_labels.py --create-labels     # idempotent label creation only
    ./manage_labels.py --apply-mapping     # one-off MAPPING labeling only
    ./manage_labels.py --add-label 42 chore   # add one label to one issue
    ./manage_labels.py --dry ...           # print actions without writing
"""
import os
import sys

# Auto-execute using the project's local virtual environment Python
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
venv_python = os.path.join(PROJECT_ROOT, "venv", "bin", "python3")
if os.path.exists(venv_python) and sys.executable != venv_python:
    os.execv(venv_python, [venv_python] + sys.argv)

from gitea_auth import get_auth_header, api_request, api_get_all, repo_api_url
import issue_workflow_labels

HOST = "gitea.dadeschools.net"
ORG = "Contractor"
REPO = "Timesheet"

# Mirror of the eAgenda label set (i18n omitted as irrelevant to Timesheet).
LABELS = [
    {"name": "chore", "color": "5319e7", "description": ""},
    {"name": "critical", "color": "b60205", "description": ""},
    {"name": "epic", "color": "8250df", "description": ""},
    {"name": "important", "color": "fbca04", "description": ""},
    {"name": "nice-to-have", "color": "0e8a16", "description": ""},
    *[
        {"name": spec.name, "color": spec.color, "description": spec.description}
        for spec in issue_workflow_labels.CANONICAL_LABEL_SPECS
    ],
]

# issue number -> label names to apply (one-off backfill)
MAPPING = {
    23: ["chore"],
    22: ["chore"],
    21: ["chore"],
    20: ["chore", "status:in-progress"],
    19: ["chore"],
    18: ["chore"],
    17: ["important"],
    14: ["nice-to-have"],
    13: ["nice-to-have"],
    12: ["important"],
    11: ["important"],
    8: ["nice-to-have"],
    7: ["nice-to-have"],
    3: ["important"],
    2: ["important"],
    1: ["nice-to-have"],
}

BASE_URL = repo_api_url(HOST, ORG, REPO)

USAGE = (
    "usage: manage_labels.py [--dry] "
    "[--create-labels | --apply-mapping | --add-label <issue> <label>]"
)


def api(method, path, auth, payload=None):
    """Thin wrapper around auth.api_request that prepends BASE_URL and
    handles errors gracefully (returns None instead of raising)."""
    url = f"{BASE_URL}{path}"
    try:
        return api_request(method, url, auth, payload)
    except RuntimeError as e:
        print(f"  {e}", file=sys.stderr)
        return None


def _labels_by_name(auth):
    """Return {label name: id} for the repo's existing labels (all pages, #627)."""
    existing = api_get_all(f"{BASE_URL}/labels", auth) or []
    name_to_id = {}
    for lb in existing:
        if not isinstance(lb, dict):
            continue
        name = lb.get("name")
        lid = lb.get("id")
        if name and lid is not None and name not in name_to_id:
            name_to_id[name] = lid
    return name_to_id


def create_labels(auth, dry=False):
    """Idempotently create the LABELS set; return the resulting name->id map."""
    by_name = _labels_by_name(auth)
    for spec in LABELS:
        if spec["name"] in by_name:
            print(f"label exists: {spec['name']}")
            continue
        if dry:
            print(f"[dry] create label: {spec['name']} #{spec['color']}")
            continue
        created = api("POST", "/labels", auth, spec)
        if created:
            by_name[created["name"]] = created["id"]
            print(f"created label: {created['name']} (id {created['id']})")
    return by_name


def apply_mapping(auth, by_name=None, dry=False):
    """Apply the one-off MAPPING (PUT replaces each issue's label set)."""
    if by_name is None:
        by_name = _labels_by_name(auth)
    for issue, names in sorted(MAPPING.items(), reverse=True):
        ids = [by_name[n] for n in names if n in by_name]
        missing = [n for n in names if n not in by_name]
        if missing:
            print(f"  #{issue}: skipping unknown labels {missing}", file=sys.stderr)
        if dry:
            print(f"[dry] #{issue} <- {names}")
            continue
        # PUT replaces the issue's labels with exactly this set (idempotent).
        res = api("PUT", f"/issues/{issue}/labels", auth, {"labels": ids})
        if res is not None:
            applied = [lb["name"] for lb in res]
            print(f"#{issue} labeled: {applied}")


def add_label(auth, issue, label, dry=False):
    """Ad-hoc: ADD a single existing label to one issue (append, not replace)."""
    by_name = _labels_by_name(auth)
    if label not in by_name:
        print(f"  unknown label '{label}'; create it first (--create-labels)",
              file=sys.stderr)
        return False
    if dry:
        print(f"[dry] #{issue} += {label}")
        return True
    # POST appends to the issue's existing labels (does not replace).
    res = api("POST", f"/issues/{issue}/labels", auth, {"labels": [by_name[label]]})
    if res is not None:
        print(f"#{issue} += {label}")
        return True
    return False


def main(argv=None):
    argv = list(sys.argv[1:] if argv is None else argv)
    dry = "--dry" in argv or "--dry-run" in argv

    auth = get_auth_header(HOST)
    if auth is None:
        print("Could not get credentials from git credential fill",
              file=sys.stderr)
        sys.exit(1)

    if "--create-labels" in argv:
        create_labels(auth, dry=dry)
    elif "--apply-mapping" in argv:
        apply_mapping(auth, dry=dry)
    elif "--add-label" in argv:
        i = argv.index("--add-label")
        if i + 2 >= len(argv):
            print(USAGE, file=sys.stderr)
            sys.exit(2)
        try:
            issue = int(argv[i + 1])
        except ValueError:
            print(f"--add-label: issue must be a number, got '{argv[i + 1]}'",
                  file=sys.stderr)
            sys.exit(2)
        add_label(auth, issue, argv[i + 2], dry=dry)
    else:
        # Default (backward compatible): create labels, then apply the mapping.
        by_name = create_labels(auth, dry=dry)
        apply_mapping(auth, by_name, dry=dry)


if __name__ == "__main__":
    main()
