package deploy

import (
	"context"
	"errors"
	"fmt"
	"os"
	"os/user"
	"path/filepath"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

// Phase 1 of the deploy-hardening I master bundle. The 12 named
// tests below pin the load-bearing invariants of the
// internal/deploy/ package: atomic-or-nothing across files,
// validate-fail-cleans-up, reload-fail-rolls-back,
// rollback-also-fails-escalates, SHA-256 idempotency,
// owner/mode preservation + override, file-level serialization,
// backup retention janitor, and AtomicWriteFile temp-file +
// rename-race correctness.
//
// All 12 are required by the prompt at
// the project's deploy-hardening I spec::"Test plan (Phase 1
// ships ≥95% coverage on the new package)".
//
// The tests run in non-root environments — they do NOT exercise
// cross-user chown (which requires CAP_CHOWN). The chown wiring
// is exercised via the same-user case (chown to os.Getuid()
// always succeeds) + the resolveOwnership white-box tests.

const testCert1 = "-----BEGIN CERTIFICATE-----\nFAKE-CERT-1-PAYLOAD\n-----END CERTIFICATE-----\n"
const testCert2 = "-----BEGIN CERTIFICATE-----\nFAKE-CERT-2-DIFFERENT\n-----END CERTIFICATE-----\n"

// TestApply_HappyPath_PreCommitSucceeds_PostCommitSucceeds_FilesAtomic
// pins the canonical happy path: write multiple files, validate
// passes, all atomic-rename, reload passes. Every File ends up
// with the new bytes; PreCommit + PostCommit each fired once.
func TestApply_HappyPath_PreCommitSucceeds_PostCommitSucceeds_FilesAtomic(t *testing.T) {
	dir := t.TempDir()
	cert := filepath.Join(dir, "tls.crt")
	key := filepath.Join(dir, "tls.key")

	preCalls, postCalls := 0, 0
	var seenTempPaths map[string]string
	plan := Plan{
		Files: []File{
			{Path: cert, Bytes: []byte(testCert1)},
			{Path: key, Bytes: []byte(testCert2)},
		},
		PreCommit: func(ctx context.Context, tempPaths map[string]string) error {
			preCalls++
			seenTempPaths = tempPaths
			// Both temp files exist + readable + carry the new
			// bytes (the load-bearing invariant for "validate-
			// against-temp" semantics).
			for finalPath, tempPath := range tempPaths {
				if _, err := os.Stat(tempPath); err != nil {
					return fmt.Errorf("temp for %s missing: %w", finalPath, err)
				}
			}
			return nil
		},
		PostCommit: func(ctx context.Context) error {
			postCalls++
			return nil
		},
	}

	res, err := Apply(context.Background(), plan)
	if err != nil {
		t.Fatalf("Apply: %v", err)
	}
	if res.SkippedAsIdempotent {
		t.Errorf("expected fresh write, got idempotent skip")
	}
	if !res.ValidateOK || !res.Reloaded {
		t.Errorf("ValidateOK=%v Reloaded=%v, want true/true", res.ValidateOK, res.Reloaded)
	}
	if preCalls != 1 || postCalls != 1 {
		t.Errorf("PreCommit/PostCommit calls = %d/%d, want 1/1", preCalls, postCalls)
	}
	if len(seenTempPaths) != 2 {
		t.Errorf("PreCommit saw %d temp paths, want 2", len(seenTempPaths))
	}
	// Final files have new bytes.
	if got, _ := os.ReadFile(cert); string(got) != testCert1 {
		t.Errorf("cert content = %q, want %q", got, testCert1)
	}
	if got, _ := os.ReadFile(key); string(got) != testCert2 {
		t.Errorf("key content = %q, want %q", got, testCert2)
	}
}

// TestApply_PreCommitFails_NoFilesChanged pins the all-or-nothing
// invariant on the validate path: PreCommit returns an error →
// neither destination is touched, ErrValidateFailed is returned.
func TestApply_PreCommitFails_NoFilesChanged(t *testing.T) {
	dir := t.TempDir()
	cert := filepath.Join(dir, "tls.crt")
	key := filepath.Join(dir, "tls.key")
	if err := os.WriteFile(cert, []byte("ORIGINAL-CERT"), 0644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(key, []byte("ORIGINAL-KEY"), 0600); err != nil {
		t.Fatal(err)
	}

	postCalls := 0
	plan := Plan{
		Files: []File{
			{Path: cert, Bytes: []byte(testCert1)},
			{Path: key, Bytes: []byte(testCert2)},
		},
		PreCommit: func(ctx context.Context, tempPaths map[string]string) error {
			return errors.New("nginx -t says: invalid SAN")
		},
		PostCommit: func(ctx context.Context) error {
			postCalls++
			return nil
		},
	}

	_, err := Apply(context.Background(), plan)
	if !errors.Is(err, ErrValidateFailed) {
		t.Fatalf("expected ErrValidateFailed, got %v", err)
	}
	if postCalls != 0 {
		t.Errorf("PostCommit called %d times after PreCommit failure, want 0", postCalls)
	}
	// Both destinations untouched.
	if got, _ := os.ReadFile(cert); string(got) != "ORIGINAL-CERT" {
		t.Errorf("cert was modified despite PreCommit failure: %q", got)
	}
	if got, _ := os.ReadFile(key); string(got) != "ORIGINAL-KEY" {
		t.Errorf("key was modified despite PreCommit failure: %q", got)
	}
	// No temp files leaked.
	entries, _ := os.ReadDir(dir)
	for _, e := range entries {
		if strings.Contains(e.Name(), TempSuffix) {
			t.Errorf("temp file leaked: %s", e.Name())
		}
	}
}

// TestApply_PostCommitFails_FilesRolledBack pins the rollback
// wire: PostCommit fails → restore from backup → re-call
// PostCommit → second one succeeds → return ErrReloadFailed +
// RolledBack=true. The destinations now hold the ORIGINAL bytes.
func TestApply_PostCommitFails_FilesRolledBack(t *testing.T) {
	dir := t.TempDir()
	cert := filepath.Join(dir, "tls.crt")
	if err := os.WriteFile(cert, []byte("ORIGINAL"), 0644); err != nil {
		t.Fatal(err)
	}

	postCalls := 0
	plan := Plan{
		Files: []File{
			{Path: cert, Bytes: []byte(testCert1)},
		},
		PostCommit: func(ctx context.Context) error {
			postCalls++
			if postCalls == 1 {
				return errors.New("nginx -s reload exited 1")
			}
			return nil
		},
	}

	res, err := Apply(context.Background(), plan)
	if !errors.Is(err, ErrReloadFailed) {
		t.Fatalf("expected ErrReloadFailed, got %v", err)
	}
	if !res.RolledBack {
		t.Error("expected RolledBack=true")
	}
	if res.Reloaded {
		t.Error("expected Reloaded=false after rollback")
	}
	if postCalls != 2 {
		t.Errorf("PostCommit calls = %d, want 2 (once for the new bytes, once for the restored bytes)", postCalls)
	}
	if got, _ := os.ReadFile(cert); string(got) != "ORIGINAL" {
		t.Errorf("cert after rollback = %q, want %q", got, "ORIGINAL")
	}
}

// TestApply_RollbackAlsoFails_ReturnsErrRollbackFailed is the
// escalation path: PostCommit fails + the second PostCommit (after
// restore) also fails. ErrRollbackFailed surfaces;
// operator-actionable.
func TestApply_RollbackAlsoFails_ReturnsErrRollbackFailed(t *testing.T) {
	dir := t.TempDir()
	cert := filepath.Join(dir, "tls.crt")
	if err := os.WriteFile(cert, []byte("ORIGINAL"), 0644); err != nil {
		t.Fatal(err)
	}

	plan := Plan{
		Files: []File{
			{Path: cert, Bytes: []byte(testCert1)},
		},
		PostCommit: func(ctx context.Context) error {
			return errors.New("nginx is wedged")
		},
	}

	_, err := Apply(context.Background(), plan)
	if !errors.Is(err, ErrRollbackFailed) {
		t.Fatalf("expected ErrRollbackFailed, got %v", err)
	}
}

// TestApply_IdempotentSkip_SHA256Match pins the idempotency
// short-circuit: when every File's destination already matches
// SHA-256, neither PreCommit nor PostCommit fires; the result
// reports SkippedAsIdempotent=true.
func TestApply_IdempotentSkip_SHA256Match(t *testing.T) {
	dir := t.TempDir()
	cert := filepath.Join(dir, "tls.crt")
	if err := os.WriteFile(cert, []byte(testCert1), 0644); err != nil {
		t.Fatal(err)
	}

	preCalls, postCalls := 0, 0
	plan := Plan{
		Files: []File{
			{Path: cert, Bytes: []byte(testCert1)},
		},
		PreCommit: func(ctx context.Context, _ map[string]string) error {
			preCalls++
			return nil
		},
		PostCommit: func(ctx context.Context) error {
			postCalls++
			return nil
		},
	}
	res, err := Apply(context.Background(), plan)
	if err != nil {
		t.Fatalf("Apply: %v", err)
	}
	if !res.SkippedAsIdempotent {
		t.Error("expected SkippedAsIdempotent=true")
	}
	if preCalls != 0 || postCalls != 0 {
		t.Errorf("expected no Pre/PostCommit calls, got %d/%d", preCalls, postCalls)
	}
	if len(res.BackupPaths) != 0 {
		t.Errorf("expected zero backups for idempotent skip, got %d", len(res.BackupPaths))
	}

	// Verify SkipIdempotent forces the calls.
	plan.SkipIdempotent = true
	res, err = Apply(context.Background(), plan)
	if err != nil {
		t.Fatalf("Apply with SkipIdempotent: %v", err)
	}
	if res.SkippedAsIdempotent {
		t.Error("expected SkipIdempotent override to force the deploy")
	}
	if preCalls != 1 || postCalls != 1 {
		t.Errorf("expected 1/1 calls under SkipIdempotent, got %d/%d", preCalls, postCalls)
	}
}

// TestApply_PreservesExistingOwnerAndMode_WhenNotOverridden pins
// the silent-failure-mode-defense: an existing nginx:nginx 0640
// file MUST stay nginx:nginx 0640 across a renewal, NOT get
// clobbered to root:root 0600.
//
// We can't actually create a non-current-user file in a non-root
// test, so this test verifies mode preservation only (the chown
// preservation is exercised by the resolveOwnership unit test
// below).
func TestApply_PreservesExistingOwnerAndMode_WhenNotOverridden(t *testing.T) {
	dir := t.TempDir()
	cert := filepath.Join(dir, "tls.crt")
	// Pre-existing file with very specific mode.
	if err := os.WriteFile(cert, []byte("ORIGINAL"), 0640); err != nil {
		t.Fatal(err)
	}
	// Some umasks downgrade 0640 → 0620; force the desired bits
	// after creation.
	if err := os.Chmod(cert, 0640); err != nil {
		t.Fatal(err)
	}

	plan := Plan{
		Files: []File{
			{Path: cert, Bytes: []byte(testCert1)}, // no Mode/Owner/Group set
		},
	}
	if _, err := Apply(context.Background(), plan); err != nil {
		t.Fatalf("Apply: %v", err)
	}
	stat, err := os.Stat(cert)
	if err != nil {
		t.Fatal(err)
	}
	if stat.Mode().Perm() != 0640 {
		t.Errorf("mode after deploy = %#o, want %#o (preservation broken)", stat.Mode().Perm(), os.FileMode(0640))
	}
}

// TestApply_RespectsOverrides_OwnerGroupMode pins the override
// path: when File.Mode is set, the existing mode is overridden.
// We use the current user/group so chown succeeds on non-root.
func TestApply_RespectsOverrides_OwnerGroupMode(t *testing.T) {
	dir := t.TempDir()
	cert := filepath.Join(dir, "tls.crt")
	if err := os.WriteFile(cert, []byte("ORIGINAL"), 0640); err != nil {
		t.Fatal(err)
	}
	if err := os.Chmod(cert, 0640); err != nil {
		t.Fatal(err)
	}

	currentUser, err := user.Current()
	if err != nil {
		t.Fatal(err)
	}
	currentGroup, err := user.LookupGroupId(currentUser.Gid)
	if err != nil {
		t.Fatal(err)
	}

	plan := Plan{
		Files: []File{{
			Path:  cert,
			Bytes: []byte(testCert1),
			Mode:  0644,
			Owner: currentUser.Username,
			Group: currentGroup.Name,
		}},
	}
	if _, err := Apply(context.Background(), plan); err != nil {
		t.Fatalf("Apply: %v", err)
	}
	stat, err := os.Stat(cert)
	if err != nil {
		t.Fatal(err)
	}
	if stat.Mode().Perm() != 0644 {
		t.Errorf("override mode = %#o, want 0644", stat.Mode().Perm())
	}
}

// TestApply_ConcurrentApplyToSameFile_Serializes pins the
// file-level mutex: 10 concurrent Applies to the same destination
// see exactly 10 PostCommit invocations and the file ends with
// one of the writers' bytes (no torn write).
func TestApply_ConcurrentApplyToSameFile_Serializes(t *testing.T) {
	dir := t.TempDir()
	cert := filepath.Join(dir, "tls.crt")

	const N = 10
	var inFlight, maxInFlight int32
	var postCount int32
	var wg sync.WaitGroup
	for i := 0; i < N; i++ {
		wg.Add(1)
		go func(idx int) {
			defer wg.Done()
			plan := Plan{
				Files: []File{{
					Path:  cert,
					Bytes: []byte(fmt.Sprintf("WRITER-%d", idx)),
				}},
				SkipIdempotent: true, // force every call through the full path
				PostCommit: func(ctx context.Context) error {
					n := atomic.AddInt32(&inFlight, 1)
					for {
						m := atomic.LoadInt32(&maxInFlight)
						if n <= m || atomic.CompareAndSwapInt32(&maxInFlight, m, n) {
							break
						}
					}
					time.Sleep(2 * time.Millisecond)
					atomic.AddInt32(&inFlight, -1)
					atomic.AddInt32(&postCount, 1)
					return nil
				},
			}
			if _, err := Apply(context.Background(), plan); err != nil {
				t.Errorf("Apply: %v", err)
			}
		}(i)
	}
	wg.Wait()

	if postCount != N {
		t.Errorf("postCount = %d, want %d", postCount, N)
	}
	if maxInFlight > 1 {
		t.Errorf("max concurrent PostCommit = %d, want 1 (serialization broken)", maxInFlight)
	}
	// File must contain exactly one of the writers' contents.
	got, _ := os.ReadFile(cert)
	if !strings.HasPrefix(string(got), "WRITER-") {
		t.Errorf("file content not from any writer: %q", got)
	}
}

// TestApply_BackupRetention_KeepsLastN pins the janitor: after
// many deploys, only the last N backups remain.
func TestApply_BackupRetention_KeepsLastN(t *testing.T) {
	dir := t.TempDir()
	cert := filepath.Join(dir, "tls.crt")

	// Initial file.
	if err := os.WriteFile(cert, []byte("V0"), 0644); err != nil {
		t.Fatal(err)
	}

	const keep = 2
	for i := 1; i <= 5; i++ {
		plan := Plan{
			Files: []File{{
				Path:  cert,
				Bytes: []byte(fmt.Sprintf("V%d", i)),
			}},
			BackupRetention: keep,
		}
		if _, err := Apply(context.Background(), plan); err != nil {
			t.Fatalf("Apply iter %d: %v", i, err)
		}
		// Stagger to ensure distinct nanosecond stamps.
		time.Sleep(2 * time.Millisecond)
	}

	entries, _ := os.ReadDir(dir)
	count := 0
	for _, e := range entries {
		if strings.Contains(e.Name(), BackupSuffix) {
			count++
		}
	}
	if count != keep {
		t.Errorf("backup count after 5 deploys with retention=%d = %d, want %d", keep, count, keep)
	}
}

// TestApply_NoExistingFile_UsesDefaultsForOwnerGroupMode covers
// the first-deploy path: destination doesn't exist; FileDefaults
// applies. We verify the mode default lands; owner/group default
// is exercised in resolveOwnership unit tests (would require root
// for cross-user chown).
func TestApply_NoExistingFile_UsesDefaultsForOwnerGroupMode(t *testing.T) {
	dir := t.TempDir()
	cert := filepath.Join(dir, "tls.crt")

	plan := Plan{
		Files: []File{
			{Path: cert, Bytes: []byte(testCert1)},
		},
		Defaults: FileDefaults{Mode: 0640},
	}
	if _, err := Apply(context.Background(), plan); err != nil {
		t.Fatalf("Apply: %v", err)
	}
	stat, err := os.Stat(cert)
	if err != nil {
		t.Fatal(err)
	}
	if stat.Mode().Perm() != 0640 {
		t.Errorf("default mode for new file = %#o, want 0640", stat.Mode().Perm())
	}
}

// TestAtomicWriteFile_TempFileCleanedUpOnError checks that a
// failure mid-flight (we simulate by passing an unwritable
// directory) leaves no .certctl-tmp.* file behind.
func TestAtomicWriteFile_TempFileCleanedUpOnError(t *testing.T) {
	dir := t.TempDir()
	// Make the directory read-only AFTER the temp open would fail.
	// Easier: target a path inside a directory that doesn't exist.
	ghost := filepath.Join(dir, "does-not-exist", "tls.crt")
	_, err := AtomicWriteFile(context.Background(), ghost, []byte(testCert1), WriteOptions{})
	if err == nil {
		t.Fatal("expected error writing into nonexistent directory")
	}
	// No leaked temps in the parent (which does exist).
	entries, _ := os.ReadDir(dir)
	for _, e := range entries {
		if strings.Contains(e.Name(), TempSuffix) {
			t.Errorf("temp file leaked: %s", e.Name())
		}
	}
}

// TestAtomicWriteFile_RenameRaceWithReader_AtomicReadAlwaysSeesOldOrNew
// pins the load-bearing POSIX-rename atomicity: a concurrent
// reader hitting the destination during a write either sees the
// pre-write bytes or the post-write bytes; never an intermediate
// state.
func TestAtomicWriteFile_RenameRaceWithReader_AtomicReadAlwaysSeesOldOrNew(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "tls.crt")
	old := []byte(strings.Repeat("OLD", 1000))
	newer := []byte(strings.Repeat("NEW", 1000))
	if err := os.WriteFile(path, old, 0644); err != nil {
		t.Fatal(err)
	}

	stop := make(chan struct{})
	var torn atomic.Bool
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			select {
			case <-stop:
				return
			default:
			}
			b, err := os.ReadFile(path)
			if err != nil {
				continue
			}
			s := string(b)
			if s != string(old) && s != string(newer) {
				torn.Store(true)
				return
			}
		}
	}()

	// Issue many writes back and forth.
	for i := 0; i < 30; i++ {
		writeBytes := old
		if i%2 == 0 {
			writeBytes = newer
		}
		if _, err := AtomicWriteFile(context.Background(), path, writeBytes, WriteOptions{
			SkipIdempotent: true,
		}); err != nil {
			t.Fatalf("AtomicWriteFile %d: %v", i, err)
		}
	}
	close(stop)
	wg.Wait()
	if torn.Load() {
		t.Error("torn read observed (rename was not atomic)")
	}
}

