#!/usr/bin/env python3
"""Attended first bootstrap for CCR-2026-0014 (silent on success)."""

from __future__ import annotations

import argparse
import datetime as dt
import json
import os
from pathlib import Path
import secrets
import subprocess
import sys
import urllib.error
import urllib.parse
import urllib.request


API_ROOT = "https://forgejo.coulomb.social/api/v1"
BAO_PATH = "platform/workloads/policy-nexus/forgejo-source-read"
ORG = "coulomb"
REPO = "policy-nexus"
USER = "policy-nexus-source"
TEAM = "policy-nexus-source-readers"
SECRET = "FORGEJO_SOURCE_TOKEN"
TOKEN_PREFIX = "policy-nexus-source-read-"
SCOPES = ["read:repository"]
REPO_DIR = Path(__file__).resolve().parent.parent
NON_CODE_UNITS = (
    "repo.actions",
    "repo.packages",
    "repo.issues",
    "repo.ext_issues",
    "repo.wiki",
    "repo.pulls",
    "repo.releases",
    "repo.projects",
    "repo.ext_wiki",
)


class ProvisionError(RuntimeError):
    pass


def api_request(
    token: str,
    method: str,
    path: str,
    *,
    payload: dict[str, object] | None = None,
    expected: tuple[int, ...] = (200,),
    read_body: bool = True,
) -> tuple[int, object | None]:
    data = None if payload is None else json.dumps(payload).encode()
    request = urllib.request.Request(
        API_ROOT + path,
        data=data,
        method=method,
        headers={
            "Authorization": f"token {token}",
            "Accept": "application/json",
            **({"Content-Type": "application/json"} if data is not None else {}),
        },
    )
    try:
        with urllib.request.urlopen(request, timeout=30) as response:
            status = response.status
            body = response.read() if read_body else response.read(1)
    except urllib.error.HTTPError as error:
        status = error.code
        body = b""
    if status not in expected:
        raise ProvisionError(f"{method} {path} returned HTTP {status}")
    if not read_body or not body:
        return status, None
    try:
        return status, json.loads(body)
    except json.JSONDecodeError as error:
        raise ProvisionError(f"{method} {path} returned invalid JSON") from error


def bao(args: list[str], *, stdin: bytes | None = None) -> subprocess.CompletedProcess[bytes]:
    return subprocess.run(
        ["bao", *args],
        cwd=REPO_DIR,
        input=stdin,
        capture_output=True,
        check=False,
        timeout=60,
    )


def require_empty_destinations(admin_token: str, existing_tokens: list[dict[str, object]]) -> None:
    if any(str(item.get("name", "")).startswith(TOKEN_PREFIX) for item in existing_tokens):
        raise ProvisionError("a Policy Nexus source PAT already exists; use the rotation procedure")
    _, action_secrets = api_request(
        admin_token,
        "GET",
        f"/repos/{ORG}/{REPO}/actions/secrets?limit=100",
    )
    if not isinstance(action_secrets, list):
        raise ProvisionError("Forgejo returned an invalid Actions-secret list")
    if any(item.get("name") == SECRET for item in action_secrets):
        raise ProvisionError("FORGEJO_SOURCE_TOKEN already exists; use the rotation procedure")
    if bao(["kv", "get", "-field=FORGEJO_SOURCE_TOKEN", BAO_PATH]).returncode == 0:
        raise ProvisionError("the OpenBao destination already contains a source token")


def ensure_user(admin_token: str) -> None:
    status, user = api_request(admin_token, "GET", f"/users/{USER}", expected=(200, 404))
    if status == 404:
        _, user = api_request(
            admin_token,
            "POST",
            "/admin/users",
            payload={
                "username": USER,
                "email": "policy-nexus-source@coulomb.social",
                "full_name": "Policy Nexus source reader",
                "password": secrets.token_urlsafe(48),
                "restricted": True,
                "must_change_password": False,
                "send_notify": False,
                "visibility": "private",
            },
            expected=(201,),
        )
    if not isinstance(user, dict):
        raise ProvisionError("Forgejo returned an invalid service user")
    if user.get("login") != USER or user.get("restricted") is not True or user.get("is_admin") is True:
        raise ProvisionError("service user does not satisfy the restricted non-admin contract")


