package datastore

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

func TestListConfirmedFileStorageTiersPaginatesCoreFiles(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	now := time.Now().UTC()
	for _, file := range []*File{
		{FileID: "a", SizeBytes: 10, StorageType: StorageDB9, Status: StatusConfirmed, CreatedAt: now, ConfirmedAt: &now},
		{FileID: "b", SizeBytes: 20, StorageType: StorageS3, Status: StatusConfirmed, CreatedAt: now, ConfirmedAt: &now},
		{FileID: "c", SizeBytes: 30, StorageType: StorageDB9, Status: StatusPending, CreatedAt: now},
	} {
		if err := s.InsertFile(ctx, file); err != nil {
			t.Fatalf("insert %s: %v", file.FileID, err)
		}
	}

	page, cursor, err := s.ListConfirmedFileStorageTiers(ctx, "", 1)
	if err != nil {
		t.Fatal(err)
	}
	if len(page) != 1 || page[0].FileID != "a" || page[0].StorageType != StorageDB9 || cursor != "a" {
		t.Fatalf("first page = %+v cursor=%q", page, cursor)
	}
	page, cursor, err = s.ListConfirmedFileStorageTiers(ctx, cursor, 1)
	if err != nil {
		t.Fatal(err)
	}
	if len(page) != 1 || page[0].FileID != "b" || page[0].StorageType != StorageS3 || cursor != "b" {
		t.Fatalf("second page = %+v cursor=%q", page, cursor)
	}
	page, cursor, err = s.ListConfirmedFileStorageTiers(ctx, cursor, 1)
	if err != nil {
		t.Fatal(err)
	}
	if len(page) != 0 || cursor != "" {
		t.Fatalf("last page = %+v cursor=%q, want empty", page, cursor)
	}
}

func TestListConfirmedFileStorageTiersReturnsUnknownStorageType(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	now := time.Now().UTC()
	if err := s.InsertFile(ctx, &File{
		FileID: "future-core", SizeBytes: 13, StorageType: StorageType("future-tier"),
		Status: StatusConfirmed, CreatedAt: now, ConfirmedAt: &now,
	}); err != nil {
		t.Fatal(err)
	}

	page, cursor, err := s.ListConfirmedFileStorageTiers(ctx, "", 10)
	if err != nil {
		t.Fatal(err)
	}
	if len(page) != 1 || page[0].FileID != "future-core" || page[0].StorageType != StorageType("future-tier") {
		t.Fatalf("page = %+v, want unknown core file returned for readiness protection", page)
	}
	if cursor != "" {
		t.Fatalf("cursor = %q, want final empty cursor", cursor)
	}
}

func TestListFSLayerStorageTiersCountsEveryPersistedPayloadVersion(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "layer-a", BaseRootPath: "/repo"}); err != nil {
		t.Fatal(err)
	}
	entries := []*FSLayerEntry{
		{LayerID: "layer-a", Path: "/repo/a.txt", Op: FSLayerEntryOpUpsert, Kind: FSLayerEntryKindFile, ContentBlob: []byte("old"), SizeBytes: 3},
		{LayerID: "layer-a", Path: "/repo/a.txt", Op: FSLayerEntryOpUpsert, Kind: FSLayerEntryKindFile, ContentBlob: []byte("newer"), SizeBytes: 5},
		{LayerID: "layer-a", Path: "/repo/b.bin", Op: FSLayerEntryOpUpsert, Kind: FSLayerEntryKindFile, StorageType: string(StorageS3), StorageRef: "layers/layer-a/b", SizeBytes: 7},
		{LayerID: "layer-a", Path: "/repo/a.txt", Op: FSLayerEntryOpWhiteout, Kind: FSLayerEntryKindFile},
		{LayerID: "layer-a", Path: "/repo/dir/", Op: FSLayerEntryOpMkdir, Kind: FSLayerEntryKindDir},
	}
	for _, entry := range entries {
		if err := s.UpsertFSLayerEntry(ctx, entry); err != nil {
			t.Fatalf("upsert %s/%s: %v", entry.Path, entry.Op, err)
		}
	}

	page, cursor, err := s.ListFSLayerStorageTiers(ctx, FSLayerStorageTierCursor{}, 2)
	if err != nil {
		t.Fatal(err)
	}
	if len(page) != 2 {
		t.Fatalf("first page length = %d, want 2: %+v", len(page), page)
	}
	if cursor.LayerID != "layer-a" || cursor.EntrySeq != page[1].EntrySeq || cursor.PathHash != page[1].PathHash {
		t.Fatalf("first cursor = %+v, want identity of second payload", cursor)
	}

	secondPage, cursor, err := s.ListFSLayerStorageTiers(ctx, cursor, 2)
	if err != nil {
		t.Fatal(err)
	}
	if len(secondPage) != 1 {
		t.Fatalf("second page length = %d, want 1: %+v", len(secondPage), secondPage)
	}
	if cursor != (FSLayerStorageTierCursor{}) {
		t.Fatalf("final cursor = %+v, want empty", cursor)
	}

	all := append(page, secondPage...)
	got := map[StorageType]map[int64]int{
		StorageDB9: {},
		StorageS3:  {},
	}
	for _, item := range all {
		got[item.StorageType][item.SizeBytes]++
	}
	if got[StorageDB9][3] != 1 || got[StorageDB9][5] != 1 || got[StorageS3][7] != 1 || len(all) != 3 {
		t.Fatalf("all payloads = %+v, want db9 sizes 3/5 and s3 size 7 exactly once", all)
	}
}

func TestListFSLayerStorageTiersReturnsUnknownStorageTypeAsUnclassified(t *testing.T) {
	s := newTestStore(t)
	ctx := context.Background()
	if err := s.CreateFSLayer(ctx, &FSLayer{LayerID: "layer-future", BaseRootPath: "/repo"}); err != nil {
		t.Fatal(err)
	}
	entry := &FSLayerEntry{
		LayerID: "layer-future", Path: "/repo/future.bin", Op: FSLayerEntryOpUpsert,
		Kind: FSLayerEntryKindFile, StorageType: "future-tier", StorageRef: "layers/layer-future/object", SizeBytes: 11,
	}
	if err := s.UpsertFSLayerEntry(ctx, entry); err != nil {
		t.Fatal(err)
	}

	page, cursor, err := s.ListFSLayerStorageTiers(ctx, FSLayerStorageTierCursor{}, 10)
	if err != nil {
		t.Fatal(err)
	}
	if len(page) != 1 {
		t.Fatalf("page = %+v, want unknown payload returned for unclassified accounting", page)
	}
	if page[0].SizeBytes != 11 || page[0].StorageType != StorageType("future-tier") {
		t.Fatalf("payload = %+v, want size 11 and future-tier", page[0])
	}
	if cursor != (FSLayerStorageTierCursor{}) {
		t.Fatalf("cursor = %+v, want final empty cursor", cursor)
	}
}