// --- White-box tests for resolveOwnership (chown semantics under
// non-root require this, since we can't write a chown-to-root
// integration test without sudo). ---

// TestResolveOwnership_ExplicitOverride_Wins verifies that an
// explicit File.Mode/Owner/Group beats both existing-file
// preservation and Defaults fallback.
func TestResolveOwnership_ExplicitOverride_Wins(t *testing.T) {
	currentUser, _ := user.Current()
	currentGroup, _ := user.LookupGroupId(currentUser.Gid)

	dir := t.TempDir()
	path := filepath.Join(dir, "f")
	if err := os.WriteFile(path, []byte("x"), 0600); err != nil {
		t.Fatal(err)
	}
	stat, _ := os.Stat(path)
	res, err := resolveOwnership(File{
		Path:  path,
		Mode:  0644,
		Owner: currentUser.Username,
		Group: currentGroup.Name,
	}, FileDefaults{Mode: 0400, Owner: "nobody", Group: "nogroup"}, stat)
	if err != nil {
		t.Fatal(err)
	}
	if res.Mode != 0644 {
		t.Errorf("mode = %#o, want 0644 (override should win)", res.Mode)
	}
	if res.OwnerLabel != currentUser.Username {
		t.Errorf("owner label = %q, want %q (override should win)", res.OwnerLabel, currentUser.Username)
	}
}

