#!/usr/bin/env python3
"""Prove a candidate SHA may be published. Exit 0 only if nothing objected.

Run inside the consuming repository, by CI, before any push to the destination.
Every failure path exits non-zero; there is no mode in which an error is treated
as permission.
"""

import argparse
import json
import os
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, "..", "lib"))

import gates          # noqa: E402
import history        # noqa: E402
import mechanism      # noqa: E402
import policy as policy_mod  # noqa: E402


def main(argv=None):
    ap = argparse.ArgumentParser(prog="verify-publication")
    ap.add_argument("--policy-dir", required=True,
                    help="checkout of the policy repository (must not be inside --repo-root)")
    ap.add_argument("--repo", required=True, help="internal repository, owner/name")
    ap.add_argument("--sha", required=True, help="candidate commit, full lowercase hex")
    ap.add_argument("--ref", required=True, help="candidate branch short name")
    ap.add_argument("--repo-root", default=".")
    ap.add_argument("--gitea-url", help="internal Gitea base URL; required unless --no-gates")
    ap.add_argument("--token-env", default="GITEA_READ_TOKEN")
    ap.add_argument("--no-gates", action="store_true",
                    help="check policy, mechanism and history only; NEVER sufficient to publish")
    args = ap.parse_args(argv)

    repo_root = os.path.abspath(args.repo_root)
    reasons = []

    try:
        policy_set = policy_mod.PolicySet.from_directory(args.policy_dir)
        pol = policy_set.authorize(args.repo)
        policy_mod.assert_policy_is_external(pol, repo_root)
    except policy_mod.PolicyError as exc:
        _emit(["POLICY " + r for r in exc.reasons], allowed=False)
        return 1

    pinned_paths = ()
    manifest_path = os.path.join(repo_root, ".publication", "MANIFEST.json")
    if not os.path.isfile(manifest_path):
        reasons.append("MECHANISM .publication/MANIFEST.json is absent")
    else:
        try:
            manifest = mechanism.read_manifest(manifest_path)
            pinned_paths = tuple(manifest["files"]) + (".publication/MANIFEST.json",)
            reasons.extend("MECHANISM " + r for r in mechanism.verify(
                repo_root, manifest, pol.mechanism_manifest_sha256, pol.mechanism_version))
        except (json.JSONDecodeError, KeyError) as exc:
            reasons.append(f"MECHANISM MANIFEST.json is unusable ({exc})")

    cleanup = []
    try:
        reasons.extend("HISTORY " + r for r in history.verify(
            args.ref, repo_root, pol, pinned_paths=pinned_paths, warnings=cleanup))
    except history.GitError as exc:
        reasons.append(f"HISTORY {exc}")

    unproven = []
    if args.no_gates:
        reasons.append("GATES --no-gates was passed; gate proof was not attempted "
                       "and this run must not authorize a push")
    else:
        if not args.gitea_url:
            reasons.append("GATES --gitea-url is required to prove gates")
        else:
            import giteaapi  # noqa: E402
            try:
                client = giteaapi.GiteaClient(args.gitea_url, token_env=args.token_env)
            except giteaapi.ApiError as exc:
                client = None
                reasons.append(f"GATES {exc}")
            if client is not None:
                request = gates.PublicationRequest(
                    internal_repository=args.repo,
                    destination_host=pol.destination_host,
                    destination_repository=pol.destination_repository,
                    sha=args.sha, ref=args.ref)
                report = gates.evaluate(pol, request, client)
                reasons.extend("GATES " + d for d in report.denials)
                unproven = report.unproven

    _emit(reasons, allowed=not reasons, unproven=unproven, policy=pol, cleanup=cleanup)
    return 1 if reasons else 0


def _emit(reasons, allowed, unproven=(), policy=None, cleanup=()):
    if policy is not None:
        print(f"[publication] {policy.internal_repository} -> "
              f"{policy.destination_host}/{policy.destination_repository} "
              f"({policy.projection}, publisher {policy.publisher_identity})")
    print(f"[publication] verdict: {'ALLOW' if allowed else 'DENY'}")
    for r in reasons:
        print(f"[publication] DENY {r}")
    for u in unproven:
        print(f"[publication] unproven: {u}")
    if cleanup:
        print(f"[publication] {len(cleanup)} cleanup finding(s) -- recorded, not blocking:")
        for c in cleanup[:40]:
            print(f"[publication]   {c}")
        if len(cleanup) > 40:
            print(f"[publication]   ... and {len(cleanup) - 40} more")


if __name__ == "__main__":
    raise SystemExit(main())
