package memory

import (
	"context"
	"strconv"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func TestMemoryContentCheckpointCASAndIdempotentProjection(t *testing.T) {
	db := newMemoryCheckpointStoreTestDB(t, &Memory{}, &MemoryContentCheckpoint{})
	now := time.Now().UTC()
	require.NoError(t, db.Create(&Memory{
		ID: "mem_1", Name: "Memory", IsActive: true, ContentID: "ctn_1", ActiveContentVersionID: "ctv_1", ContentGeneration: 3,
		ContentHistorySequence: 1,
		CreatedBy:              "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now,
	}).Error)
	store := New(db)
	checkpoint := MemoryContentCheckpoint{
		ID: "mcp_1", WorkspaceID: "wsp_1", MemoryID: "mem_1", ContentID: "ctn_1", ContentVersionID: "ctv_2", ParentContentVersionID: "ctv_1",
		OperationID: "operation-1", OperationKind: "checkpoint", ReplicaID: "replica-1", CoveredFromSequence: 1, CoveredThroughSequence: 4,
		Generation: 3, HistorySequence: 2, RootDigest: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", FileCount: 1, SizeBytes: 12,
		CreatedBy: "usr_1", CreatedAt: now,
	}

	updated, err := store.AdvanceMemoryContentCheckpoint(context.Background(), "wsp_1", "mem_1", "ctn_1", "ctv_1", 3, checkpoint, "usr_1", now)
	require.NoError(t, err)
	require.Equal(t, "ctv_2", updated.ActiveContentVersionID)
	require.Equal(t, "replica-1", updated.ActiveReplicaID)
	require.EqualValues(t, 4, updated.AcknowledgedSequence)
	require.EqualValues(t, 2, updated.ContentHistorySequence)

	created, err := store.CreateMemoryContentCheckpoint(context.Background(), checkpoint)
	require.NoError(t, err)
	require.Equal(t, "ctv_2", created.ContentVersionID)
	replayed, err := store.CreateMemoryContentCheckpoint(context.Background(), checkpoint)
	require.NoError(t, err)
	require.Equal(t, created.ID, replayed.ID)

	conflict := checkpoint
	conflict.ID = "mcp_other"
	conflict.RootDigest = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
	_, err = store.CreateMemoryContentCheckpoint(context.Background(), conflict)
	require.ErrorIs(t, err, ErrMemoryContentConflict)

	_, err = store.AdvanceMemoryContentCheckpoint(context.Background(), "wsp_1", "mem_1", "ctn_1", "ctv_1", 3, checkpoint, "usr_1", now)
	require.ErrorIs(t, err, ErrMemoryContentConflict)
}

func TestListMemoryContentCheckpointsUsesBoundedLookahead(t *testing.T) {
	db := newMemoryCheckpointStoreTestDB(t, &MemoryContentCheckpoint{})
	store := New(db)
	base := time.Now().UTC()
	for index := 0; index < 3; index++ {
		suffix := strconv.Itoa(index + 1)
		require.NoError(t, db.Create(&MemoryContentCheckpoint{
			ID: "mcp_" + suffix, WorkspaceID: "wsp_1", MemoryID: "mem_1", ContentID: "ctn_1", ContentVersionID: "ctv_" + suffix,
			OperationID: "operation-" + suffix, OperationKind: "import", Generation: 1, HistorySequence: uint64(index + 1),
			RootDigest: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", CreatedBy: "usr_1", CreatedAt: base.Add(time.Duration(index) * time.Second),
		}).Error)
	}
	rows, err := store.ListMemoryContentCheckpoints(context.Background(), "wsp_1", "mem_1", 2, 0)
	require.NoError(t, err)
	require.Len(t, rows, 3)
	require.Equal(t, "ctv_3", rows[0].ContentVersionID)
	_, err = store.ListMemoryContentCheckpoints(context.Background(), "wsp_1", "mem_1", 101, 0)
	require.Error(t, err)
}

func TestFinalizeMemoryContentImportIsExactAndIdempotent(t *testing.T) {
	db := newMemoryCheckpointStoreTestDB(t, &Memory{}, &MemoryContentCheckpoint{})
	now := time.Now().UTC()
	require.NoError(t, db.Create(&Memory{ID: "mem_1", Name: "Memory", IsActive: true, CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now}).Error)
	require.NoError(t, db.Create(&MemoryContentCheckpoint{
		ID: "mcp_1", WorkspaceID: "wsp_1", MemoryID: "mem_1", ContentID: "ctn_1", ContentVersionID: "ctv_1",
		OperationID: "memory-import:one", OperationKind: "import", Generation: 1, HistorySequence: 1, RootDigest: "digest", FileCount: 1,
		SizeBytes: 10, CreatedBy: "usr_1", CreatedAt: now,
	}).Error)
	store := New(db)
	finalized, err := store.FinalizeMemoryContentImport(context.Background(), "wsp_1", "mem_1", "ctn_1", "ctv_1", 1, 1, "usr_1", now.Add(time.Second))
	require.NoError(t, err)
	require.Equal(t, "ctv_1", finalized.ActiveContentVersionID)
	require.EqualValues(t, 1, finalized.ContentHistorySequence)
	replayed, err := store.FinalizeMemoryContentImport(context.Background(), "wsp_1", "mem_1", "ctn_1", "ctv_1", 1, 1, "usr_1", now.Add(2*time.Second))
	require.NoError(t, err)
	require.Equal(t, finalized.ActiveContentVersionID, replayed.ActiveContentVersionID)
	_, err = store.FinalizeMemoryContentImport(context.Background(), "wsp_1", "mem_1", "ctn_1", "ctv_other", 1, 1, "usr_1", now.Add(3*time.Second))
	require.ErrorIs(t, err, ErrMemoryContentConflict)
}

func newMemoryCheckpointStoreTestDB(t *testing.T, models ...any) *gorm.DB {
	t.Helper()
	db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
	require.NoError(t, err)
	sqlDB, err := db.DB()
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, sqlDB.Close()) })
	require.NoError(t, db.AutoMigrate(models...))
	return db
}