// TestResolveOwnership_PreservesExisting_WhenNoOverride verifies
// the preservation path: no explicit override + existing file →
// existing uid/gid/mode are returned.
func TestResolveOwnership_PreservesExisting_WhenNoOverride(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "f")
	if err := os.WriteFile(path, []byte("x"), 0640); err != nil {
		t.Fatal(err)
	}
	if err := os.Chmod(path, 0640); err != nil {
		t.Fatal(err)
	}
	stat, _ := os.Stat(path)

	res, err := resolveOwnership(File{Path: path}, FileDefaults{Mode: 0400}, stat)
	if err != nil {
		t.Fatal(err)
	}
	if res.Mode != 0640 {
		t.Errorf("mode = %#o, want 0640 (preservation)", res.Mode)
	}
	uid, gid, ok := unixOwnerFromStat(stat)
	if !ok {
		t.Skip("non-unix platform")
	}
	if res.UID != uid || res.GID != gid {
		t.Errorf("uid/gid = %d/%d, want %d/%d", res.UID, res.GID, uid, gid)
	}
}

// TestResolveOwnership_NewFile_FallsBackToDefaults verifies the
// defaults path: no override + no existing file → Plan.Defaults.
func TestResolveOwnership_NewFile_FallsBackToDefaults(t *testing.T) {
	currentUser, _ := user.Current()
	currentGroup, _ := user.LookupGroupId(currentUser.Gid)

	res, err := resolveOwnership(File{Path: "/tmp/never"}, FileDefaults{
		Mode:  0640,
		Owner: currentUser.Username,
		Group: currentGroup.Name,
	}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if res.Mode != 0640 {
		t.Errorf("mode = %#o, want 0640 (default)", res.Mode)
	}
	if res.OwnerLabel != currentUser.Username {
		t.Errorf("owner = %q, want %q (default)", res.OwnerLabel, currentUser.Username)
	}
}

// TestApply_RejectsInvalidPlan_NoFiles + duplicate-paths + empty-
// path. Pin the validatePlan gate.
func TestApply_RejectsInvalidPlan(t *testing.T) {
	tests := []struct {
		name string
		plan Plan
	}{
		{"no files", Plan{}},
		{"empty path", Plan{Files: []File{{Path: "", Bytes: []byte("x")}}}},
		{"duplicate", Plan{Files: []File{
			{Path: "/tmp/dup", Bytes: []byte("a")},
			{Path: "/tmp/dup", Bytes: []byte("b")},
		}}},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			_, err := Apply(context.Background(), tc.plan)
			if !errors.Is(err, ErrPlanInvalid) {
				t.Errorf("got %v, want ErrPlanInvalid", err)
			}
		})
	}
}

