#!/usr/bin/env python3
#
# usage:
#   maint/cargo-check-publishable
#
# Checks that every crate that is supposed to be published,
# looks like it could be published.
#
# Currently the only check performed is that the `package.categories`
# value is correct for crates.io.

import os
import requests
import sys
import time
from list_crates import list_crates, Crate
from typing import Any, Dict, TYPE_CHECKING

problems = 0

if not TYPE_CHECKING:
    sys.stdout.reconfigure(line_buffering=True)

api_call_memo: Dict[str, Any] = {}


def crates_io_api_call(endpoint: str, response_expect_key: str) -> Any:
    """
    Queries
        https://crates.io/api/$endpoint
    Expects to receive either
        HTTP 200 and a json document which is an object containing a key `response_expect_key`
        HTTP 404 and a json document containing a `.errors` key
    The fetched JSON is returned, or `None` for 404

    Reimplementation in Python of the shell version in `crates-io-utils.sh`
    """
    try:
        base = os.environ["CRATES_IO_URL_BASE"]
    except KeyError:
        base = "https://crates.io/api"
    url = base + "/" + endpoint

    try:
        json = api_call_memo[url]

    except KeyError:
        session = requests.Session()
        session.headers.update(
            {"User-Agent": "maint/ scripts for Tor Project CI (python)"}
        )
        time.sleep(1)

        response = session.get(url)

        try:
            json = response.json()
        except Exception:
            print("from url %s obtained %s" % (url, repr(response)), file=sys.stderr)
            raise

        if response.status_code == 404:
            if "errors" not in json:
                raise (
                    RuntimeError(
                        "404 but no `errors` URL %s: %s, %s"
                        % (url, response, repr(json))
                    )
                )
            api_call_memo[url] = None
            return None

        if response.status_code != 200:
            raise (
                RuntimeError(
                    "error data from URL %s: %s, %s"
                    % (url, response, repr(response.content))
                )
            )

        api_call_memo[url] = json

    if response_expect_key not in json:
        raise (RuntimeError("unexpected JSON data from URL %s: %s" % (url, json)))

    return json


def problem(msg: str) -> None:
    print("problem: %s" % msg, file=sys.stderr)
    global problems
    problems += 1


def prepare() -> None:
    pass


def check_crate(crate: Crate) -> None:
    # if not crate.publish:
    #    return

    print("checking crate %s" % crate.name)
    try:
        cats = crate.raw_metadata["package"]["categories"]
    except KeyError:
        cats = []

    for spec_cat in cats:
        if "::" in spec_cat:
            (cat, subcat) = spec_cat.split("::", 1)
        else:
            cat = spec_cat
            subcat = None
        info = crates_io_api_call("v1/categories/%s" % cat, "category")
        if info is None:
            problem("category %s not known at crates.io!" % repr(cat))
            continue
        info = info["category"]
        if subcat is not None:
            if not (any([s["slug"] == spec_cat for s in info["subcategories"]])):
                problem("subcategory %s not known at crates.io!" % repr(spec_cat))


prepare()

for crate in list_crates():
    check_crate(crate)

if problems:
    print("%d problems!" % problems, file=sys.stderr)
    sys.exit(1)
else:
    print("ok!")
