# Full, isolated test of the Funktionsklempner software.
#
# Author: Malte J. Ziebarth (malte.ziebarth@tum.de)
#
# Copyright (C) 2025 Technische Universität München
#
# Licensed under the EUPL, Version 1.2 or – as soon they will be approved by
# the European Commission - subsequent versions of the EUPL (the "Licence");
# You may not use this work except in compliance with the Licence.
# You may obtain a copy of the Licence at:
#
# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Licence is distributed on an "AS IS" basis,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the Licence for the specific language governing permissions and
# limitations under the Licence.
#
# SPDX-FileCopyrightText: 2025 Technical University of Munich
# SPDX-License-Identifier: EUPL-1.2
# SPDX-FileContributor: Malte J. Ziebarth <malte.ziebarth@tum.de>

import os
import re
import json
from subprocess import run
from pathlib import Path
from shutil import copyfileobj
from hashlib import sha256
from typing import BinaryIO, TYPE_CHECKING
from tempfile import TemporaryDirectory

# Change to the source root directory. We have to be
# there for the container build context to include the
# full Funktionsklempner source archive:
root = Path(__file__).parent.resolve()
os.chdir(root)


# 1. Perform Meson testing:
meson_test_dir = root / ".test_builddir"
run(
    ('meson','setup',meson_test_dir),
    check=True
)
os.chdir(meson_test_dir)
run(
    ('meson','test'),
    check=True
)
os.chdir(root)


# Type checking:
if TYPE_CHECKING:
    from tempfile import _TemporaryFileWrapper # pyright: ignore[reportPrivateUsage]

type JSONElement = \
    "str | float | int | list[JSONElement] | dict[str,JSONElement]"


# Batched file hashing:
def filehash(f: "BinaryIO | _TemporaryFileWrapper[bytes]") -> str:
    """
    Computes the SHA256 of a file.
    """
    Nb = 1024
    f.seek(0)
    batch = f.read(Nb)
    h = sha256()
    while batch:
        h.update(batch)
        batch = f.read(Nb)
    return h.hexdigest()


# The Python module source files (shall be synchronized with the files
# in meson.build)
SOURCES = [
    'pysrc/fklpr_base/__init__.py',
    'pysrc/fklpr_base/function.py',
    'pysrc/fklpr_base/library.py',
    'pysrc/fklpr_base/paralist.py',
    'pysrc/fklpr_base/parameter.py',
    'pysrc/fklpr_base/parse.py',
    'pysrc/fklpr_base/write.py',
    'pysrc/fklpr_base/fklpr_types/__init__.py',
    'pysrc/fklpr_base/fklpr_types/fklpr_types.py',
    'pysrc/fklpr_base/fklpr_types/preprocessor.py',
    'pysrc/fklpr_base/templates/wrapper.py',
    'pysrc/fklpr_base/scripts/__init__.py',
    'pysrc/fklpr_base/scripts/pre-commit',
]

# RegEx matching the structure of the wheel files generated by build:
re_wheel = re.compile('funktionsklempner-[0-9]+\\.[0-9]+\\.[0-9]+-py3-none-any\\.whl')

#
# First we build an up-to-date wheel of the Funktionsklempner package
# and see whether the (potentially existing) current version used in the
# test container is equally up to date.
#
with TemporaryDirectory() as tmpdir:
    # Build the wheel:
    run(
        ('python','-m','build','--wheel','--outdir',tmpdir),
        check=True
    )

    # Get the output file name:
    outdir = Path(tmpdir)
    outfile: str | None = None
    for p in outdir.iterdir():
        if not p.is_file():
            continue
        if not re_wheel.match(p.name):
            continue
        if outfile is None or outfile < p.name:
            outfile = p.name

        # Get the version string:
        version = p.name.split('funktionsklempner-')[1] \
            .split('-py3-none-any.whl')[0]
        print(f"Found wheel for version {version}")

    if outfile is None:
        raise RuntimeError(
            "Did not find a wheel matching the expected name after build. "
            f"Content of the build directory: {list(p.name for p in outdir.iterdir())}"
        )

    # Then decide whether to copy it:
    dest = root / "testsuite" / outfile
    with open(outdir / outfile, 'rb') as fsrc:
        if dest.is_file():
            # Want to check here whether the two files are identical. If so,
            # we don't need to overwrite `testsuite/funktionsklempner.zip` and
            # thereby avoid some container layer rebuilds.
            h1 = filehash(fsrc)
            with open(dest, 'rb') as fdest:
                h0 = filehash(fdest)
                if h0 != h1:
                    print(f"renewing wheel {outfile}...")
                    with open(dest, 'wb') as fdest:
                        fsrc.seek(0)
                        copyfileobj(fsrc, fdest)
                else:
                    print(f"using existing wheel {outfile}...")
        else:
            print(f"creating wheel {outfile}...")
            with open(dest, 'wb') as fdest:
                copyfileobj(fsrc, fdest)

    # Remove all other wheels:
    for path in (root / "testsuite").iterdir():
        if path == dest:
            continue
        if not re_wheel.match(path.name):
            continue
        print("removing old wheel", path)
        os.remove(path)

# Check if a machine is running:
def have_running_machine() -> bool:
    """
    This method uses `podman machine inspect` to check
    whether a machine is running.
    """
    # The command will print the status to stdout:
    res = run(
        ["podman","machine","inspect"],
        check=True,
        capture_output=True
    ).stdout.decode()
    res_json: JSONElement = json.loads(res)
    assert isinstance(res_json, list)
    machine: JSONElement
    for machine in res_json:
        assert isinstance(machine, dict)
        if machine["Name"] == "podman-machine-default":
            return machine["State"] == "running"

    raise RuntimeError(
        "Did not find machine 'podman-machine-default'."
    )


# Check if we need to start the podman default machine:
if not have_running_machine():
    run(
        ["podman","machine","start"],
        check=True
    )


# Now build the container:
run(
    [
        "podman","build","-t","funktionsklempner-test",
        "-f","testsuite/Dockerfile","."
    ],
    check=True
)