#!/usr/bin/env python3
"""
Annotate pull requests to the GitHub repository with links to specifications.
"""

from __future__ import annotations

from pathlib import Path
from typing import Any
import json
import re
import sys

from uritemplate import URITemplate


BIN_DIR = Path(__file__).parent
TESTS = BIN_DIR.parent / "tests"
URLS = json.loads(BIN_DIR.joinpath("specification_urls.json").read_text())


def urls(version: str) -> dict[str, URITemplate]:
    """
    Retrieve the version-specific URLs for specifications.
    """
    for_version = {**URLS["json-schema"][version], **URLS["external"]}
    return {k: URITemplate(v) for k, v in for_version.items()}


def annotation(
    path: Path,
    message: str,
    line: int = 1,
    level: str = "notice",
    **kwargs: Any,
) -> str:
    """
    Format a GitHub annotation.

    See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
    for full syntax.
    """

    if kwargs:
        additional = "," + ",".join(f"{k}={v}" for k, v in kwargs.items())
    else:
        additional = ""

    relative = path.relative_to(TESTS.parent)
    return f"::{level} file={relative},line={line}{additional}::{message}\n"


def line_number_of(path: Path, case: dict[str, Any]) -> int:
    """
    Crudely find the line number of a test case.
    """
    with path.open() as file:
        description = case["description"]
        return next(
            (i + 1 for i, line in enumerate(file, 1) if description in line),
            1,
        )

def extract_kind_and_spec(key: str) -> (str, str):
    """
    Extracts specification number and kind from the defined key
    """
    can_have_spec = ["rfc", "iso"]
    if not any(key.startswith(el) for el in can_have_spec):
        return key, ""
    number = re.search(r"\d+", key)
    spec = "" if number is None else number.group(0)
    kind = key.removesuffix(spec)
    return kind, spec


def main():
    # Clear annotations which may have been emitted by a previous run.
    sys.stdout.write("::remove-matcher owner=me::\n")

    for version in TESTS.iterdir():
        if version.name in {"draft-next", "latest"}:
            continue

        version_urls = urls(version.name)

        for path in version.rglob("*.json"):
            try:
                contents = json.loads(path.read_text())
            except json.JSONDecodeError as error:
                error = annotation(
                    level="error",
                    path=path,
                    line=error.lineno,
                    col=error.pos + 1,
                    title=str(error),
                    message=f"cannot load {path}"
                )
                sys.stdout.write(error)
                continue

            for test_case in contents:
                specifications = test_case.get("specification")
                if specifications is not None:
                    for each in specifications:
                        quote = each.pop("quote", "")
                        (key, section), = each.items()

                        (kind, spec) = extract_kind_and_spec(key)

                        url_template = version_urls[kind]
                        if url_template is None:
                            error = annotation(
                                level="error",
                                path=path,
                                line=line_number_of(path, test_case),
                                title=f"unsupported template '{kind}'",
                                message=f"cannot find a URL template for '{kind}'"
                            )
                            sys.stdout.write(error)
                            continue

                        url = url_template.expand(
                            spec=spec,
                            section=section,
                        )

                        message = f"{url}\n\n{quote}" if quote else url
                        sys.stdout.write(
                            annotation(
                                path=path,
                                line=line_number_of(path, test_case),
                                title="Specification Link",
                                message=message,
                            ),
                        )


if __name__ == "__main__":
    main()
