package guesthook

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strconv"

	"gitea.dooplex.hu/admin/felhom-agent/internal/proxmox"
)

// Install/registration of the pre-start hook. The wrapper lives in a PVE `snippets`-enabled storage dir
// (the `local` storage maps to /var/lib/vz/snippets) and is referenced per-guest by its volid.
const (
	// SnippetDir is the local-storage snippets directory PVE serves hookscripts from.
	SnippetDir = "/var/lib/vz/snippets"
	// SnippetName is the wrapper filename.
	SnippetName = "felhom-guest-hook.sh"
	// HookVolID is the volid form `pct set --hookscript` expects.
	HookVolID = "local:snippets/" + SnippetName
	// AgentBin is the installed agent binary the wrapper delegates to.
	AgentBin = "/usr/local/bin/felhom-agent"
)

// SnippetPath is the absolute path of the installed wrapper.
var SnippetPath = filepath.Join(SnippetDir, SnippetName)

// snippetBody is the tiny wrapper PVE execs as `<script> <vmid> <phase>`. It delegates to the agent
// binary so the heal LOGIC is the unit-tested Go, never duplicated (divergence-proof) shell. Executable.
// The wrapper NEVER exec's and ALWAYS exits 0 (CAMPAIGN-3 F10/rc255 belt): a hook that exits nonzero
// aborts the guest start. `exec` would surface the binary's exit code to PVE; instead we run it as a
// child, swallow any nonzero (missing/crashed binary, OOM-kill), and `exit 0` unconditionally. The Go
// side has its own recover + per-phase timeout — this is the second belt at the shell layer.
const snippetBody = `#!/bin/sh
# felhom-agent guest pre-start self-heal hook (C1 net). PVE calls: <script> <vmid> <phase>.
` + AgentBin + ` guest-hook "$1" "$2" || true
exit 0
`

// InstallSnippet writes the pre-start hook wrapper into the PVE snippets dir (idempotent, root-owned,
// executable). The agent runs as a non-root service user, so it writes an agent-writable temp file then
// `install`s it host-root (same pattern as the bootstrap mount + dnsmasq drop-ins). Safe to call repeatedly.
// The temp file is a RANDOM-named os.CreateTemp (audit B1): a fixed, predictable /tmp name could be
// pre-created by another local user and rewritten between our write and root's install (TOCTOU into a
// root-executed hookscript). The final mode comes from `install -m`, so the 0600 temp is fine.
func InstallSnippet(ctx context.Context, runner proxmox.Runner) error {
	f, err := os.CreateTemp("", "felhom-guest-hook-*.sh")
	if err != nil {
		return fmt.Errorf("guesthook: create temp snippet: %w", err)
	}
	tmp := f.Name()
	defer os.Remove(tmp)
	if _, err := f.WriteString(snippetBody); err != nil {
		f.Close()
		return fmt.Errorf("guesthook: write temp snippet: %w", err)
	}
	if err := f.Close(); err != nil {
		return fmt.Errorf("guesthook: close temp snippet: %w", err)
	}
	// Ensure the snippets dir exists FIRST (B2, DRILL-day0-cleanroom-2026-07-03): a fresh PVE has
	// no /var/lib/vz/snippets, and `install` (without -D) won't create the parent — the whole
	// hook install silently failed on a freshly-bootstrapped box. Fenced root op like the install
	// itself; idempotent.
	if _, stderr, err := runner.Run(ctx, "mkdir", "-p", SnippetDir); err != nil {
		return fmt.Errorf("guesthook: ensure snippets dir %s: %w: %s", SnippetDir, err, string(stderr))
	}
	if _, stderr, err := runner.Run(ctx, "install", "-m", "0755", "--", tmp, SnippetPath); err != nil {
		return fmt.Errorf("guesthook: install snippet to %s: %w: %s", SnippetPath, err, string(stderr))
	}
	return nil
}

// Register points a guest at the pre-start hook (`pct set <vmid> --hookscript <volid>`). Idempotent —
// re-setting the same hookscript is a no-op. Safe on a running guest (a config edit, not a start, so no
// start-lock contention).
func Register(ctx context.Context, runner proxmox.Runner, vmid int) error {
	if _, stderr, err := runner.Run(ctx, "pct", "set", strconv.Itoa(vmid), "--hookscript", HookVolID); err != nil {
		return fmt.Errorf("guesthook: register hookscript on %d: %w: %s", vmid, err, string(stderr))
	}
	return nil
}
