#!/usr/bin/env python3
# vi:et
# SPDX-License-Identifier: GPL-2.0-only
# Copyright (c) 2020 Max Rees
# See LICENSE for more information.
import base64     # b64decode
import logging    # info, error
import os         # environ
import shutil     # copy2, rmtree
import subprocess # check_output
import sys        # argv, exit
import tempfile   # NamedTemporaryFile
from pathlib import Path

import apkfoundry           # MOUNTS, proj_conf, SYSCONFDIR
import apkfoundry.build     # buildrepo
import apkfoundry.container # Container
import apkfoundry._log as _log
import apkfoundry._util as _util

FAKE_BEFORE = "0000000000000000000000000000000000000000"
SYS_FAIL = os.environ["SYSTEM_FAILURE_EXIT_CODE"]
BUILD_FAIL = os.environ["BUILD_FAILURE_EXIT_CODE"]

os.environ["USE_COLORS"] = "force"
_log.init(color=True, sections=True)

def sanitize_script(script):
    _util.check_call((
        "sed", "-i",
        "-e", f"s@{env.cdir}[/]*@/@g",
        "-e", "/^export 'AF_PRIVKEY'=/d",
        "-e", "/^export 'AF_PRIVKEY_B64'=/d",
        "-e", "/^export 'AF_PUBKEY'=/d",
        "-e", "0,/^'cd' \"\\/af\\/aports\"$/{//d;}",
        script,
    ))

def get_keys():
    privkey = env.get("AF_PRIVKEY_B64", "").strip()
    pubkey = env.get("AF_PUBKEY", "").strip()
    if privkey and pubkey:
        privkey = base64.b64decode(privkey)
        logging.info("Using base64-encoded key with public name '%s'", pubkey)
        return privkey, pubkey

    privkey = env.get("AF_PRIVKEY", "").strip()
    if privkey:
        if "/" in privkey:
            logging.error("AF_PRIVKEY cannot contain slashes")
            return None, None

        privkey = Path(apkfoundry.SYSCONFDIR / "keys" / env.project / privkey)
        if not privkey.is_file():
            logging.error("AF_PRIVKEY does not exist on this builder")
            return None, None

        pubkey = env.get("AF_PUBKEY", "").strip() or privkey.name + ".pub"
        logging.info(
            "Using private key '%s' with public name '%s'",
            privkey.name,
            pubkey,
        )
        privkey = privkey.read_bytes()
        return privkey, pubkey

    return None, None

def get_sources(script, env):
    _util.check_call((script,))
    (env.cdir / "af/scripts").mkdir()

    if env.mr:
        _util.check_call((
            "git", "-C", env.aportsdir,
            "fetch", env.target_url,
            f"+refs/heads/{env.ref}:refs/heads/{env.ref}",
        ))

    proj_config = env.get("AF_PROJ_CONFIG", "").strip().split(maxsplit=1)
    if proj_config:
        url = proj_config[0]
        if len(proj_config) == 2:
            branch = ["--branch", proj_config[1]]
        else:
            branch = []

        if not url.startswith(("http://", "https://", "git://")):
            logging.error("Invalid AF_PROJ_CONFIG URL: %r", url)
            return 1

        _util.check_call((
            "git", "clone",
            *branch, url, env.aportsdir / ".apkfoundry",
        ))

    else:
        if not (env.aportsdir / ".apkfoundry").is_dir():
            logging.critical("No .apkfoundry configuration directory exists!")
            return 1

    return 0

def build_script(script, env, privkey, pubkey):
    shutil.copy2(script, env.cdir / "af/scripts/gl-build")
    sanitize_script(env.cdir / "af/scripts/gl-build")
    try:
        shutil.rmtree(env.tmp)
    except FileNotFoundError:
        pass
    env.tmp.symlink_to("/tmp")
    (env.aportsdir / ".gl-repos").symlink_to("../repos")

    conf = apkfoundry.proj_conf(env.aportsdir, env.ref)

    if conf.getboolean("persistent_repodest") and not env.mr:
        repodest = f"{env.project}-{env.ref_slug}"
        repodest = apkfoundry.LOCALSTATEDIR / "repos" / repodest
    else:
        repodest = env.cdir / apkfoundry.MOUNTS["repodest"].lstrip("/")

    if env.mr:
        env.before = subprocess.check_output(
            ("git", "-C", env.aportsdir, "merge-base", env.ref, env.after),
            encoding="utf-8",
        )
        if not env.before or env.before == env.after:
            logging.error("Could not merge %s into %s", env.after, env.ref)
            return 1

    manual_pkgs = env.get("AF_PACKAGES", "").strip().split()

    if env.before == FAKE_BEFORE:
        if manual_pkgs:
            logging.info("Manual job detected, ignoring revision range")
        else:
            logging.error("New branch detected, ignoring revision range")
        rev_range = None
    elif env.get("AF_MANUAL_ONLY"):
        logging.info("Manual job detected, ignoring revision range")
        rev_range = None
    else:
        rev_range = " ".join((env.before, env.after))

    args = [
        "--directory", env.cdir,
        "--aportsdir", env.aportsdir,
        "--branch", env.ref,
        "--cache", env.cache,
        "--delete", "never",
        "--srcdest", env.srcdest,
        "--script", "/af/scripts/gl-build",
    ]

    if rev_range:
        args += ["--rev-range", rev_range]

    if privkey:
        args += ["--key", privkey, "--pubkey", pubkey]

    if env.arch:
        args += ["--arch", env.arch]

    args += [
        "--",
        repodest,
        *manual_pkgs,
    ]

    args = [str(i) for i in args]
    rc = apkfoundry.build.buildrepo(args)
    (env.cdir / "af/rc").write_text(str(rc))
    env.tmp.unlink()
    return rc

def after_script(script, env):
    shutil.copy2(script, env.cdir / "af/scripts/gl-after")
    sanitize_script(env.cdir / "af/scripts/gl-after")

    cont = apkfoundry.container.Container(env.cdir)
    return cont.run(["/af/scripts/gl-after"])[0]

env = _util.CI_Env()
script = sys.argv[1]
stage = sys.argv[2]

if stage == "get_sources":
    rc = get_sources(script, env)
elif stage == "build_script":
    privkey, pubkey = get_keys()
    with tempfile.NamedTemporaryFile() as f:
        if privkey:
            f.write(privkey)
            f.flush()
            privkey = f.name
        rc = build_script(script, env, privkey, pubkey)
elif stage == "after_script":
    if (env.cdir / "af/config").is_dir():
        rc = after_script(script, env)
    else:
        logging.error("Container was not created, skipping after_script")
        rc = 1
else:
    _util.check_call((script,))
    rc = 0

sys.exit(rc)
