#!/usr/bin/python3

# SPDX-FileCopyrightText: © 2024 Olivier Meunier <olivier@neokraft.net>
#
# SPDX-License-Identifier: AGPL-3.0-only

import json
import sys
from argparse import ArgumentParser
from contextlib import contextmanager
from pathlib import Path
from subprocess import check_call, check_output

BASE_IMAGE = "docker.io/library/busybox:uclibc"
ALPINE_IMAGE = "docker.io/library/alpine:latest"

BUILDAH = "/usr/bin/buildah"
SKOPEO = "/usr/bin/skopeo"
ARCHS = ["amd64", "arm64"]
IMAGE_NAME = "localhost/readeck/release"


@contextmanager
def work_container(image: str, arch: str):
    container = check_output(
        [
            BUILDAH,
            "--pull=newer",
            "from",
            f"--arch={arch}",
            image,
        ]
    )
    container = container.decode().strip()
    yield container

    check_call(
        [
            BUILDAH,
            "rm",
            container,
        ]
    )


@contextmanager
def work_manifest(name: str, images: list[str]):
    check_call([BUILDAH, "manifest", "create", name] + images)
    yield name

    check_call([BUILDAH, "manifest", "rm", name])


def build_image(version: str, arch: str):
    """
    This builds an image using Readeck binary file for the given architecture.
    """

    # Start with a busybox container
    with work_container(BASE_IMAGE, arch) as container:
        # Copy CA certificates
        check_call(
            [
                BUILDAH,
                "copy",
                "--from",
                ALPINE_IMAGE,
                container,
                "/etc/ssl/certs/ca-certificates.crt",
                "/etc/ssl/certs/ca-certificates.crt",
            ]
        )

        # Copy readeck binary
        check_call(
            [
                BUILDAH,
                "copy",
                container,
                f"dist/readeck-{version}-linux-{arch}",
                "/bin/readeck",
            ]
        )

        # Configure image
        check_call(
            [
                BUILDAH,
                "config",
                "--workingdir=/readeck",
                "--volume=/readeck",
                "--cmd=/bin/readeck serve -config config.toml",
                "--port=8000/tcp",
                "--env=READECK_SERVER_HOST=0.0.0.0",
                "--env=READECK_SERVER_PORT=",
                "--label=org.opencontainers.image.authors=olivier@readeck.com",
                f"--label=version={version}",
                container,
            ]
        )

        # Commit the image
        image = f"{IMAGE_NAME}/{arch}:{version}"
        check_call([BUILDAH, "commit", container, image])
        return image


def main():
    parser = ArgumentParser(description="Build a Readeck OCI image")
    parser.add_argument("version", help="Readeck version")
    parser.add_argument("dest", help="Destination file")
    parser.add_argument("--rm", action="store_true", help="Remove containers when done")
    args = parser.parse_args()

    dest = Path(args.dest).resolve()

    check_call([BUILDAH, "pull", "--policy=always", ALPINE_IMAGE])

    images = []
    for arch in ARCHS:
        images.append(
            build_image(args.version, arch),
        )

    manifest = f"{IMAGE_NAME}:{args.version}"
    with work_manifest(manifest, images):
        # Create a multi-arch OCI archive
        check_call(
            [
                BUILDAH,
                "manifest",
                "push",
                "--all",
                manifest,
                f"oci-archive:{dest}",
            ]
        )

    if args.rm:
        # Remove the temporary images when needed
        for arch in ARCHS:
            check_call([BUILDAH, "rmi", f"{IMAGE_NAME}/{arch}:{args.version}"])

    print(f">> {dest} created")

    data = check_output([SKOPEO, "inspect", "--raw", f"oci-archive:{dest}"])
    r = json.loads(data)
    json.dump(r, sys.stdout, indent=2)
    sys.stdout.write("\n")


if __name__ == "__main__":
    main()
