package utils

import (
	"testing"
)

type TestUser struct {
	ID    string
	Name  string
	Email string
}

func TestMultiIndex_AddAndFind(t *testing.T) {
	idx := NewMultiIndex[TestUser](2)

	user := TestUser{"1", "John Doe", "john@example.com"}
	idx.AddItem(user, [][]string{
		{"john@example.com"},
		{"john", "doe"},
	})

	found, ok := idx.FindItem(0, []string{"john@example.com"})
	if !ok {
		t.Fatal("expected to find user by email")
	}
	if found.ID != "1" {
		t.Errorf("got ID %s, want 1", found.ID)
	}

	found, ok = idx.FindItem(1, []string{"john", "doe"})
	if !ok {
		t.Fatal("expected to find user by name")
	}
	if found.Name != "John Doe" {
		t.Errorf("got Name %s, want John Doe", found.Name)
	}
}

func TestMultiIndex_NotFound(t *testing.T) {
	idx := NewMultiIndex[TestUser](1)

	idx.AddItem(TestUser{"1", "John", "john@example.com"}, [][]string{
		{"john@example.com"},
	})

	_, ok := idx.FindItem(0, []string{"jane@example.com"})
	if ok {
		t.Error("expected not found")
	}
}

func TestMultiIndex_WrongCompositeKey(t *testing.T) {
	idx := NewMultiIndex[TestUser](1)

	idx.AddItem(TestUser{"1", "John Doe", "john@example.com"}, [][]string{
		{"john", "doe"},
	})

	_, ok := idx.FindItem(0, []string{"john", "smith"})
	if ok {
		t.Error("expected not found with wrong composite key")
	}

	_, ok = idx.FindItem(0, []string{"john"})
	if ok {
		t.Error("expected not found with partial key")
	}
}

func TestMultiIndex_KeyCollisionHandling(t *testing.T) {
	idx := NewMultiIndex[TestUser](1)

	idx.AddItem(TestUser{"1", "User1", "user1@example.com"}, [][]string{
		{"ab", "cd"},
	})

	idx.AddItem(TestUser{"2", "User2", "user2@example.com"}, [][]string{
		{"abc", "d"},
	})

	found, ok := idx.FindItem(0, []string{"ab", "cd"})
	if !ok || found.ID != "1" {
		t.Error("collision: wrong user for ['ab', 'cd']")
	}

	found, ok = idx.FindItem(0, []string{"abc", "d"})
	if !ok || found.ID != "2" {
		t.Error("collision: wrong user for ['abc', 'd']")
	}
}

func TestMultiIndex_InvalidIndex(t *testing.T) {
	idx := NewMultiIndex[TestUser](2)

	idx.AddItem(TestUser{"1", "John", "john@example.com"}, [][]string{
		{"john@example.com"},
		{"john"},
	})

	_, ok := idx.FindItem(-1, []string{"john@example.com"})
	if ok {
		t.Error("expected not found for negative index")
	}

	_, ok = idx.FindItem(5, []string{"john@example.com"})
	if ok {
		t.Error("expected not found for out of range index")
	}
}

func TestMultiIndex_Items(t *testing.T) {
	idx := NewMultiIndex[TestUser](1)

	users := []TestUser{
		{"1", "John", "john@example.com"},
		{"2", "Jane", "jane@example.com"},
		{"3", "Bob", "bob@example.com"},
	}

	for _, u := range users {
		idx.AddItem(u, [][]string{{u.Email}})
	}

	items := idx.Items()
	if len(items) != 3 {
		t.Fatalf("got %d items, want 3", len(items))
	}

	for i, u := range users {
		if items[i].ID != u.ID {
			t.Errorf("item %d: got ID %s, want %s", i, items[i].ID, u.ID)
		}
	}
}

func TestMultiIndex_ItemsIsCopy(t *testing.T) {
	idx := NewMultiIndex[TestUser](1)

	idx.AddItem(TestUser{"1", "John", "john@example.com"}, [][]string{
		{"john@example.com"},
	})

	items := idx.Items()
	items[0].Name = "Modified"

	original, _ := idx.FindItem(0, []string{"john@example.com"})
	if original.Name != "John" {
		t.Error("Items() should return a copy, not affect internal state")
	}
}

func TestMultiIndex_PanicOnWrongKeysLength(t *testing.T) {
	defer func() {
		if r := recover(); r == nil {
			t.Error("expected panic on wrong searchKeys length")
		}
	}()

	idx := NewMultiIndex[TestUser](2)
	idx.AddItem(TestUser{"1", "John", "john@example.com"}, [][]string{
		{"john@example.com"},
		// missing second index key
	})
}

func TestMultiIndex_MultipleItemsSameIndex(t *testing.T) {
	idx := NewMultiIndex[TestUser](2)

	idx.AddItem(TestUser{"1", "John Doe", "john@example.com"}, [][]string{
		{"john@example.com"},
		{"john", "doe"},
	})

	idx.AddItem(TestUser{"2", "Jane Doe", "jane@example.com"}, [][]string{
		{"jane@example.com"},
		{"jane", "doe"},
	})

	john, ok := idx.FindItem(1, []string{"john", "doe"})
	if !ok || john.ID != "1" {
		t.Error("failed to find John by name")
	}

	jane, ok := idx.FindItem(1, []string{"jane", "doe"})
	if !ok || jane.ID != "2" {
		t.Error("failed to find Jane by name")
	}
}

func TestMultiIndex_EmptyIndex(t *testing.T) {
	idx := NewMultiIndex[TestUser](1)

	_, ok := idx.FindItem(0, []string{"anything"})
	if ok {
		t.Error("expected not found in empty index")
	}

	items := idx.Items()
	if len(items) != 0 {
		t.Errorf("expected 0 items, got %d", len(items))
	}
}
