#! /usr/bin/env nix-shell
#! nix-shell -i python3 -p python3 python3.pkgs.semver nix-prefetch-github
from urllib.request import Request, urlopen
import dataclasses
import subprocess
import hashlib
import os.path
import semver
import base64
from typing import (
    Optional,
    Dict,
    List,
)
import json
import os


SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
NIXPKGS = os.path.abspath(os.path.join(SCRIPT_DIR, "../../../../"))


OWNER = "LemmyNet"
UI_REPO = "lemmy-ui"
SERVER_REPO = "lemmy"


@dataclasses.dataclass
class Pin:
    serverVersion: str
    uiVersion: str
    serverSha256: str = ""
    serverCargoSha256: str = ""
    uiSha256: str = ""
    uiYarnDepsSha256: str = ""

    filename: Optional[str] = None

    def write(self) -> None:
        if not self.filename:
            raise ValueError("No filename set")

        with open(self.filename, "w") as fd:
            pin = dataclasses.asdict(self)
            del pin["filename"]
            json.dump(pin, fd, indent=2)
            fd.write("\n")


def github_get(path: str) -> Dict:
    """Send a GET request to Gituhb, optionally adding GITHUB_TOKEN auth header"""
    url = f"https://api.github.com/{path.lstrip('/')}"
    print(f"Retreiving {url}")

    req = Request(url)

    if "GITHUB_TOKEN" in os.environ:
        req.add_header("authorization", f"Bearer {os.environ['GITHUB_TOKEN']}")

    with urlopen(req) as resp:
        return json.loads(resp.read())


def get_latest_release(owner: str, repo: str) -> str:
    return github_get(f"/repos/{owner}/{repo}/releases/latest")["tag_name"]


def sha256_url(url: str) -> str:
    sha256 = hashlib.sha256()
    with urlopen(url) as resp:
        while data := resp.read(1024):
            sha256.update(data)
    return "sha256-" + base64.urlsafe_b64encode(sha256.digest()).decode()


def prefetch_github(owner: str, repo: str, rev: str) -> str:
    """Prefetch github rev and return sha256 hash"""
    print(f"Prefetching {owner}/{repo}({rev})")

    proc = subprocess.run(
        ["nix-prefetch-github", owner, repo, "--rev", rev, "--fetch-submodules"],
        check=True,
        stdout=subprocess.PIPE,
    )

    sha256 = json.loads(proc.stdout)["sha256"]
    if not sha256.startswith("sha256-"):  # Work around bug in nix-prefetch-github
        return "sha256-" + sha256

    return sha256


def get_latest_tag(owner: str, repo: str, prerelease: bool = False) -> str:
    """Get the latest tag from a Github Repo"""
    tags: List[str] = []

    # As the Github API doesn't have any notion of "latest" for tags we need to
    # collect all of them and sort so we can figure out the latest one.
    i = 0
    while i <= 100:  # Prevent infinite looping
        i += 1
        resp = github_get(f"/repos/{owner}/{repo}/tags?page={i}")
        if not resp:
            break

        # Filter out unparseable tags
        for tag in resp:
            try:
                parsed = semver.Version.parse(tag["name"])
                if (
                    semver.Version.parse(tag["name"])
                    and not prerelease
                    and parsed.prerelease
                ):  # Filter out release candidates
                    continue
            except ValueError:
                continue
            else:
                tags.append(tag["name"])

    # Sort and return latest
    return sorted(tags, key=lambda name: semver.Version.parse(name))[-1]


def get_fod_hash(attr: str) -> str:
    """
    Get fixed output hash for attribute.
    This depends on a fixed output derivation with an empty hash.
    """

    print(f"Getting fixed output hash for {attr}")

    proc = subprocess.run(["nix-build", NIXPKGS, "-A", attr], stderr=subprocess.PIPE)
    if proc.returncode != 1:
        raise ValueError("Expected nix-build to fail")

    # Iterate list in reverse order so we get the "got:" line early
    for line in proc.stderr.decode().split("\n")[::-1]:
        cols = line.split()
        if cols and cols[0] == "got:":
            return cols[1]

    raise ValueError("No fixed output hash found")


def make_server_pin(pin: Pin, attr: str) -> None:
    pin.serverSha256 = prefetch_github(OWNER, SERVER_REPO, pin.serverVersion)
    pin.write()
    pin.serverCargoSha256 = get_fod_hash(attr)
    pin.write()


def make_ui_pin(pin: Pin, package_json: str, attr: str) -> None:
    # Save a copy of package.json
    print("Getting package.json")
    with urlopen(
        f"https://raw.githubusercontent.com/{OWNER}/{UI_REPO}/{pin.uiVersion}/package.json"
    ) as resp:
        with open(os.path.join(SCRIPT_DIR, package_json), "wb") as fd:
            fd.write(resp.read())

    pin.uiSha256 = prefetch_github(OWNER, UI_REPO, pin.uiVersion)
    pin.write()
    pin.uiYarnDepsSha256 = get_fod_hash(attr)
    pin.write()


if __name__ == "__main__":
    # Get server version
    server_version = get_latest_tag(OWNER, SERVER_REPO)

    # Get UI version (not always the same as lemmy-server)
    ui_version = get_latest_tag(OWNER, UI_REPO)

    pin = Pin(server_version, ui_version, filename=os.path.join(SCRIPT_DIR, "pin.json"))
    make_server_pin(pin, "lemmy-server")
    make_ui_pin(pin, "package.json", "lemmy-ui")
