package web

import (
	"testing"
)

// v0.147.0 slice 4b — the Megosztás bring-up progress card.
//
// The card's whole value is that it distinguishes states the old synchronous handler collapsed into
// one silent form post. So the tests are about the DISTINCTIONS:
//   - "pulling" vs "starting" — decided by local image presence, and decided BEFORE the work starts
//     (afterwards the image is always present and the card could never truthfully say „letöltés").
//   - reconcile-returned-nil is NOT the same as running: it also covers "deliberately deployed
//     nothing because there is no household password yet".
//   - compose up -d exits 0 on a crash-loop, so success must be PROBED, never inferred.
//
// SCOPE, STATED HONESTLY: these cover the single-flight slot, snapshot isolation and the phase
// vocabulary. They do NOT drive runSambaEnsureJob end-to-end — that needs a real *stacks.Manager
// (the Server field is the concrete type, not an interface), and introducing an interface purely for
// this card was more churn than the slice warranted. The pulling-vs-starting decision and the
// probed-liveness terminal state are therefore covered by the LIVE validation on the demo box, not
// by a unit test. Recorded here rather than left as an implied gap; the ROADMAP's unified
// async-job feedback item is where that seam belongs.

func TestSambaEnsureSingleFlight(t *testing.T) {
	var st sambaEnsureState
	job := &sambaEnsureJob{Phase: sambaPhaseStarting}
	if !st.acquire(job) {
		t.Fatal("first acquire refused")
	}
	// A double-submit must not start a second `compose up -d` against the same stack dir.
	if st.acquire(&sambaEnsureJob{Phase: sambaPhaseStarting}) {
		t.Error("second acquire succeeded while a job was in flight")
	}
	st.release()
	if !st.acquire(&sambaEnsureJob{Phase: sambaPhaseStarting}) {
		t.Error("acquire refused after release")
	}
}

func TestSambaEnsureSnapshotIsACopy(t *testing.T) {
	var st sambaEnsureState
	job := &sambaEnsureJob{Phase: sambaPhasePulling}
	st.acquire(job)

	snap := st.snapshot()
	snap.Phase = "mutated-by-caller"
	if again := st.snapshot(); again.Phase != sambaPhasePulling {
		t.Errorf("a caller mutated the shared job through its snapshot: %q", again.Phase)
	}
	// And the live job must not leak into an already-taken snapshot either.
	snap2 := st.snapshot()
	job.Phase = sambaPhaseRunning
	st.set(job)
	if snap2.Phase != sambaPhasePulling {
		t.Errorf("an earlier snapshot changed under the caller: %q", snap2.Phase)
	}
}

func TestSambaEnsureStateStartsNil(t *testing.T) {
	var st sambaEnsureState
	// nil = never ran this process. The status handler maps that to `idle` and lets live container
	// state win, so a page loaded after a restart still tells the truth.
	if j := st.snapshot(); j != nil {
		t.Errorf("fresh state reported a job: %+v", j)
	}
}

// TestSambaPhaseConstantsAreDistinct guards the template contract: sharing.html maps these exact
// strings, and a duplicate would silently render the wrong Hungarian sentence.
func TestSambaPhaseConstantsAreDistinct(t *testing.T) {
	all := []string{
		sambaPhasePulling, sambaPhaseStarting, sambaPhaseRunning,
		sambaPhaseNeedsPassword, sambaPhaseFailed, sambaPhaseIdle,
	}
	seen := map[string]bool{}
	for _, p := range all {
		if p == "" {
			t.Error("a phase constant is empty — the template would fall through to 'no card'")
		}
		if seen[p] {
			t.Errorf("duplicate phase constant %q", p)
		}
		seen[p] = true
	}
}
