package main

import (
	"context"
	"testing"
	"time"
)

// F10/rc255 (CAMPAIGN-3): a guest-hook phase body that PANICS must never crash the process — the hook
// must return cleanly so the guest start proceeds (a nonzero exit blocks the start). runHookPhase
// recovers the panic and returns.
func TestRunHookPhase_PanicRecovered(t *testing.T) {
	done := make(chan struct{})
	go func() {
		defer close(done)
		runHookPhase("9201", "pre-start", 5*time.Second, func(context.Context) {
			panic("simulated heal panic (e.g. a future Heal bug)")
		})
	}()
	select {
	case <-done:
		// returned cleanly — the guest start would proceed
	case <-time.After(3 * time.Second):
		t.Fatal("runHookPhase did not return after a panicking body (would have crashed the hook)")
	}
}

// A phase body that overruns the timeout must be abandoned — the hook returns rather than hanging the
// PVE start task. (The body's context is cancelled; the hook does not wait for the body to notice.)
func TestRunHookPhase_TimeoutReturns(t *testing.T) {
	bodyCtxCancelled := make(chan struct{}, 1)
	done := make(chan struct{})
	go func() {
		defer close(done)
		runHookPhase("9201", "post-start", 20*time.Millisecond, func(ctx context.Context) {
			<-ctx.Done() // simulate a body that respects cancellation eventually
			bodyCtxCancelled <- struct{}{}
		})
	}()
	select {
	case <-done:
	case <-time.After(2 * time.Second):
		t.Fatal("runHookPhase did not return after the timeout (would hang the guest start)")
	}
	select {
	case <-bodyCtxCancelled:
		// the body's context was cancelled at the deadline — the intended signal
	case <-time.After(time.Second):
		t.Fatal("the phase body's context was not cancelled at the timeout")
	}
}

// A body that errors (returns normally, no panic) is fine — the hook returns cleanly.
func TestRunHookPhase_NormalBodyReturns(t *testing.T) {
	ran := false
	runHookPhase("9201", "pre-start", time.Second, func(context.Context) { ran = true })
	if !ran {
		t.Fatal("the phase body must run")
	}
}
