# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
# SPDX-License-Identifier: BSD-2-Clause
"""Run commands in a Docker container."""

from __future__ import annotations

import contextlib
import dataclasses
import os
import subprocess  # noqa: S404
import typing

if typing.TYPE_CHECKING:
    import pathlib
    from collections.abc import Iterator, Mapping
    from typing import Final


ENV_LC_ALL: Final = "C.UTF-8"


def _get_utf8_env(env: Mapping[str, str]) -> dict[str, str]:
    """Set the `LC_ALL` and `LANGUAGE` variables for a UTF-8-capable environment."""
    res: Final = dict(env)
    res["LC_ALL"] = ENV_LC_ALL
    if "LANGUAGE" in res:
        del res["LANGUAGE"]
    return res


@dataclasses.dataclass(frozen=True)
class Container:
    """A single instance of a Docker container."""

    cid: str
    """The ID of the running container."""

    image: str
    """The image the container was started from."""

    @classmethod
    @contextlib.contextmanager
    def start(
        cls,
        *,
        image: str,
        vols_ro: dict[str | pathlib.Path, str | pathlib.Path] | None = None,
    ) -> Iterator[Container]:
        """Start a container with the specified image."""
        env: Final = _get_utf8_env(os.environ)

        opts_vols: Final = (
            [] if vols_ro is None else [f"-v{src}:{dst}:ro" for src, dst in sorted(vols_ro.items())]
        )

        print(f"Starting a {image} container")
        lines: Final = subprocess.check_output(  # noqa: S603
            [  # noqa: S607
                "docker",
                "run",
                "-d",
                "--init",
                "--rm",
                *opts_vols,
                "--",
                image,
                "sleep",
                "3600",
            ],
            encoding="UTF-8",
            env=env,
        ).splitlines()
        match lines:
            case [cid]:
                try:
                    print(f"Got {image} container ID {cid}")
                    yield Container(cid=cid, image=image)
                finally:
                    print(f"Stopping the {image} container {cid}")
                    subprocess.check_call(["docker", "stop", "--", cid], env=env)  # noqa: S603,S607

            case _:
                raise RuntimeError(repr(lines))

    def run(
        self,
        cmd: list[str | pathlib.Path],
        *,
        capture_output: bool = False,
        check: bool = False,
        run_as: str | None = None,
    ) -> subprocess.CompletedProcess:
        """Run a command within the container."""
        opt_user: Final = [] if run_as is None else ["-u", run_as]

        return subprocess.run(  # noqa: S603
            [  # noqa: S607
                "docker",
                "exec",
                "-i",
                *opt_user,
                "--",
                self.cid,
                "env",
                f"LC_ALL={ENV_LC_ALL}",
                "LANGUAGE=",
                *cmd,
            ],
            capture_output=capture_output,
            check=check,
            encoding="UTF-8",
            env=_get_utf8_env(os.environ),
        )

    def check_call(self, cmd: list[str | pathlib.Path], *, run_as: str | None = None) -> None:
        """Run a command, do not capture its output, make sure it exits with code 0."""
        self.run(cmd, capture_output=False, check=True, run_as=run_as)
