//go:build !vectors

package memorysearch

import (
	"context"
	"errors"
	"os"
	"path/filepath"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func newLifecycleTestStore(t *testing.T) (*lifecycleStore, *buildTestHarness, *time.Time) {
	t.Helper()
	h := newBuildTestHarness(t, map[string]string{
		"system/identity.md": "# Identity\n\nbody\n",
	})
	now := time.Date(2026, 7, 16, 3, 0, 0, 0, time.UTC)
	store := newLifecycleStore(h.derivativeRoot, h.binding, func() time.Time {
		return now
	})
	return store, h, &now
}

func TestLifecycleRecordPublishAndSupersede(t *testing.T) {
	store, _, now := newLifecycleTestStore(t)
	first := Current{
		BindingFingerprint: store.expectedFP,
		GenerationBasename: "gen-a",
		PublishedAt:        *now,
	}
	require.NoError(t, store.recordPublish(first, ""))

	*now = now.Add(time.Hour)
	second := Current{
		BindingFingerprint: store.expectedFP,
		GenerationBasename: "gen-b",
		PublishedAt:        *now,
	}
	require.NoError(t, store.recordPublish(second, "gen-a"))

	state, err := store.load()
	require.NoError(t, err)
	require.Equal(t, BindingLifecycleActive, state.BindingState)
	require.Equal(t, GenerationLifecycleCurrent, state.Generations["gen-b"].Kind)
	rollback := state.Generations["gen-a"]
	require.Equal(t, GenerationLifecycleRollback, rollback.Kind)
	require.NotNil(t, rollback.SupersededAt)
	require.Equal(t, second.PublishedAt, *rollback.SupersededAt)
}

func TestLifecycleMarkQuerySuccessFirstWriteWins(t *testing.T) {
	store, h, now := newLifecycleTestStore(t)
	current := Current{
		BindingFingerprint: store.expectedFP,
		GenerationBasename: "gen-a",
		PublishedAt:        *now,
	}
	require.NoError(t, writeCurrentAtomic(h.derivativeRoot, current))
	require.NoError(t, store.recordPublish(current, ""))

	firstSuccess := now.Add(15 * time.Minute)
	*now = firstSuccess
	const callers = 16
	var wg sync.WaitGroup
	errs := make(chan error, callers)
	for range callers {
		wg.Add(1)
		go func() {
			defer wg.Done()
			errs <- store.markQuerySuccess(context.Background(), "gen-a")
		}()
	}
	wg.Wait()
	close(errs)
	for err := range errs {
		require.NoError(t, err)
	}
	*now = now.Add(time.Hour)
	require.NoError(t, store.markQuerySuccess(context.Background(), "gen-a"))

	state, err := store.load()
	require.NoError(t, err)
	require.NotNil(t, state.Generations["gen-a"].FirstSuccessfulQueryAt)
	require.Equal(t, firstSuccess,
		*state.Generations["gen-a"].FirstSuccessfulQueryAt)
}

func TestLifecycleMarkQuerySuccessRejectsSupersededGeneration(t *testing.T) {
	store, h, now := newLifecycleTestStore(t)
	first := Current{
		BindingFingerprint: store.expectedFP,
		GenerationBasename: "gen-a",
		PublishedAt:        *now,
	}
	require.NoError(t, store.recordPublish(first, ""))
	*now = now.Add(time.Hour)
	second := Current{
		BindingFingerprint: store.expectedFP,
		GenerationBasename: "gen-b",
		PublishedAt:        *now,
	}
	require.NoError(t, writeCurrentAtomic(h.derivativeRoot, second))
	require.NoError(t, store.recordPublish(second, "gen-a"))

	err := store.markQuerySuccess(context.Background(), "gen-a")
	require.ErrorIs(t, err, ErrQueryGenerationSuperseded)
	state, loadErr := store.load()
	require.NoError(t, loadErr)
	require.Nil(t, state.Generations["gen-a"].FirstSuccessfulQueryAt)
}

func TestLifecycleCorruptFailsClosedUntilPublishRepairs(t *testing.T) {
	store, h, now := newLifecycleTestStore(t)
	path := filepath.Join(h.derivativeRoot, lifecycleFileName)
	require.NoError(t, os.WriteFile(path, []byte(`{"schema_version":1,"broken":true}`), 0o600))
	_, err := store.load()
	require.ErrorIs(t, err, ErrLifecycleCorrupt)

	current := Current{
		BindingFingerprint: store.expectedFP,
		GenerationBasename: "gen-repaired",
		PublishedAt:        *now,
	}
	require.NoError(t, store.recordPublish(current, ""))
	state, err := store.load()
	require.NoError(t, err)
	require.Equal(t, GenerationLifecycleCurrent,
		state.Generations["gen-repaired"].Kind)
}

func TestLifecycleRejectsForgedBasenameAndBinding(t *testing.T) {
	store, h, now := newLifecycleTestStore(t)
	err := store.recordPublish(Current{
		BindingFingerprint: store.expectedFP,
		GenerationBasename: "../escape",
		PublishedAt:        *now,
	}, "")
	require.Error(t, err)

	forged := &LifecycleState{
		SchemaVersion:      lifecycleSchemaVersion,
		BindingFingerprint: "other",
		BindingState:       BindingLifecycleActive,
		BindingChangedAt:   *now,
		Generations:        map[string]GenerationLifecycle{},
	}
	require.NoError(t, writeJSONAtomic(
		filepath.Join(h.derivativeRoot, lifecycleFileName), forged))
	_, err = store.load()
	require.ErrorIs(t, err, ErrLifecycleBindingMismatch)
}

func TestLifecycleMarkQuerySuccessHonorsCancellation(t *testing.T) {
	store, _, _ := newLifecycleTestStore(t)
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	err := store.markQuerySuccess(ctx, "gen-a")
	require.True(t, errors.Is(err, context.Canceled))
}
