"""Git provisioning inside a running smolmachines bottle
(PRD 0023 chunk 4d).

Three concerns, all about git in the agent:

  1. If --cwd was passed AND the host cwd has a .git, copy that
     .git into /home/node/workspace/.git so the agent operates on
     the user's repo.
  2. If the bottle declares `git` entries (PRD 0008), write a
     ~/.gitconfig with insteadOf rules so every git operation
     against a declared upstream transparently hits the per-bottle
     git-gate. The gate mirrors the upstream in both directions,
     so URL rewriting is symmetric.
  3. If the bottle declares `git.user` (issue #86), set
     `git config --global user.{name,email}` inside the guest so
     the agent's commits are attributed to that identity.

Differs from `backend.docker.provision.git` in one address detail:
the TSI-allowlisted guest can only reach the bundle's pinned IP
(no DNS resolver in the /32 allowlist), so the insteadOf URLs
are `http://<bundle_ip>:<port>/<name>.git` rather than the
docker backend's `git://git-gate/<name>.git`. The render itself
is the shared `git_gate_render_gitconfig` on the platform-neutral
git_gate module."""

from __future__ import annotations

import os
import tempfile
from pathlib import Path

from ....git_gate import git_gate_render_gitconfig
from ....log import info
from .. import smolvm as _smolvm
from ..bottle_plan import SmolmachinesBottlePlan


# `node` is the agent user from the repo Dockerfile. Override via
# BOT_BOTTLE_GUEST_HOME mirrors the docker backend's
# BOT_BOTTLE_CONTAINER_HOME knob — same purpose, different
# transport.
_DEFAULT_GUEST_HOME = "/home/node"


def _guest_home() -> str:
    return os.environ.get("BOT_BOTTLE_GUEST_HOME", _DEFAULT_GUEST_HOME)


def provision_git(plan: SmolmachinesBottlePlan, target: str) -> None:
    """Set up git inside the guest. Runs all three subcases; each
    no-ops when its condition isn't met."""
    _provision_cwd_git(plan, target)
    _provision_git_gate_config(plan, target)
    _provision_git_user(plan, target)


def _provision_cwd_git(plan: SmolmachinesBottlePlan, target: str) -> None:
    """If --cwd was set and the host cwd has a .git directory, copy
    it into <guest_home>/workspace/.git and fix ownership. No-op
    otherwise."""
    if not (plan.spec.copy_cwd and Path(plan.spec.user_cwd, ".git").is_dir()):
        return
    guest_workspace_git = f"{_guest_home()}/workspace/.git"
    info(f"copying {plan.spec.user_cwd}/.git -> {target}:{guest_workspace_git}")
    # mkdir -p the workspace dir so `machine cp` lands the .git
    # directly there even on first-time bottles.
    _smolvm.machine_exec(target, ["mkdir", "-p", f"{_guest_home()}/workspace"])
    _smolvm.machine_cp(
        f"{plan.spec.user_cwd}/.git", f"{target}:{guest_workspace_git}",
    )
    # `machine cp` lands files as root; the agent runs as node so
    # the workspace tree must be chowned over.
    _smolvm.machine_exec(
        target, ["chown", "-R", "node:node", guest_workspace_git],
    )


def _provision_git_gate_config(plan: SmolmachinesBottlePlan, target: str) -> None:
    """Write ~/.gitconfig in the guest with the git-gate insteadOf
    rules. No-op when the bottle has no `git` entries."""
    bottle = plan.spec.manifest.bottle_for(plan.spec.agent_name)
    if not bottle.git:
        return

    # `<loopback alias>:<host port>` form: the bundle's git-gate
    # HTTP port is published on host loopback at launch time so
    # the smolvm guest (which can only reach macOS networking via
    # TSI, not the docker bridge IP) can dial it. launch.py
    # populates `plan.agent_git_gate_host` after bundle bringup.
    content = git_gate_render_gitconfig(
        bottle.git, plan.agent_git_gate_host, scheme="http",
    )

    guest_gitconfig = f"{_guest_home()}/.gitconfig"
    # Stage the file under the plan's stage_dir so `machine cp`
    # has a stable host path. The plan's stage_dir is cleaned up
    # by start.py's session-end teardown.
    with tempfile.NamedTemporaryFile(
        "w", dir=str(plan.stage_dir), prefix="gitconfig.",
        delete=False,
    ) as f:
        f.write(content)
        config_file = Path(f.name)
    os.chmod(config_file, 0o600)

    info(f"writing {guest_gitconfig} with {len(bottle.git)} insteadOf rule(s)")
    _smolvm.machine_cp(str(config_file), f"{target}:{guest_gitconfig}")
    _smolvm.machine_exec(target, ["chown", "node:node", guest_gitconfig])
    _smolvm.machine_exec(target, ["chmod", "644", guest_gitconfig])


def _provision_git_user(
    plan: SmolmachinesBottlePlan, target: str,
) -> None:
    """Apply `git config --global user.{name,email}` inside the
    guest as the node user so --global lands in the same
    `/home/node/.gitconfig` that `_provision_git_gate_config`
    writes to. No-op when the bottle didn't declare `git.user`.

    Runs via `runuser -u node --`; HOME is forced via smolvm's
    `-e` flag because runuser (without -l) inherits root's
    HOME=/root, which would put --global in the wrong file."""
    bottle = plan.spec.manifest.bottle_for(plan.spec.agent_name)
    gu = bottle.git_user
    if gu.is_empty():
        return
    env = {"HOME": _guest_home(), "USER": "node"}
    if gu.name:
        info(f"git config --global user.name = {gu.name!r}")
        _smolvm.machine_exec(
            target,
            ["runuser", "-u", "node", "--",
             "git", "config", "--global", "user.name", gu.name],
            env=env,
        )
    if gu.email:
        info(f"git config --global user.email = {gu.email!r}")
        _smolvm.machine_exec(
            target,
            ["runuser", "-u", "node", "--",
             "git", "config", "--global", "user.email", gu.email],
            env=env,
        )
