"""Generate and validate the dockerfile."""

from dataclasses import dataclass
from pathlib import Path
import re

from homeassistant import core
from homeassistant.util import executor, thread

from .model import Config, Integration

# Don't forget to update also Dockerfile.dev when updating this.
_DOCKERFILE_SYNTAX_SHA = (
    "2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769"  # 1.23.0
)

_DOCKERFILE_SYNTAX_PATTERN = re.compile(r"# syntax=docker/dockerfile@sha256:[0-9a-f]+")
_DOCKERFILE_S6_GRACETIME_PATTERN = re.compile(r"S6_SERVICES_GRACETIME=\d+")


def _update_dockerfile(content: str, timeout: int) -> str:
    """Update the hassfest-managed parts of the Dockerfile."""
    content = _DOCKERFILE_SYNTAX_PATTERN.sub(
        f"# syntax=docker/dockerfile@sha256:{_DOCKERFILE_SYNTAX_SHA}",
        content,
        count=1,
    )
    return _DOCKERFILE_S6_GRACETIME_PATTERN.sub(
        f"S6_SERVICES_GRACETIME={timeout}", content, count=1
    )


@dataclass(frozen=True)
class _MachineConfig:
    """Machine-specific Dockerfile configuration."""

    arch: str
    packages: tuple[str, ...] = ()


_MACHINES = {
    "generic-x86-64": _MachineConfig(arch="amd64", packages=("libva-intel-driver",)),
    "green": _MachineConfig(arch="aarch64"),
    "khadas-vim3": _MachineConfig(arch="aarch64"),
    "odroid-c2": _MachineConfig(arch="aarch64"),
    "odroid-c4": _MachineConfig(arch="aarch64"),
    "odroid-m1": _MachineConfig(arch="aarch64"),
    "odroid-n2": _MachineConfig(arch="aarch64"),
    "qemuarm-64": _MachineConfig(arch="aarch64"),
    "qemux86-64": _MachineConfig(arch="amd64"),
    "raspberrypi3-64": _MachineConfig(arch="aarch64", packages=("raspberrypi-utils",)),
    "raspberrypi4-64": _MachineConfig(arch="aarch64", packages=("raspberrypi-utils",)),
    "raspberrypi5-64": _MachineConfig(arch="aarch64", packages=("raspberrypi-utils",)),
    "yellow": _MachineConfig(arch="aarch64", packages=("raspberrypi-utils",)),
}

_MACHINE_DOCKERFILE_TEMPLATE = r"""# syntax=docker/dockerfile@sha256:{dockerfile_syntax}
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker
ARG BUILD_FROM=ghcr.io/home-assistant/{arch}-homeassistant:latest
FROM ${{BUILD_FROM}}
{extra_packages}
LABEL io.hass.machine="{machine}"
"""


def _generate_machine_dockerfile(
    machine_name: str, machine_config: _MachineConfig
) -> str:
    """Generate a machine Dockerfile from configuration."""
    if machine_config.packages:
        pkg_lines = " \\\n    ".join(machine_config.packages)
        extra_packages = f"\nRUN apk --no-cache add \\\n    {pkg_lines}\n"
    else:
        extra_packages = ""

    return _MACHINE_DOCKERFILE_TEMPLATE.format(
        dockerfile_syntax=_DOCKERFILE_SYNTAX_SHA,
        arch=machine_config.arch,
        extra_packages=extra_packages,
        machine=machine_name,
    )


_HASSFEST_TEMPLATE = r"""# syntax=docker/dockerfile@sha256:{dockerfile_syntax}
# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker
FROM python:{python_version}-alpine

ENV \
    UV_SYSTEM_PYTHON=true \
    UV_EXTRA_INDEX_URL="https://wheels.home-assistant.io/musllinux-index/"

SHELL ["/bin/sh", "-o", "pipefail", "-c"]
ENTRYPOINT ["/usr/src/homeassistant/script/hassfest/docker/entrypoint.sh"]
WORKDIR "/github/workspace"

COPY --parents requirements.txt homeassistant/ script /usr/src/homeassistant/

# Uv creates a lock file in /tmp
RUN --mount=type=tmpfs,target=/tmp \
    --mount=type=bind,source=requirements_test.txt,target=/tmp/requirements_test.txt,readonly \
    --mount=type=bind,source=requirements_test_pre_commit.txt,target=/tmp/requirements_test_pre_commit.txt,readonly \
    # Required for PyTurboJPEG
    apk add --no-cache libturbojpeg \
    # Install uv at the version pinned in the requirements file
    && pip install --no-cache-dir "uv==$(awk -F'==' '/^uv==/{{print $2}}' /usr/src/homeassistant/requirements.txt)" \
    && uv pip install \
        --no-build \
        --no-cache \
        -c /usr/src/homeassistant/homeassistant/package_constraints.txt \
        -r /usr/src/homeassistant/requirements.txt \
        "pipdeptree==$(awk -F'==' '/^pipdeptree==/{{print $2}}' /tmp/requirements_test.txt)" \
        "tqdm==$(awk -F'==' '/^tqdm==/{{print $2}}' /tmp/requirements_test.txt)" \
        "ruff==$(awk -F'==' '/^ruff==/{{print $2}}' /tmp/requirements_test_pre_commit.txt)"

LABEL "name"="hassfest"
LABEL "maintainer"="Home Assistant <hello@home-assistant.io>"

LABEL "com.github.actions.name"="hassfest"
LABEL "com.github.actions.description"="Run hassfest to validate standalone integration repositories"
LABEL "com.github.actions.icon"="terminal"
LABEL "com.github.actions.color"="gray-dark"
"""


def _get_python_version(root: Path) -> str:
    """Extract the Python version from .python-version."""
    return (root / ".python-version").read_text(encoding="UTF-8").strip()


@dataclass
class File:
    """File."""

    content: str
    path: Path


def _generate_files(config: Config) -> list[File]:
    timeout = (
        core.STOPPING_STAGE_SHUTDOWN_TIMEOUT
        + core.STOP_STAGE_SHUTDOWN_TIMEOUT
        + core.FINAL_WRITE_STAGE_SHUTDOWN_TIMEOUT
        + core.CLOSE_STAGE_SHUTDOWN_TIMEOUT
        + executor.EXECUTOR_SHUTDOWN_TIMEOUT
        + thread.THREADING_SHUTDOWN_TIMEOUT
        + 10
    ) * 1000

    dockerfile_path = config.root / "Dockerfile"

    files = [
        File(
            _update_dockerfile(dockerfile_path.read_text(encoding="UTF-8"), timeout),
            dockerfile_path,
        ),
        File(
            _HASSFEST_TEMPLATE.format(
                dockerfile_syntax=_DOCKERFILE_SYNTAX_SHA,
                python_version=_get_python_version(config.root),
            ),
            config.root / "script/hassfest/docker/Dockerfile",
        ),
    ]

    for machine_name, machine_config in sorted(_MACHINES.items()):
        files.append(
            File(
                _generate_machine_dockerfile(machine_name, machine_config),
                config.root / "machine" / machine_name,
            )
        )

    return files


def validate(integrations: dict[str, Integration], config: Config) -> None:
    """Validate dockerfile."""
    docker_files = _generate_files(config)
    config.cache["docker"] = docker_files

    for file in docker_files:
        if file.content != file.path.read_text():
            config.add_error(
                "docker",
                f"File {file.path} is not up to date. Run python3 -m script.hassfest",
                fixable=True,
            )


def generate(integrations: dict[str, Integration], config: Config) -> None:
    """Generate dockerfile."""
    for file in _generate_files(config):
        file.path.write_text(file.content)