// TestApply_ContextCancelledBeforeStart_AbortsCleanly pins the
// context-respect contract: a cancelled context aborts before
// any I/O.
func TestApply_ContextCancelledBeforeStart_AbortsCleanly(t *testing.T) {
	dir := t.TempDir()
	cert := filepath.Join(dir, "tls.crt")
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	_, err := Apply(ctx, Plan{
		Files: []File{{Path: cert, Bytes: []byte(testCert1)}},
	})
	if err == nil || !errors.Is(err, context.Canceled) {
		t.Errorf("got %v, want context.Canceled", err)
	}
	if _, statErr := os.Stat(cert); statErr == nil {
		t.Error("file was created despite cancelled context")
	}
}

// TestApply_NoBackupRetention_DisablesBackups pins
// BackupRetention = -1 sentinel: no backup created; rollback
// becomes impossible.
func TestApply_NoBackupRetention_DisablesBackups(t *testing.T) {
	dir := t.TempDir()
	cert := filepath.Join(dir, "tls.crt")
	if err := os.WriteFile(cert, []byte("ORIGINAL"), 0644); err != nil {
		t.Fatal(err)
	}
	plan := Plan{
		Files:           []File{{Path: cert, Bytes: []byte(testCert1)}},
		BackupRetention: -1,
	}
	if _, err := Apply(context.Background(), plan); err != nil {
		t.Fatalf("Apply: %v", err)
	}
	entries, _ := os.ReadDir(dir)
	for _, e := range entries {
		if strings.Contains(e.Name(), BackupSuffix) {
			t.Errorf("backup created despite BackupRetention=-1: %s", e.Name())
		}
	}
}

