package catalogd

import (
	"context"
	"errors"
	"sync"
	"testing"
	"time"

	"git.agentview.dev/profit/golangLAKEHOUSE/internal/storeclient"
)

// memStore is an in-memory Store fake for unit tests.
type memStore struct {
	mu   sync.Mutex
	data map[string][]byte
}

func newMemStore() *memStore { return &memStore{data: map[string][]byte{}} }

func (m *memStore) Put(_ context.Context, key string, body []byte) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	cp := make([]byte, len(body))
	copy(cp, body)
	m.data[key] = cp
	return nil
}

func (m *memStore) Get(_ context.Context, key string) ([]byte, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	b, ok := m.data[key]
	if !ok {
		return nil, storeclient.ErrKeyNotFound
	}
	return b, nil
}

func (m *memStore) List(_ context.Context, prefix string) ([]string, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	out := []string{}
	for k := range m.data {
		if len(k) >= len(prefix) && k[:len(prefix)] == prefix {
			out = append(out, k)
		}
	}
	return out, nil
}

func mkRegistry(t *testing.T) (*Registry, *memStore) {
	t.Helper()
	s := newMemStore()
	r := NewRegistry(s)
	r.now = func() time.Time { return time.Unix(1777435000, 0).UTC() }
	return r, s
}

func TestRegister_NewManifest(t *testing.T) {
	r, _ := mkRegistry(t)
	rc := int64(100)
	m, existing, err := r.Register(context.Background(), "workers", "sha256:abc",
		[]Object{{Key: "datasets/workers/p1.parquet", Size: 1024}}, &rc)
	if err != nil {
		t.Fatalf("Register: %v", err)
	}
	if existing {
		t.Error("expected existing=false for new manifest")
	}
	if m.DatasetID != DatasetIDForName("workers") {
		t.Errorf("DatasetID: got %q, want UUIDv5(workers)", m.DatasetID)
	}
}

func TestRegister_SameFingerprint_Idempotent(t *testing.T) {
	r, _ := mkRegistry(t)
	rc := int64(100)
	first, _, _ := r.Register(context.Background(), "workers", "sha256:abc",
		[]Object{{Key: "p1.parquet", Size: 1024}}, &rc)

	// Re-register same name + fingerprint with new objects.
	rc2 := int64(200)
	second, existing, err := r.Register(context.Background(), "workers", "sha256:abc",
		[]Object{{Key: "p1.parquet", Size: 1024}, {Key: "p2.parquet", Size: 2048}}, &rc2)
	if err != nil {
		t.Fatalf("Register (idempotent): %v", err)
	}
	if !existing {
		t.Error("expected existing=true on idempotent re-register")
	}
	if second.DatasetID != first.DatasetID {
		t.Errorf("DatasetID changed: %q → %q", first.DatasetID, second.DatasetID)
	}
	if len(second.Objects) != 2 {
		t.Errorf("Objects not replaced: got %d, want 2", len(second.Objects))
	}
	if second.RowCount == nil || *second.RowCount != 200 {
		t.Errorf("RowCount not bumped: got %v, want 200", second.RowCount)
	}
}

func TestRegister_DifferentFingerprint_Conflict(t *testing.T) {
	r, _ := mkRegistry(t)
	_, _, _ = r.Register(context.Background(), "workers", "sha256:abc",
		[]Object{{Key: "p1.parquet", Size: 1024}}, nil)

	_, _, err := r.Register(context.Background(), "workers", "sha256:DIFFERENT",
		[]Object{{Key: "p1.parquet", Size: 1024}}, nil)
	if !errors.Is(err, ErrFingerprintConflict) {
		t.Fatalf("expected ErrFingerprintConflict, got %v", err)
	}
}

func TestRehydrate_RecoversManifests(t *testing.T) {
	// Build first registry, register 2 manifests.
	r1, store := mkRegistry(t)
	_, _, _ = r1.Register(context.Background(), "workers", "sha256:a", nil, nil)
	_, _, _ = r1.Register(context.Background(), "candidates", "sha256:b", nil, nil)

	// Build a second registry against the same store + rehydrate.
	r2 := NewRegistry(store)
	n, err := r2.Rehydrate(context.Background())
	if err != nil {
		t.Fatalf("Rehydrate: %v", err)
	}
	if n != 2 {
		t.Errorf("recovered %d, want 2", n)
	}
	if _, err := r2.Get("workers"); err != nil {
		t.Errorf("Get(workers): %v", err)
	}
	if _, err := r2.Get("candidates"); err != nil {
		t.Errorf("Get(candidates): %v", err)
	}
}

func TestList_Sorted(t *testing.T) {
	r, _ := mkRegistry(t)
	_, _, _ = r.Register(context.Background(), "zoo", "fp", nil, nil)
	_, _, _ = r.Register(context.Background(), "alpha", "fp", nil, nil)
	_, _, _ = r.Register(context.Background(), "midway", "fp", nil, nil)
	got := r.List()
	want := []string{"alpha", "midway", "zoo"}
	for i, m := range got {
		if m.Name != want[i] {
			t.Errorf("List[%d]: got %q, want %q", i, m.Name, want[i])
		}
	}
}

func TestRegister_RejectsEmptyInputs(t *testing.T) {
	r, _ := mkRegistry(t)
	_, _, err := r.Register(context.Background(), "", "fp", nil, nil)
	if err == nil {
		t.Error("expected error on empty name")
	}
	_, _, err = r.Register(context.Background(), "x", "", nil, nil)
	if err == nil {
		t.Error("expected error on empty fingerprint")
	}
}
