package main

import (
	"context"
	"testing"
	"time"

	"github.com/kami/maven/internal/loop"
	"github.com/kami/maven/internal/store"
)

// The row is the whole mechanism, and nothing wrote it (Vikunja #532).
//
// The existing hysteresis test in internal/store scores the pure function and
// passed throughout, which is exactly why this went unnoticed: Resolve was
// always correct and was always handed the cold-start Away. So this test asserts
// the round trip — the tick writes what gather resolved, and the next load
// reads it back — rather than re-testing the function.
func TestTickPersistsTheResolvedBucket(t *testing.T) {
	ctx := context.Background()
	st := newTestStore(t)
	now := time.Now()

	// Cold start: no row, so a load must say Away and the zero time.
	b, score, updated, err := st.LoadPresenceState(ctx)
	if err != nil {
		t.Fatalf("load before: %v", err)
	}
	if b != store.Away || score != 0 || !updated.IsZero() {
		t.Fatalf("cold start = %s/%v/%v, want away/0/zero", b, score, updated)
	}

	tl := &tickLoop{store: st}
	tl.savePresence(ctx, loop.State{Presence: store.Present, PresenceScore: 0.9}, now)

	b, score, updated, err = st.LoadPresenceState(ctx)
	if err != nil {
		t.Fatalf("load after: %v", err)
	}
	if b != store.Present {
		t.Errorf("bucket = %s, want present", b)
	}
	if score != 0.9 {
		t.Errorf("score = %v, want 0.9", score)
	}
	if updated.IsZero() {
		t.Error("updated_ts was not written, so /dash still reads (never)")
	}
}

// The singleton stays a singleton, and a later tick overwrites rather than
// accumulating. A row per tick would make LoadPresenceState's single-row query
// return whichever one SQLite felt like.
func TestPresenceStateIsOverwrittenNotAppended(t *testing.T) {
	ctx := context.Background()
	st := newTestStore(t)
	tl := &tickLoop{store: st}
	now := time.Now()

	tl.savePresence(ctx, loop.State{Presence: store.Present, PresenceScore: 0.9}, now)
	tl.savePresence(ctx, loop.State{Presence: store.Away, PresenceScore: 0.1}, now.Add(time.Minute))

	b, score, _, err := st.LoadPresenceState(ctx)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	if b != store.Away || score != 0.1 {
		t.Fatalf("got %s/%v, want the second write (away/0.1)", b, score)
	}
}

// What the persisted row buys: the hold band. A score sitting between Exit and
// Enter holds Present when the last bucket was Present, and stays Away when it
// was Away. Before the write existed the second arm was the only one that could
// ever run, so presence dropped at roughly four minutes of idle instead of
// holding to the exit threshold at about nine.
func TestPersistedBucketIsWhatFeedsHysteresis(t *testing.T) {
	ctx := context.Background()
	st := newTestStore(t)
	tl := &tickLoop{store: st}

	mid := (store.PresenceExit + store.PresenceEnter) / 2
	if mid <= store.PresenceExit || mid >= store.PresenceEnter {
		t.Fatalf("%v is not inside the hold band", mid)
	}

	tl.savePresence(ctx, loop.State{Presence: store.Present, PresenceScore: 0.9}, time.Now())
	last, _, _, err := st.LoadPresenceState(ctx)
	if err != nil {
		t.Fatalf("load: %v", err)
	}
	if got := store.Resolve(mid, last); got != store.Present {
		t.Errorf("Resolve(%v, %s) = %s, want present — the hold band did not apply", mid, last, got)
	}
}