// TestAtomicWriteFile_HappyPath_ReplacesExistingAtomically covers
// the simple AtomicWriteFile path used by F5 + K8s connectors.
func TestAtomicWriteFile_HappyPath_ReplacesExistingAtomically(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "f")
	if err := os.WriteFile(path, []byte("OLD"), 0644); err != nil {
		t.Fatal(err)
	}
	res, err := AtomicWriteFile(context.Background(), path, []byte("NEW"), WriteOptions{})
	if err != nil {
		t.Fatalf("AtomicWriteFile: %v", err)
	}
	if !res.Replaced {
		t.Error("Replaced=false; want true")
	}
	if res.BackupPath == "" {
		t.Error("expected non-empty BackupPath")
	}
	if got, _ := os.ReadFile(path); string(got) != "NEW" {
		t.Errorf("file = %q, want NEW", got)
	}
}

// TestAtomicWriteFile_IdempotentSkip covers the AtomicWriteFile
// SHA-256 skip — same coverage as Plan.Apply but for the lower-
// level entry point used by F5/K8s.
func TestAtomicWriteFile_IdempotentSkip(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "f")
	if err := os.WriteFile(path, []byte("SAME"), 0644); err != nil {
		t.Fatal(err)
	}
	res, err := AtomicWriteFile(context.Background(), path, []byte("SAME"), WriteOptions{})
	if err != nil {
		t.Fatalf("AtomicWriteFile: %v", err)
	}
	if !res.Idempotent {
		t.Error("Idempotent=false; want true")
	}
	if res.Replaced {
		t.Error("Replaced=true on idempotent skip; want false")
	}
}

