#! /usr/bin/env nix-shell
#! nix-shell -i python -p python3.pkgs.click python3.pkgs.click-log nix
"""
electron updater

A script for updating binary hashes.

It supports the following modes:

| Mode         | Description                                     |
|------------- | ----------------------------------------------- |
| `update`     | for updating a specific Electron release        |
| `update-all` | for updating all electron releases at once      |

The `update` command requires a `--version` flag
to specify the major release to be update.
The `update-all` command updates all non-eol major releases.

The `update` and `update-all` commands accept an optional `--commit`
flag to automatically commit the changes for you.
"""
import logging
import os
import subprocess
import sys
import click
import click_log

from typing import Tuple
os.chdir(os.path.dirname(__file__))
sys.path.append("..")
from update_util import *


# Relatice path to the electron-bin info.json
BINARY_INFO_JSON = "info.json"

# Relative path to the electron-chromedriver info.json
CHROMEDRIVER_INFO_JSON = "../chromedriver/info.json"

logger = logging.getLogger(__name__)
click_log.basic_config(logger)


systems = {
    "i686-linux": "linux-ia32",
    "x86_64-linux": "linux-x64",
    "armv7l-linux": "linux-armv7l",
    "aarch64-linux": "linux-arm64",
    "aarch64-darwin": "darwin-arm64",
}

def get_shasums256(version: str) -> list:
    """Returns the contents of SHASUMS256.txt"""
    try:
        called_process: subprocess.CompletedProcess = subprocess.run(
            [
                "nix-prefetch-url",
                "--print-path",
                f"https://github.com/electron/electron/releases/download/v{version}/SHASUMS256.txt",
            ],
            capture_output=True,
            check=True,
            text=True,
        )

        hash_file_path = called_process.stdout.split("\n")[1]

        with open(hash_file_path, "r") as f:
            return f.read().split("\n")

    except subprocess.CalledProcessError as err:
        print(err.stderr)
        sys.exit(1)


def get_headers(version: str) -> str:
    """Returns the hash of the release headers tarball"""
    try:
        called_process: subprocess.CompletedProcess = subprocess.run(
            [
                "nix-prefetch-url",
                "--unpack",
                f"https://artifacts.electronjs.org/headers/dist/v{version}/node-v{version}-headers.tar.gz",
            ],
            capture_output=True,
            check=True,
            text=True,
        )
        return called_process.stdout.split("\n")[0]
    except subprocess.CalledProcessError as err:
        print(err.stderr)
        sys.exit(1)


def get_electron_hashes(major_version: str) -> dict:
    """Returns a dictionary of hashes for a given major version"""
    m, _ = get_latest_version(major_version)
    version: str = m["version"]

    out = {}
    out[major_version] = {
        "hashes": {},
        "version": version,
    }

    hashes: list = get_shasums256(version)

    for nix_system, electron_system in systems.items():
        filename = f"*electron-v{version}-{electron_system}.zip"
        if any([x.endswith(filename) for x in hashes]):
            out[major_version]["hashes"][nix_system] = [
                x.split(" ")[0] for x in hashes if x.endswith(filename)
            ][0]
            out[major_version]["hashes"]["headers"] = get_headers(version)

    return out


def get_chromedriver_hashes(major_version: str) -> dict:
    """Returns a dictionary of hashes for a given major version"""
    m, _ = get_latest_version(major_version)
    version: str = m["version"]

    out = {}
    out[major_version] = {
        "hashes": {},
        "version": version,
    }

    hashes: list = get_shasums256(version)

    for nix_system, electron_system in systems.items():
        filename = f"*chromedriver-v{version}-{electron_system}.zip"
        if any([x.endswith(filename) for x in hashes]):
            out[major_version]["hashes"][nix_system] = [
                x.split(" ")[0] for x in hashes if x.endswith(filename)
            ][0]
            out[major_version]["hashes"]["headers"] = get_headers(version)

    return out


def update_binary(major_version: str, commit: bool, chromedriver: bool) -> None:
    """Update a given electron-bin or chromedriver release

    Args:
        major_version: The major version number, e.g. '27'
        commit: Whether the updater should commit the result
    """
    if chromedriver:
        json_path=CHROMEDRIVER_INFO_JSON
        package_name = f"electron-chromedriver_{major_version}"
        update_fn=get_chromedriver_hashes
    else:
        json_path=BINARY_INFO_JSON
        package_name = f"electron_{major_version}-bin"
        update_fn = get_electron_hashes
    print(f"Updating {package_name}")

    old_info = load_info_json(json_path)
    new_info = update_fn(major_version)

    out = old_info | new_info

    save_info_json(json_path, out)

    old_version = (
        old_info[major_version]["version"] if major_version in old_info else None
    )
    new_version = new_info[major_version]["version"]
    if old_version == new_version:
        print(f"{package_name} is up-to-date")
    elif commit:
        commit_result(package_name, old_version, new_version, json_path)


@click.group()
def cli() -> None:
    """A script for updating electron-bin and chromedriver hashes"""
    pass


@cli.command("update-chromedriver", help="Update a single major release")
@click.option("-v", "--version", help="The major version, e.g. '23'")
@click.option("-c", "--commit", is_flag=True, default=False, help="Commit the result")
def update_chromedriver(version: str, commit: bool) -> None:
    update_binary(version, commit, True)


@cli.command("update", help="Update a single major release")
@click.option("-v", "--version", required=True, type=str, help="The major version, e.g. '23'")
@click.option("-c", "--commit", is_flag=True, default=False, help="Commit the result")
def update(version: str, commit: bool) -> None:
    update_binary(version, commit, False)
    update_binary(version, commit, True)


@cli.command("update-all", help="Update all releases at once")
@click.option("-c", "--commit", is_flag=True, default=False, help="Commit the result")
def update_all(commit: bool) -> None:
    # Filter out releases that have reached end-of-life
    filtered_bin_info = dict(
        filter(
            lambda entry: int(entry[0]) in supported_version_range(),
            load_info_json(BINARY_INFO_JSON).items(),
        )
    )

    for major_version, _ in filtered_bin_info.items():
        update_binary(str(major_version), commit, False)
        update_binary(str(major_version), commit, True)


if __name__ == "__main__":
    cli()
