package backup

// REVIEW ROUND 2 (2026-10-24), HUNT-1 H1 — an EVIDENCE test kept in the audit, NOT committed to the repo.
// A restore of a DRIVE-backed app is cut after its undo copy (kept on the app's drive) is recorded. At the next
// start the drive is not bound yet: Recover finds the copy „gone", the gated start refuses (drive absent), the
// marker and its Undo are KEPT. The drive then binds and the app runs (the boot recreate checks only „held") and the
// household writes. At the NEXT controller start Recover finds the copy present and pours it back.

import (
	"io"
	"log"
	"os"
	"path/filepath"
	"testing"
)

type recordingPutBacker struct{ calls []string }

func (p *recordingPutBacker) PutBackInterruptedRestore(stack, dir string) error {
	p.calls = append(p.calls, stack+"@"+dir)
	return nil
}

func TestReview2H1_DriveBackedCutRestore_PutBackRunsAfterTheAppAlreadyRan(t *testing.T) {
	root := t.TempDir()
	marker := filepath.Join(root, "appstop-state.json")
	drive := filepath.Join(root, "drive")
	undo := filepath.Join(drive, "ns", "backups", "restore-undo", "immich")
	if err := os.MkdirAll(undo, 0o755); err != nil {
		t.Fatal(err)
	}
	quiet := log.New(io.Discard, "", 0)

	// the restore: Begin, the copy recorded, then the power cut (no End)
	g0 := NewAppStopGuard(marker, quiet)
	if err := g0.Begin("restore-1", ReasonUnitRestore, []string{"immich"}); err != nil {
		t.Fatal(err)
	}
	if err := g0.SetUndo("immich", undo); err != nil {
		t.Fatal(err)
	}

	// boot 1: the drive is not bound yet (its path is absent in the controller's view)
	if err := os.Rename(drive, drive+".unbound"); err != nil {
		t.Fatal(err)
	}
	pb1 := &recordingPutBacker{}
	g1 := NewAppStopGuard(marker, quiet)
	g1.SetStarter(&gatingStarter{refuse: map[string]string{"immich": "the data drive is not present"}})
	g1.SetPutBacker(pb1)
	r1 := g1.Recover()
	t.Logf("boot 1: put-backs=%v refused=%v failed=%v restarted=%v marker kept=%v", pb1.calls, r1.Refused, r1.Failed, r1.Restarted, r2Exists(marker))
	if len(pb1.calls) != 0 || len(r1.Refused) != 1 || !r2Exists(marker) {
		t.Fatalf("boot 1 did not take the copy-gone, refused, marker-kept path")
	}

	// the drive binds; the boot recreate starts the app (it checks only appHeld) and the household writes
	if err := os.Rename(drive+".unbound", drive); err != nil {
		t.Fatal(err)
	}
	householdWrite := filepath.Join(drive, "ns", "new-photo-after-boot")
	if err := os.WriteFile(householdWrite, []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}

	// boot 2 (any controller restart, e.g. the nightly self-update): the marker is still active
	pb2 := &recordingPutBacker{}
	g2 := NewAppStopGuard(marker, quiet)
	g2.SetStarter(&fakeStarter{})
	g2.SetPutBacker(pb2)
	r2 := g2.Recover()
	t.Logf("boot 2: put-backs=%v restarted=%v marker kept=%v", pb2.calls, r2.Restarted, r2Exists(marker))
	if len(pb2.calls) == 1 {
		t.Logf("DEFECT SHOWN: the copy taken BEFORE the restore is poured back AFTER the app already ran on the drive and the household wrote (%s)", householdWrite)
	} else {
		t.Fatalf("no put-back at boot 2 — the chain does not hold as described")
	}
}

func r2Exists(p string) bool { _, err := os.Stat(p); return err == nil }