// TestAtomicWriteFile_RejectsEmptyPath pins the input validation.
func TestAtomicWriteFile_RejectsEmptyPath(t *testing.T) {
	_, err := AtomicWriteFile(context.Background(), "", []byte("x"), WriteOptions{})
	if !errors.Is(err, ErrPlanInvalid) {
		t.Errorf("got %v, want ErrPlanInvalid", err)
	}
}

// TestPruneBackups_NoOp_WhenUnderRetention pins the early return
// when there are fewer backups than the retention bar.
func TestPruneBackups_NoOp_WhenUnderRetention(t *testing.T) {
	dir := t.TempDir()
	abs := filepath.Join(dir, "f")
	// Create two backup-style files.
	os.WriteFile(abs+BackupSuffix+"0000000000000000001", []byte("a"), 0644)
	os.WriteFile(abs+BackupSuffix+"0000000000000000002", []byte("b"), 0644)
	if err := pruneBackups(abs, 5); err != nil {
		t.Fatal(err)
	}
	entries, _ := os.ReadDir(dir)
	count := 0
	for _, e := range entries {
		if strings.Contains(e.Name(), BackupSuffix) {
			count++
		}
	}
	if count != 2 {
		t.Errorf("count = %d, want 2 (no pruning under retention)", count)
	}
}