def ensure_team(admin_token: str) -> int:
    _, teams = api_request(admin_token, "GET", f"/orgs/{ORG}/teams?limit=100")
    if not isinstance(teams, list):
        raise ProvisionError("Forgejo returned an invalid team list")
    team = next((item for item in teams if item.get("name") == TEAM), None)
    if team is None:
        units_map = {unit: "none" for unit in NON_CODE_UNITS}
        units_map["repo.code"] = "read"
        _, team = api_request(
            admin_token,
            "POST",
            f"/orgs/{ORG}/teams",
            payload={
                "name": TEAM,
                "description": "Read-only source acquisition for Policy Nexus publication",
                "permission": "read",
                "includes_all_repositories": True,
                "can_create_org_repo": False,
                "units": ["repo.code"],
                "units_map": units_map,
            },
            expected=(201,),
        )
    if not isinstance(team, dict) or not isinstance(team.get("id"), int):
        raise ProvisionError("Forgejo returned an invalid source-reader team")
    units_map = team.get("units_map") or {}
    if (
        team.get("permission") != "read"
        or team.get("includes_all_repositories") is not True
        or team.get("can_create_org_repo") is not False
        or units_map.get("repo.code") != "read"
        or any(units_map.get(unit, "none") != "none" for unit in NON_CODE_UNITS)
    ):
        raise ProvisionError("source-reader team does not satisfy the least-privilege contract")
    team_id = int(team["id"])
    api_request(
        admin_token,
        "PUT",
        f"/teams/{team_id}/members/{USER}",
        expected=(204,),
    )
    return team_id


def verify_source_token(source_token: str, scopes: object) -> None:
    if scopes != SCOPES:
        raise ProvisionError("Forgejo returned scopes broader than read:repository")
    _, branch = api_request(source_token, "GET", f"/repos/{ORG}/{REPO}/branches/main")
    if not isinstance(branch, dict) or not isinstance(branch.get("commit"), dict):
        raise ProvisionError("source token could not resolve the main branch")
    revision = branch["commit"].get("id")
    if not isinstance(revision, str) or len(revision) != 40:
        raise ProvisionError("source token returned an invalid main revision")
    api_request(
        source_token,
        "GET",
        f"/repos/{ORG}/{REPO}/archive/{urllib.parse.quote(revision)}.tar.gz",
        expected=(200,),
        read_body=False,
    )
    api_request(source_token, "GET", "/admin/users", expected=(403, 404))
    api_request(
        source_token,
        "GET",
        f"/repos/{ORG}/{REPO}/actions/secrets?limit=1",
        expected=(403, 404),
    )


def verify_effective_permission(admin_token: str) -> None:
    _, permission = api_request(
        admin_token,
        "GET",
        f"/repos/{ORG}/{REPO}/collaborators/{USER}/permission",
    )
    if not isinstance(permission, dict) or permission.get("permission") != "read":
        raise ProvisionError("service user effective repository permission is not read-only")


def record_evidence(run_id: int | None, token_name: str) -> None:
    detail = (
        f"Restricted user {USER} and all-repository repo.code-read team {TEAM} verified; "
        f"PAT {token_name} reports exactly read:repository; archive read passed; effective repository "
        f"permission is read-only; Actions-secret read and instance-admin probes were denied; OpenBao and repository "
        f"Actions secret were populated without value output; workflow dispatch run_id={run_id}."
    )
    result = subprocess.run(
        [
            sys.executable,
            "scripts/credential-change.py",
            "record-evidence",
            "CCR-2026-0014",
            "--actor",
            "attended operator via governed platform-admin lane",
            "--kind",
            "forgejo_source_bootstrap",
            "--result",
            "passed",
            "--detail",
            detail,
            "--status",
            "applied",
            "--record-state-hub",
        ],
        cwd=REPO_DIR,
        capture_output=True,
        check=False,
        timeout=120,
    )
    if result.returncode != 0:
        raise ProvisionError("credential evidence recording failed")


