package store

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

// snoozeNudge records a nudge and immediately snoozes it at ts.
func snoozeNudge(t *testing.T, s *Store, rule string, ts time.Time) {
	t.Helper()
	ctx := context.Background()
	id, err := s.RecordNudge(ctx, rule, "voice", "drink water", ts)
	if err != nil {
		t.Fatalf("RecordNudge: %v", err)
	}
	if err := s.ResolveNudge(ctx, id, NudgeSnoozed, ts); err != nil {
		t.Fatalf("ResolveNudge: %v", err)
	}
}

func TestSnoozedUntilPerRule(t *testing.T) {
	s := newTestStore(t)
	now := time.Now().UTC().Truncate(time.Millisecond)

	snoozeNudge(t, s, "water", now.Add(-10*time.Minute))
	snoozeNudge(t, s, "break", now.Add(-30*time.Minute))

	got, err := s.SnoozedUntil(context.Background(), now)
	if err != nil {
		t.Fatalf("SnoozedUntil: %v", err)
	}
	if len(got) != 2 {
		t.Fatalf("want 2 snoozed rules, got %v", got)
	}
	wantWater := now.Add(-10 * time.Minute).Add(SnoozeDuration)
	if !got["water"].Equal(wantWater) {
		t.Fatalf("water until = %v, want %v", got["water"], wantWater)
	}
}

// The map must only ever hold the newest snooze for a rule, so a stale one
// can't shorten (or lengthen) the live one.
func TestSnoozedUntilUsesNewestSnooze(t *testing.T) {
	s := newTestStore(t)
	now := time.Now().UTC().Truncate(time.Millisecond)

	snoozeNudge(t, s, "water", now.Add(-90*time.Minute))
	snoozeNudge(t, s, "water", now.Add(-5*time.Minute))

	got, err := s.SnoozedUntil(context.Background(), now)
	if err != nil {
		t.Fatalf("SnoozedUntil: %v", err)
	}
	want := now.Add(-5 * time.Minute).Add(SnoozeDuration)
	if !got["water"].Equal(want) {
		t.Fatalf("water until = %v, want %v", got["water"], want)
	}
}

// A snooze must expire. If this ever regresses Maven goes quiet forever and
// nobody can tell why.
func TestSnoozedUntilExpires(t *testing.T) {
	s := newTestStore(t)
	now := time.Now().UTC().Truncate(time.Millisecond)

	snoozeNudge(t, s, "water", now.Add(-SnoozeDuration-time.Minute))

	got, err := s.SnoozedUntil(context.Background(), now)
	if err != nil {
		t.Fatalf("SnoozedUntil: %v", err)
	}
	if _, ok := got["water"]; ok {
		t.Fatalf("expired snooze still active: %v", got)
	}
}

// Other outcomes are not snoozes.
func TestSnoozedUntilIgnoresOtherOutcomes(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	now := time.Now().UTC().Truncate(time.Millisecond)

	for _, outcome := range []string{NudgeActed, NudgeIgnored} {
		id, err := s.RecordNudge(ctx, "water", "voice", "drink water", now)
		if err != nil {
			t.Fatalf("RecordNudge: %v", err)
		}
		if err := s.ResolveNudge(ctx, id, outcome, now); err != nil {
			t.Fatalf("ResolveNudge: %v", err)
		}
	}
	if _, err := s.RecordNudge(ctx, "break", "voice", "stand up", now); err != nil {
		t.Fatalf("RecordNudge: %v", err)
	}

	got, err := s.SnoozedUntil(ctx, now)
	if err != nil {
		t.Fatalf("SnoozedUntil: %v", err)
	}
	if len(got) != 0 {
		t.Fatalf("want no snoozes, got %v", got)
	}
}
