"""
hatch_build.py  -  Hatchling build hook for llmfit.

Injects the pre-built llmfit binary into each wheel via ``shared_scripts`` so
that the installer places it in the environment's scripts directory (e.g.
``.venv/bin/llmfit``).  Also overrides the wheel platform tag so that wheels
built on different CI runners get the correct platform-specific name.

For editable installs (``uv sync``, ``uv run``), the locally compiled debug
binary (from ``cargo build``) is used.  For release installs (``uv build``),
the release binary (from ``cargo build --release``) is used.

Release builds also support setting ``LLMFIT_PYTHON_PLATFORM_TAG``, in which
case the release binary for the corresponding Rust target is used instead
(e.g. after ``cargo build --release --target aarch64-unknown-linux-gnu``).

Environment variables
---------------------
LLMFIT_PYTHON_PLATFORM_TAG
    Wheel platform tag to target (e.g. ``manylinux_2_17_x86_64``).
    Not supported for editable installs.
LLMFIT_VERSION
    Override the version read from ``Cargo.toml`` (e.g. ``0.9.8``).
    Supported for both release and editable installs.
"""

from __future__ import annotations

import os
import re
import subprocess
import sys
from pathlib import Path

import tomli
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
from hatchling.metadata.plugin.interface import MetadataHookInterface
from packaging.tags import sys_tags

# wheel_platform_tag -> (upstream_target, binary_name)
TARGET_CONFIGS: dict[str, tuple[str, str]] = {
    "manylinux_2_17_x86_64": ("x86_64-unknown-linux-gnu", "llmfit"),
    "manylinux_2_17_aarch64": ("aarch64-unknown-linux-gnu", "llmfit"),
    "manylinux_2_39_riscv64": ("riscv64gc-unknown-linux-gnu", "llmfit"),
    "musllinux_1_2_x86_64": ("x86_64-unknown-linux-musl", "llmfit"),
    "musllinux_1_2_aarch64": ("aarch64-unknown-linux-musl", "llmfit"),
    "macosx_10_12_x86_64": ("x86_64-apple-darwin", "llmfit"),
    "macosx_11_0_arm64": ("aarch64-apple-darwin", "llmfit"),
    "win_amd64": ("x86_64-pc-windows-msvc", "llmfit.exe"),
    "win_arm64": ("aarch64-pc-windows-msvc", "llmfit.exe"),
}


class LlmfitMetadataHook(MetadataHookInterface):
    """Hatchling metadata hook that sets version and license-expression dynamically."""

    PLUGIN_NAME = "llmfit version and license"

    def update(self, metadata: dict) -> None:
        """Populate ``version`` and ``license-expression`` from ``Cargo.toml``.

        Version resolution order:

        1. ``LLMFIT_VERSION`` environment variable (e.g. ``0.9.8``).
        2. The ``version`` field in ``[workspace.package]`` from ``Cargo.toml``.
        """
        with (Path(self.root).parent / "Cargo.toml").open("rb") as f:
            workspace_package: dict[str, str] = tomli.load(f)["workspace"]["package"]
        version: str = os.environ.get("LLMFIT_VERSION") or workspace_package["version"]
        if not re.match(r"^\d+\.\d+\.\d+$", version):
            raise ValueError(f"Invalid version: {version!r}")
        metadata["version"] = version
        metadata["license-expression"] = workspace_package["license"]