def provision(admin_token: str) -> None:
    ensure_user(admin_token)
    ensure_team(admin_token)
    verify_effective_permission(admin_token)
    _, existing_tokens = api_request(admin_token, "GET", f"/users/{USER}/tokens?limit=100")
    if not isinstance(existing_tokens, list):
        raise ProvisionError("Forgejo returned an invalid token list")
    require_empty_destinations(admin_token, existing_tokens)

    timestamp = dt.datetime.now(dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    token_name = TOKEN_PREFIX + timestamp
    token_id: int | None = None
    source_token = ""
    bao_written = False
    activated = False
    try:
        _, created = api_request(
            admin_token,
            "POST",
            f"/users/{USER}/tokens",
            payload={"name": token_name, "scopes": SCOPES},
            expected=(201,),
        )
        if not isinstance(created, dict) or not isinstance(created.get("id"), int):
            raise ProvisionError("Forgejo returned an invalid access-token record")
        token_id = int(created["id"])
        source_token = str(created.get("sha1") or "")
        if not source_token:
            raise ProvisionError("Forgejo did not return the new access token")
        verify_source_token(source_token, created.get("scopes"))

        stored = bao(
            [
                "kv",
                "put",
                BAO_PATH,
                "FORGEJO_SOURCE_TOKEN=-",
                f"API_USER={USER}",
                "API_BASE_URL=https://forgejo.coulomb.social",
                "TOKEN_SCOPES=read:repository",
                f"GENERATED_AT={timestamp}",
            ],
            stdin=source_token.encode(),
        )
        if stored.returncode != 0:
            raise ProvisionError("OpenBao source-token write failed")
        bao_written = True
        readback = bao(["kv", "get", "-field=FORGEJO_SOURCE_TOKEN", BAO_PATH])
        if readback.returncode != 0 or readback.stdout.strip() != source_token.encode():
            raise ProvisionError("OpenBao source-token readback mismatch")

        api_request(
            admin_token,
            "PUT",
            f"/repos/{ORG}/{REPO}/actions/secrets/{SECRET}",
            payload={"data": source_token},
            expected=(201, 204),
        )
        activated = True
        _, dispatch = api_request(
            admin_token,
            "POST",
            f"/repos/{ORG}/{REPO}/actions/workflows/publish-image.yaml/dispatches",
            payload={"ref": "main", "return_run_info": True},
            expected=(201, 204),
        )
        run_id = dispatch.get("id") if isinstance(dispatch, dict) else None
        record_evidence(run_id if isinstance(run_id, int) else None, token_name)
    except Exception:
        if not activated and token_id is not None:
            try:
                api_request(
                    admin_token,
                    "DELETE",
                    f"/users/{USER}/tokens/{token_id}",
                    expected=(204, 404),
                )
            except Exception:
                pass
        if bao_written and not activated:
            bao(["kv", "delete", BAO_PATH])
        raise
    finally:
        source_token = ""


def outer() -> int:
    result = subprocess.run(
        [
            "warden",
            "access",
            "forgejo-admin-api-token",
            "--field",
            "API_TOKEN",
            "--exec",
            "--",
            sys.executable,
            str(Path(__file__).resolve()),
            "--inner",
        ],
        cwd=REPO_DIR,
        capture_output=True,
        check=False,
        timeout=900,
    )
    return result.returncode


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--inner", action="store_true", help=argparse.SUPPRESS)
    args = parser.parse_args()
    if not args.inner:
        return outer()
    admin_token = os.environ.get("API_TOKEN", "")
    if not admin_token:
        raise ProvisionError("Warden did not provide API_TOKEN")
    provision(admin_token)
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except Exception:
        # The surrounding Warden lane records only the failure and never receives
        # provider response bodies or credential material.
        raise SystemExit(1) from None
