"""Preserve staged CI source for the app session without mutating its build checkout."""
from __future__ import annotations

import json
import os
import re
import subprocess
import sys


def git(*args: str, data: str | None = None, allowed: tuple[int, ...] = (0,)) -> str:
    result = subprocess.run(["git", *args], input=data, text=True, capture_output=True, check=False)
    if result.returncode not in allowed:
        raise ValueError("source_maintenance_git_failed")
    return result.stdout.strip()


def source_paths() -> list[str]:
    names = git("diff", "--cached", "--name-only", "-z").split("\0")
    names = [name for name in names if name]
    for name in names:
        if (name.startswith("creds/") or "\n" in name or "\r" in name
                or re.search(r"\.(?:p8|p12|pfx|pem|key|keystore|jks|mobileprovision)$", name, re.I)):
            raise ValueError("source_maintenance_secret_path_refused")
        managed = name.startswith((".github/actions/swift-app/", ".github/actions/android-app/"))
        version = re.search(r"(?:\.pbxproj|\.plist|(?:^|/)[Pp]roject\.ya?ml|\.xcconfig)$", name)
        if not (managed or version):
            raise ValueError("source_maintenance_unexpected_path")
    return names


def remote_commit(branch: str) -> str:
    rows = git("ls-remote", "--heads", "origin", f"refs/heads/{branch}").splitlines()
    if not rows:
        return ""
    if len(rows) != 1 or not re.fullmatch(r"[0-9a-f]{40}\trefs/heads/" + re.escape(branch), rows[0]):
        raise ValueError("source_maintenance_remote_identity_invalid")
    return rows[0].split()[0]


def validate_origin(repo: str, base: str) -> None:
    origin = git("config", "--get", "remote.origin.url")
    match = re.fullmatch(r"(?:https://github\.com/|git@github\.com:)([\w.-]+/[\w.-]+?)(?:\.git)?", origin)
    if not match or match[1].casefold() != repo.casefold():
        raise ValueError("source_maintenance_repository_mismatch")
    if os.environ.get("GITHUB_SHA", base) != base:
        raise ValueError("source_maintenance_build_head_changed")


def preserve(base: str, tree: str, branch: str, subject: str) -> str:
    head = remote_commit(branch)
    if not head:
        head = git("commit-tree", tree, "-p", base, data=subject.replace(" [skip ci]", "") + "\n")
        # A transport refusal is ambiguous: settle by exact remote readback, never force or rebase.
        git("push", "origin", f"{head}:refs/heads/{branch}", allowed=(0, 1, 128))
        remote = remote_commit(branch)
        if not remote:
            raise ValueError("source_maintenance_push_unconfirmed")
        head = remote
    git("fetch", "--no-tags", "origin", f"refs/heads/{branch}")
    if git("rev-parse", "FETCH_HEAD") != head:
        raise ValueError("source_maintenance_remote_changed")
    if git("show", "-s", "--format=%P %T", head) != f"{base} {tree}":
        raise ValueError("source_maintenance_candidate_mismatch")
    return head


def main() -> int:
    names = source_paths()
    if not names:
        print("source maintenance: nothing staged")
        return 0
    repo, run_id = os.environ.get("GITHUB_REPOSITORY", ""), os.environ.get("GITHUB_RUN_ID", "")
    if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", repo) or not run_id.isdecimal():
        raise ValueError("source_maintenance_run_identity_required")
    base, tree = git("rev-parse", "HEAD"), git("write-tree")
    validate_origin(repo, base)
    branch = f"task/ci-maintenance-{run_id}-{tree[:16]}"
    head = preserve(base, tree, branch, sys.argv[1] if len(sys.argv) > 1 else "Preserve CI source maintenance")
    receipt = {"schema": "gowalk-cicd/source-maintenance-pending.v1", "repo": repo,
               "base_sha": base, "head_sha": head, "branch": branch, "run_id": run_id}
    # Clear only the preserved index entries. Worktree bytes and HEAD stay exactly as the build expects.
    git("reset", "--quiet", "HEAD", "--", *names)
    print("::notice title=source_maintenance_pending::" + json.dumps(receipt, separators=(",", ":")))
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except ValueError as exc:
        print("::error::" + str(exc), file=sys.stderr)
        raise SystemExit(1) from None
