#!/usr/bin/env python3
import os
import re
import subprocess
import sys
from urllib.parse import urlparse

import requests
import simplejson

github_token = os.environ["GITHUB_TOKEN"]
auth = (github_token, "x-oauth-basic")
repo = "getredash/redash"


def _github_request(method, path, params=None, headers={}):
    if urlparse(path).hostname != "api.github.com":
        url = "https://api.github.com/{}".format(path)
    else:
        url = path

    if params is not None:
        params = simplejson.dumps(params)

    response = requests.request(method, url, data=params, auth=auth)
    return response


def exception_from_error(message, response):
    return Exception("({}) {}: {}".format(response.status_code, message, response.json().get("message", "?")))


def rc_tag_name(version):
    return "v{}-rc".format(version)


def get_rc_release(version):
    tag = rc_tag_name(version)
    response = _github_request("get", "repos/{}/releases/tags/{}".format(repo, tag))

    if response.status_code == 404:
        return None
    elif response.status_code == 200:
        return response.json()

    raise exception_from_error("Unknown error while looking RC release: ", response)


def create_release(version, commit_sha):
    tag = rc_tag_name(version)

    params = {
        "tag_name": tag,
        "name": "{} - RC".format(version),
        "target_commitish": commit_sha,
        "prerelease": True,
    }

    response = _github_request("post", "repos/{}/releases".format(repo), params)

    if response.status_code != 201:
        raise exception_from_error("Failed creating new release", response)

    return response.json()


def upload_asset(release, filepath):
    upload_url = release["upload_url"].replace("{?name,label}", "")
    filename = filepath.split("/")[-1]

    with open(filepath) as file_content:
        headers = {"Content-Type": "application/gzip"}
        response = requests.post(
            upload_url, file_content, params={"name": filename}, headers=headers, auth=auth, verify=False
        )

    if response.status_code != 201:  # not 200/201/...
        raise exception_from_error("Failed uploading asset", response)

    return response


def remove_previous_builds(release):
    for asset in release["assets"]:
        response = _github_request("delete", asset["url"])
        if response.status_code != 204:
            raise exception_from_error("Failed deleting asset", response)


def get_changelog(commit_sha):
    latest_release = _github_request("get", "repos/{}/releases/latest".format(repo))
    if latest_release.status_code != 200:
        raise exception_from_error("Failed getting latest release", latest_release)

    latest_release = latest_release.json()
    previous_sha = latest_release["target_commitish"]

    args = [
        "git",
        "--no-pager",
        "log",
        "--merges",
        "--grep",
        "Merge pull request",
        '--pretty=format:"%h|%s|%b|%p"',
        "{}...{}".format(previous_sha, commit_sha),
    ]
    log = subprocess.check_output(args)
    changes = ["Changes since {}:".format(latest_release["name"])]

    for line in log.split("\n"):
        try:
            sha, subject, body, parents = line[1:-1].split("|")
        except ValueError:
            continue

        try:
            pull_request = re.match(r"Merge pull request #(\d+)", subject).groups()[0]
            pull_request = " #{}".format(pull_request)
        except Exception:
            pull_request = ""

        author = subprocess.check_output(["git", "log", "-1", '--pretty=format:"%an"', parents.split(" ")[-1]])[1:-1]

        changes.append("{}{}: {} ({})".format(sha, pull_request, body.strip(), author))

    return "\n".join(changes)


def update_release_commit_sha(release, commit_sha):
    params = {
        "target_commitish": commit_sha,
    }

    response = _github_request("patch", "repos/{}/releases/{}".format(repo, release["id"]), params)

    if response.status_code != 200:
        raise exception_from_error("Failed updating commit sha for existing release", response)

    return response.json()


def update_release(version, build_filepath, commit_sha):
    try:
        release = get_rc_release(version)
        if release:
            release = update_release_commit_sha(release, commit_sha)
        else:
            release = create_release(version, commit_sha)

        print("Using release id: {}".format(release["id"]))

        remove_previous_builds(release)
        response = upload_asset(release, build_filepath)

        changelog = get_changelog(commit_sha)

        response = _github_request("patch", release["url"], {"body": changelog})
        if response.status_code != 200:
            raise exception_from_error("Failed updating release description", response)

    except Exception as ex:
        print(ex)


if __name__ == "__main__":
    commit_sha = sys.argv[1]
    version = sys.argv[2]
    filepath = sys.argv[3]

    # TODO: make sure running from git directory & remote = repo
    update_release(version, filepath, commit_sha)
