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

from dataclasses import dataclass
from pathlib import Path

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

from .model import Config, Integration
from .requirements import PACKAGE_REGEX, PIP_VERSION_RANGE_SEPARATOR

_GO2RTC_SHA = (
    "675c318b23c06fd862a61d262240c9a63436b4050d177ffc68a32710d9e05bae"  # 1.9.14
)

DOCKERFILE_TEMPLATE = r"""# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker
ARG BUILD_FROM
FROM ${{BUILD_FROM}}

LABEL \
    io.hass.type="core" \
    org.opencontainers.image.authors="The Home Assistant Authors" \
    org.opencontainers.image.description="Open-source home automation platform running on Python 3" \
    org.opencontainers.image.documentation="https://www.home-assistant.io/docs/" \
    org.opencontainers.image.licenses="Apache-2.0" \
    org.opencontainers.image.source="https://github.com/home-assistant/core" \
    org.opencontainers.image.title="Home Assistant" \
    org.opencontainers.image.url="https://www.home-assistant.io/"

# Synchronize with homeassistant/core.py:async_stop
ENV \
    S6_SERVICES_GRACETIME={timeout} \
    UV_SYSTEM_PYTHON=true \
    UV_NO_CACHE=true

# Home Assistant S6-Overlay
COPY rootfs /

# Add go2rtc binary
COPY --from=ghcr.io/alexxit/go2rtc@sha256:{go2rtc} /usr/local/bin/go2rtc /bin/go2rtc

RUN \
    # Verify go2rtc can be executed
    go2rtc --version \
    # Install uv
    && pip3 install uv=={uv}

WORKDIR /usr/src

## Setup Home Assistant Core dependencies
COPY requirements.txt homeassistant/
COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
RUN \
    uv pip install \
        --no-build \
        -r homeassistant/requirements.txt

COPY requirements_all.txt home_assistant_frontend-* home_assistant_intents-* homeassistant/
RUN \
    if ls homeassistant/home_assistant_*.whl 1> /dev/null 2>&1; then \
        uv pip install homeassistant/home_assistant_*.whl; \
    fi \
    && uv pip install \
        --no-build \
        -r homeassistant/requirements_all.txt

## Setup Home Assistant Core
COPY . homeassistant/
RUN \
    uv pip install \
        -e ./homeassistant \
    && python3 -m compileall \
        homeassistant/homeassistant

WORKDIR /config
"""

_HASSFEST_TEMPLATE = r"""# Automatically generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker
FROM python:3.14-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 . /usr/src/homeassistant

# Uv is only needed during build
RUN --mount=from=ghcr.io/astral-sh/uv:{uv},source=/uv,target=/bin/uv \
    # Uv creates a lock file in /tmp
    --mount=type=tmpfs,target=/tmp \
    # Required for PyTurboJPEG
    apk add --no-cache libturbojpeg \
    && uv pip install \
        --no-build \
        --no-cache \
        -c /usr/src/homeassistant/homeassistant/package_constraints.txt \
        -r /usr/src/homeassistant/requirements.txt \
        pipdeptree=={pipdeptree} \
        tqdm=={tqdm} \
        ruff=={ruff}

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_package_versions(file: Path, packages: set[str]) -> dict[str, str]:
    package_versions: dict[str, str] = {}
    with file.open(encoding="UTF-8") as fp:
        for _, line in enumerate(fp):
            if package_versions.keys() == packages:
                return package_versions

            if match := PACKAGE_REGEX.match(line):
                pkg, sep, version = match.groups()

                if pkg not in packages:
                    continue

                if sep != "==" or not version:
                    raise RuntimeError(
                        f'Requirement {pkg} need to be pinned "{pkg}==<version>".'
                    )

                for part in version.split(";", 1)[0].split(","):
                    version_part = PIP_VERSION_RANGE_SEPARATOR.match(part)
                    if version_part:
                        package_versions[pkg] = version_part.group(2)
                        break

    if package_versions.keys() == packages:
        return package_versions

    raise RuntimeError("At least one package was not found in the requirements file.")


@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

    package_versions = _get_package_versions(config.root / "requirements.txt", {"uv"})
    package_versions |= _get_package_versions(
        config.root / "requirements_test.txt", {"pipdeptree", "tqdm"}
    )
    package_versions |= _get_package_versions(
        config.root / "requirements_test_pre_commit.txt", {"ruff"}
    )

    return [
        File(
            DOCKERFILE_TEMPLATE.format(
                timeout=timeout,
                **package_versions,
                go2rtc=_GO2RTC_SHA,
            ),
            config.root / "Dockerfile",
        ),
        File(
            _HASSFEST_TEMPLATE.format(
                timeout=timeout,
                **package_versions,
            ),
            config.root / "script/hassfest/docker/Dockerfile",
        ),
    ]


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)
