package guesthook

import (
	"context"
	"io"
	"os"
	"regexp"
	"testing"
)

// recordingRunner is a fake proxmox.Runner that records every call and snapshots the content of the
// install SOURCE file at call time (the deferred os.Remove would erase it before the test can look).
type recordingRunner struct {
	calls      [][]string
	srcContent []string
}

func (r *recordingRunner) Run(_ context.Context, name string, args ...string) ([]byte, []byte, error) {
	r.calls = append(r.calls, append([]string{name}, args...))
	if name == "install" && len(args) > 0 {
		src := args[len(args)-2]
		b, _ := os.ReadFile(src)
		r.srcContent = append(r.srcContent, string(b))
	}
	return nil, nil, nil
}

func (r *recordingRunner) RunStdin(ctx context.Context, _ io.Reader, name string, args ...string) ([]byte, []byte, error) {
	return r.Run(ctx, name, args...)
}

// TestInstallSnippet_RandomTempName is the audit-B1 negative test: the staged install SOURCE must be a
// RANDOM os.CreateTemp name (felhom-guest-hook-<random>.sh), never the fixed, pre-creatable
// /tmp/felhom-guest-hook.sh (a local TOCTOU into a root-executed hookscript), and two consecutive
// installs must stage through DIFFERENT paths.
func TestInstallSnippet_RandomTempName(t *testing.T) {
	r := &recordingRunner{}
	if err := InstallSnippet(context.Background(), r); err != nil {
		t.Fatalf("InstallSnippet #1: %v", err)
	}
	if err := InstallSnippet(context.Background(), r); err != nil {
		t.Fatalf("InstallSnippet #2: %v", err)
	}
	var installs [][]string
	for _, call := range r.calls {
		if call[0] == "install" {
			installs = append(installs, call)
		}
	}
	if len(installs) != 2 {
		t.Fatalf("expected 2 install calls, got %d: %v", len(installs), r.calls)
	}

	randomName := regexp.MustCompile(`felhom-guest-hook-[^/\\]+\.sh$`)
	fixedName := regexp.MustCompile(`felhom-guest-hook\.sh$`)
	var srcs []string
	for i, call := range installs {
		// install -m 0755 -- <src> <dest>
		if len(call) != 6 {
			t.Fatalf("call %d: unexpected vector %v", i, call)
		}
		src, dest := call[4], call[5]
		if dest != SnippetPath {
			t.Errorf("call %d: dest = %q, want %q", i, dest, SnippetPath)
		}
		if !randomName.MatchString(src) {
			t.Errorf("call %d: source %q does not match the random felhom-guest-hook-*.sh pattern", i, src)
		}
		if fixedName.MatchString(src) {
			t.Errorf("call %d: source %q is the FIXED predictable temp name (B1 TOCTOU)", i, src)
		}
		srcs = append(srcs, src)
	}
	if srcs[0] == srcs[1] {
		t.Errorf("two consecutive installs staged through the SAME source path %q — must be random per call", srcs[0])
	}
	// Non-hollow: the staged file must actually carry the snippet body at install time.
	for i, c := range r.srcContent {
		if c != snippetBody {
			t.Errorf("call %d: staged content is not the snippet body (got %d bytes)", i, len(c))
		}
	}
	// And the temp is cleaned up after.
	for _, src := range srcs {
		if _, err := os.Stat(src); err == nil {
			t.Errorf("staged temp %q left behind (defer os.Remove missing)", src)
		}
	}
}

// B2 Scenario D (DRILL-day0-cleanroom-2026-07-03): on a fresh PVE, /var/lib/vz/snippets does not
// exist and `install` (no -D) cannot create it — the drill saw
// `install: cannot create regular file … No such file or directory` and the guest silently got no
// pre-start self-heal hook. InstallSnippet must therefore issue a `mkdir -p <SnippetDir>` fenced op
// BEFORE the `install` op. Pre-fix wrong outcome: no mkdir call at all — only the doomed install.
func TestInstallSnippet_EnsuresSnippetsDirFirst(t *testing.T) {
	r := &recordingRunner{}
	if err := InstallSnippet(context.Background(), r); err != nil {
		t.Fatalf("InstallSnippet: %v", err)
	}

	mkdirIdx, installIdx := -1, -1
	for i, call := range r.calls {
		switch call[0] {
		case "mkdir":
			if mkdirIdx == -1 {
				mkdirIdx = i
				want := []string{"mkdir", "-p", SnippetDir}
				if len(call) != 3 || call[1] != want[1] || call[2] != want[2] {
					t.Errorf("mkdir vector = %v, want %v (the sudoers fence matches exactly this argv)", call, want)
				}
			}
		case "install":
			if installIdx == -1 {
				installIdx = i
			}
		}
	}
	if mkdirIdx == -1 {
		t.Fatalf("no `mkdir -p %s` op issued — on a fresh box the snippet install fails ENOENT (B2); calls: %v", SnippetDir, r.calls)
	}
	if installIdx == -1 {
		t.Fatalf("no install op issued; calls: %v", r.calls)
	}
	if mkdirIdx > installIdx {
		t.Fatalf("mkdir (call %d) must PRECEDE install (call %d) — order: %v", mkdirIdx, installIdx, r.calls)
	}
}
