#!/usr/bin/env python3
"""Create a release check and wait for an explicit passed gate. Python 3.10+."""
import argparse
import json
import os
import sys
import time
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlsplit
from urllib.request import HTTPRedirectHandler, Request, build_opener


class NoRedirect(HTTPRedirectHandler):
    # Never forward the API key to a redirect target.
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None


def request_json(method, url, key, timeout, body=None):
    request = Request(
        url,
        data=json.dumps(body).encode() if body is not None else None,
        headers={"X-API-Key": key, "Accept": "application/json", "Content-Type": "application/json"},
        method=method,
    )
    with build_opener(NoRedirect).open(request, timeout=timeout) as response:
        result = json.load(response)
    if not isinstance(result, dict):
        raise ValueError("The API returned an unexpected response.")
    return result


def run_check(payload, key, api_base="https://onchange.app/api/v1", timeout=600):
    base = urlsplit(api_base)
    if base.scheme != "https" or not base.hostname or base.username or base.password or base.query or base.fragment:
        raise ValueError("Use an HTTPS API base URL without credentials, query or fragment.")
    if not key or not isinstance(payload, dict) or not 1 <= timeout <= 3600:
        raise ValueError("Provide a key, a JSON request object and a timeout from 1 to 3600 seconds.")
    deadline = time.monotonic() + timeout

    def call(method, suffix, body=None):
        remaining = deadline - time.monotonic()
        if remaining <= 0:
            raise TimeoutError("Release approval deadline exceeded.")
        return request_json(method, api_base.rstrip("/") + "/release-checks" + suffix, key, min(20, remaining), body)

    result = call("POST", "", payload)
    check_id = result.get("id")
    if not isinstance(check_id, str) or not check_id:
        raise ValueError("The create response did not contain a release-check ID.")
    print("Release check created. Review it in OnChange while this job waits.", flush=True)
    while True:
        if time.monotonic() >= deadline:
            raise TimeoutError("Release approval deadline exceeded.")
        gate = result.get("gate")
        if gate == "passed":
            if result.get("status") != "completed" or result.get("review") != "approved":
                raise ValueError("Passed gate did not include a completed, approved result.")
            print("Release gate passed after approval.", flush=True)
            return 0
        if gate == "failed":
            raise ValueError("Release gate failed. Inspect incomplete evidence or the rejection in OnChange.")
        if gate != "pending":
            raise ValueError("Unknown or missing gate value; refusing to approve.")
        remaining = deadline - time.monotonic()
        if remaining <= 0:
            raise TimeoutError("Release approval deadline exceeded.")
        time.sleep(min(5, remaining))
        result = call("GET", "/" + quote(check_id, safe=""))


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("request_file", help="JSON request matching the release-check create endpoint")
    parser.add_argument("--timeout", type=int, default=600, help="Total deadline in seconds, maximum 3600")
    parser.add_argument("--api-base", default="https://onchange.app/api/v1")
    args = parser.parse_args()
    try:
        with open(args.request_file, encoding="utf-8") as source:
            payload = json.load(source)
        return run_check(payload, os.environ.get("ONCHANGE_API_KEY", ""), args.api_base, args.timeout)
    except HTTPError as error:
        print(f"Release API returned HTTP {error.code}. Gate did not pass.", file=sys.stderr)
    except (URLError, OSError, ValueError, TimeoutError):
        # Avoid echoing request URLs, secrets or arbitrary server error bodies.
        print("Release gate did not pass. Check the request, credentials, network and review deadline.", file=sys.stderr)
    return 1


if __name__ == "__main__":
    sys.exit(main())