class LlmfitBinaryBuildHook(BuildHookInterface):
    """Hatchling build hook that injects the llmfit binary into each wheel."""

    PLUGIN_NAME = "llmfit binary"

    @staticmethod
    def _detect_platform() -> str:
        """Return the best platform tag for the current machine."""
        best = next((t.platform for t in sys_tags() if t.platform in TARGET_CONFIGS), None)
        if best is not None:
            return best
        first = next(t.platform for t in sys_tags())
        raise RuntimeError(f"No suitable wheel platform found for runtime platform {first!r}.")

    @staticmethod
    def _find_binary_for_target(llmfit_root: Path, py_target: str) -> Path:
        """Find the binary compiled for a specific Rust target.

        Looks in ``target/{upstream_target}/release/``, which is where Cargo
        places the binary when built with ``--target``.
        """
        upstream_target, binary_name = TARGET_CONFIGS[py_target]
        bin_path = llmfit_root / "target" / upstream_target / "release" / binary_name
        if not bin_path.is_file():
            raise FileNotFoundError(
                f"Binary not found at {bin_path}. Expected it to be built for target {upstream_target!r}.",
            )
        return bin_path

    @staticmethod
    def _find_local_binary(llmfit_root: Path) -> Path:
        """Find the locally compiled binary in default Cargo output locations.

        Checks ``target/debug/`` first (from ``make build``), then
        ``target/release/`` (from ``make release``).
        """
        binary_name = "llmfit.exe" if sys.platform == "win32" else "llmfit"
        candidates = [
            llmfit_root / "target" / "debug" / binary_name,
            llmfit_root / "target" / "release" / binary_name,
        ]
        for candidate in candidates:
            if candidate.is_file():
                return candidate
        raise FileNotFoundError(
            "No compiled binary found. Checked:\n"
            + "\n".join(f"  {c}" for c in candidates)
            + "\nRun 'make build' first.",
        )

    @staticmethod
    def _check_binary_version(bin_path: Path, expected_version: str) -> None:
        """Run the binary with ``--version`` and verify it matches the expected version.

        Raises ``RuntimeError`` on a mismatch — this indicates a stale build.
        """
        result = subprocess.run(
            [str(bin_path), "--version"],
            capture_output=True,
            check=True,
            text=True,
            timeout=5,
        )
        output = result.stdout.strip()  # e.g. "llmfit 0.9.8"
        match = re.match(r"^llmfit v?(\d+\.\d+\.\d+)$", output)
        if not match:
            raise RuntimeError(f"Unexpected output from '{bin_path} --version': {output!r}")
        binary_version = match.group(1)
        if binary_version != expected_version:
            raise RuntimeError(
                f"Binary version mismatch: binary at {bin_path} reports {binary_version!r} "
                f"but Cargo.toml (or LLMFIT_VERSION) says {expected_version!r}. "
                "Run 'make build' to recompile.",
            )
        print(f"  Binary version OK ({binary_version})")

    def initialize(self, version: str, build_data: dict) -> None:
        """Locate the platform binary and configure the wheel before it is built."""
        py_target_from_env = os.environ.get("LLMFIT_PYTHON_PLATFORM_TAG")
        if version == "editable" and py_target_from_env:
            raise ValueError(
                "LLMFIT_PYTHON_PLATFORM_TAG is not supported for editable installs. "
                "Let the build system detect the host platform instead.",
            )
        running_platform = self._detect_platform()
        py_target = py_target_from_env or running_platform
        if py_target not in TARGET_CONFIGS:
            raise ValueError(
                f"Unknown LLMFIT_PYTHON_PLATFORM_TAG={py_target!r}. Must be one of: {sorted(TARGET_CONFIGS)}",
            )

        upstream_target, binary_name = TARGET_CONFIGS[py_target]
        pypi_version: str = self.metadata.version

        print(f"  target={upstream_target}  version={pypi_version}  wheel tag=py3-none-{py_target}")

        llmfit_root = Path(self.root).parent
        if version == "editable":
            # For editable installs, look for target/debug/llmfit or target/release/llmfit (or llmfit.exe on Windows).
            bin_path = self._find_local_binary(llmfit_root)
        elif version == "standard":
            # For release installs, look for e.g. target/x86_64-unknown-linux-gnu/release/llmfit on Linux.
            bin_path = self._find_binary_for_target(llmfit_root, py_target)
        else:
            raise ValueError(f"Unknown version: {version!r}")

        # Always check that the binary exists.
        if not bin_path.is_file():
            raise FileNotFoundError(
                f"Binary not found at {bin_path}. The binary selection logic selected a file that does not exist.",
            )

        # If possible, check the self-reported version of the binary.
        # If the binary was built for a different platform then it's not possible.
        if py_target == running_platform:
            self._check_binary_version(bin_path, pypi_version)

        # Place the binary in the wheel's scripts directory so that the
        # installer puts it in .venv/bin/ (or Scripts/ on Windows).
        build_data["shared_scripts"][str(bin_path.absolute())] = binary_name

        # Override the platform tag so the wheel gets the correct platform-specific name.
        build_data["tag"] = f"py3-none-{py_target}"
        build_data["pure_python"] = False
