package quiesce

import (
	"context"
	"io"
	"log"
	"path/filepath"
	"sync"
	"testing"
	"time"
)

// eventStacks records an ordered event log (shared with eventBackend) so a test can assert that
// StartStack (resume) happened at the `snapshotted` poll, before `done`.
type eventStacks struct {
	mu      *sync.Mutex
	events  *[]string
	running []string
}

func (s *eventStacks) RunningAppStacks() []string { return append([]string(nil), s.running...) }
func (s *eventStacks) StopStack(string) error     { return nil }
func (s *eventStacks) StartStack(string) error {
	s.mu.Lock()
	*s.events = append(*s.events, "RESUME")
	s.mu.Unlock()
	return nil
}

type eventBackend struct {
	mu     *sync.Mutex
	events *[]string
	phases []string
	i      int
}

func (b *eventBackend) Due(context.Context) (bool, error)              { return true, nil }
func (b *eventBackend) StartBackup(context.Context) (string, error)    { return "job-1", nil }
func (b *eventBackend) BackupStatus(context.Context) (string, error) {
	ph := b.phases[len(b.phases)-1]
	if b.i < len(b.phases) {
		ph = b.phases[b.i]
		b.i++
	}
	b.mu.Lock()
	*b.events = append(*b.events, ph)
	b.mu.Unlock()
	return ph, nil
}

func eventLoop(t *testing.T, phases []string) (*Loop, *[]string, *eventStacks) {
	var mu sync.Mutex
	events := &[]string{}
	st := &eventStacks{mu: &mu, events: events, running: []string{"pgapp"}}
	be := &eventBackend{mu: &mu, events: events, phases: phases}
	l := New(Options{
		Backend: be, Stacks: st,
		MarkerPath: filepath.Join(t.TempDir(), "q.json"),
		Poll:       time.Hour, StatusPoll: time.Millisecond, MaxQuiesce: 5 * time.Second,
		Logger: log.New(io.Discard, "", 0),
	})
	return l, events, st
}

// 8B.2: resume at `snapshotted` (RESUME before `done`), then keep tracking to `done`; marker cleared.
func TestRunOnce_ResumesAtSnapshotted(t *testing.T) {
	l, events, _ := eventLoop(t, []string{"running", "snapshotted", "running", "done"})
	if err := l.runOnce(context.Background()); err != nil {
		t.Fatal(err)
	}
	// RESUME must appear and must come BEFORE the first "done".
	resumeIdx, doneIdx := -1, -1
	for i, e := range *events {
		if e == "RESUME" && resumeIdx < 0 {
			resumeIdx = i
		}
		if e == "done" && doneIdx < 0 {
			doneIdx = i
		}
	}
	if resumeIdx < 0 {
		t.Fatalf("never resumed: %v", *events)
	}
	if doneIdx < 0 {
		t.Fatalf("never tracked to done (must keep polling after early resume): %v", *events)
	}
	if resumeIdx > doneIdx {
		t.Fatalf("resumed at/after done, not at snapshotted: %v", *events)
	}
	// the event right before RESUME should be a snapshotted poll
	if (*events)[resumeIdx-1] != "snapshotted" {
		t.Fatalf("resume not triggered by snapshotted: %v", *events)
	}
	if _, ok := l.readMarker(); ok {
		t.Fatal("marker not cleared after resume")
	}
}

// Fallback: stop mode (never snapshotted) → resume at `done` (8B behavior).
func TestRunOnce_FallbackResumeAtDone(t *testing.T) {
	l, events, _ := eventLoop(t, []string{"running", "running", "done"})
	if err := l.runOnce(context.Background()); err != nil {
		t.Fatal(err)
	}
	// RESUME comes only at/after done (no snapshotted in the stream).
	for _, e := range *events {
		if e == "snapshotted" {
			t.Fatal("snapshotted appeared in stop-mode stream")
		}
	}
	last := (*events)[len(*events)-1]
	if last != "RESUME" && last != "done" {
		t.Fatalf("expected resume at done: %v", *events)
	}
}

// A backup that FAILS after snapshotted: the app is already up (resumed once), the cycle ends.
func TestRunOnce_FailAfterSnapshotted_AppStaysUp(t *testing.T) {
	l, events, _ := eventLoop(t, []string{"snapshotted", "failed"})
	if err := l.runOnce(context.Background()); err != nil {
		t.Fatal(err)
	}
	resumes := 0
	for _, e := range *events {
		if e == "RESUME" {
			resumes++
		}
	}
	if resumes != 1 {
		t.Fatalf("expected exactly one resume (at snapshotted), got %d: %v", resumes, *events)
	}
	if _, ok := l.readMarker(); ok {
		t.Fatal("marker not cleared")
	}
}