// TestLookupUID_Numeric covers the "numeric passthrough" branch
// of lookupUID — agents can configure with either "nginx" or "1000".
func TestLookupUID_Numeric(t *testing.T) {
	uid, err := lookupUID("12345")
	if err != nil {
		t.Fatal(err)
	}
	if uid != 12345 {
		t.Errorf("uid = %d, want 12345", uid)
	}
}

// TestLookupGID_Numeric mirror.
func TestLookupGID_Numeric(t *testing.T) {
	gid, err := lookupGID("54321")
	if err != nil {
		t.Fatal(err)
	}
	if gid != 54321 {
		t.Errorf("gid = %d, want 54321", gid)
	}
}

// TestSHA256Eq_EdgeCases pins the helper used by the idempotency
// short-circuit.
func TestSHA256Eq_EdgeCases(t *testing.T) {
	if !sha256Eq([]byte{}, []byte{}) {
		t.Error("empty == empty failed")
	}
	if sha256Eq([]byte("a"), []byte("b")) {
		t.Error("a == b unexpectedly true")
	}
	if sha256Eq([]byte("ab"), []byte("ac")) {
		t.Error("ab == ac unexpectedly true")
	}
	if !sha256Eq([]byte("abc"), []byte("abc")) {
		t.Error("abc == abc failed")
	}
}
