package eventstore

import (
	"database/sql"
	"reflect"
	"testing"
	"time"

	"github.com/zitadel/zitadel/internal/v2/database"
)

func TestPaginationOpt(t *testing.T) {
	type args struct {
		opts []paginationOpt
	}
	tests := []struct {
		name string
		args args
		want *Pagination
	}{
		{
			name: "desc",
			args: args{
				opts: []paginationOpt{
					Descending(),
				},
			},
			want: &Pagination{
				desc: true,
			},
		},
		{
			name: "limit",
			args: args{
				opts: []paginationOpt{
					Limit(10),
				},
			},
			want: &Pagination{
				pagination: &database.Pagination{
					Limit: 10,
				},
			},
		},
		{
			name: "offset",
			args: args{
				opts: []paginationOpt{
					Offset(10),
				},
			},
			want: &Pagination{
				pagination: &database.Pagination{
					Offset: 10,
				},
			},
		},
		{
			name: "limit and offset",
			args: args{
				opts: []paginationOpt{
					Limit(10),
					Offset(20),
				},
			},
			want: &Pagination{
				pagination: &database.Pagination{
					Limit:  10,
					Offset: 20,
				},
			},
		},
		{
			name: "global position greater",
			args: args{
				opts: []paginationOpt{
					GlobalPositionGreater(&GlobalPosition{Position: 10}),
				},
			},
			want: &Pagination{
				position: &PositionCondition{
					min: &GlobalPosition{
						Position:        10,
						InPositionOrder: 0,
					},
				},
			},
		},
		{
			name: "position greater",
			args: args{
				opts: []paginationOpt{
					PositionGreater(10, 0),
				},
			},
			want: &Pagination{
				position: &PositionCondition{
					min: &GlobalPosition{
						Position:        10,
						InPositionOrder: 0,
					},
				},
				desc: false,
			},
		},
		{
			name: "position less",
			args: args{
				opts: []paginationOpt{
					PositionLess(10, 12),
				},
			},
			want: &Pagination{
				position: &PositionCondition{
					max: &GlobalPosition{
						Position:        10,
						InPositionOrder: 12,
					},
				},
			},
		},
		{
			name: "global position less",
			args: args{
				opts: []paginationOpt{
					GlobalPositionLess(&GlobalPosition{Position: 12, InPositionOrder: 24}),
				},
			},
			want: &Pagination{
				position: &PositionCondition{
					max: &GlobalPosition{
						Position:        12,
						InPositionOrder: 24,
					},
				},
			},
		},
		{
			name: "position between",
			args: args{
				opts: []paginationOpt{
					PositionBetween(
						&GlobalPosition{10, 12},
						&GlobalPosition{20, 0},
					),
				},
			},
			want: &Pagination{
				position: &PositionCondition{
					min: &GlobalPosition{
						Position:        10,
						InPositionOrder: 12,
					},
					max: &GlobalPosition{
						Position:        20,
						InPositionOrder: 0,
					},
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := new(Pagination)
			for _, opt := range tt.args.opts {
				opt(got)
			}

			if tt.want.Desc() != got.Desc() {
				t.Errorf("unexpected desc %v, want: %v", got.desc, tt.want.desc)
			}
			if !reflect.DeepEqual(tt.want.Pagination(), got.Pagination()) {
				t.Errorf("unexpected pagination %v, want: %v", got.pagination, tt.want.pagination)
			}
			if !reflect.DeepEqual(tt.want.Position(), got.Position()) {
				t.Errorf("unexpected position %v, want: %v", got.position, tt.want.position)
			}
			if !reflect.DeepEqual(tt.want.Position().Max(), got.Position().Max()) {
				t.Errorf("unexpected position.max %v, want: %v", got.Position().max, tt.want.Position().max)
			}
			if !reflect.DeepEqual(tt.want.Position().Min(), got.Position().Min()) {
				t.Errorf("unexpected position.min %v, want: %v", got.Position().min, tt.want.Position().min)
			}
		})
	}
}

func TestEventFilterOpt(t *testing.T) {
	type args struct {
		opts []EventFilterOpt
	}
	now := time.Now()
	tests := []struct {
		name string
		args args
		want *EventFilter
	}{
		{
			name: "EventType",
			args: args{
				opts: []EventFilterOpt{
					SetEventType("test"),
					SetEventType("test2"),
				},
			},
			want: &EventFilter{
				types: []string{"test2"},
			},
		},
		{
			name: "EventTypes",
			args: args{
				opts: []EventFilterOpt{
					SetEventTypes("a", "s"),
					SetEventTypes("d", "f"),
				},
			},
			want: &EventFilter{
				types: []string{"d", "f"},
			},
		},
		{
			name: "AppendEventTypes",
			args: args{
				opts: []EventFilterOpt{
					AppendEventTypes("a", "s"),
					AppendEventTypes("d", "f"),
				},
			},
			want: &EventFilter{
				types: []string{"a", "s", "d", "f"},
			},
		},
		{
			name: "EventRevisionEquals",
			args: args{
				opts: []EventFilterOpt{
					EventRevisionEquals(12),
				},
			},
			want: &EventFilter{
				revision: &filter[uint16]{
					condition: database.NewNumberEquals[uint16](12),
					value:     toPtr(uint16(12)),
				},
			},
		},
		{
			name: "EventRevisionAtLeast",
			args: args{
				opts: []EventFilterOpt{
					EventRevisionAtLeast(12),
				},
			},
			want: &EventFilter{
				revision: &filter[uint16]{
					condition: database.NewNumberAtLeast[uint16](12),
					value:     toPtr(uint16(12)),
				},
			},
		},
		{
			name: "EventRevisionGreater",
			args: args{
				opts: []EventFilterOpt{
					EventRevisionGreater(12),
				},
			},
			want: &EventFilter{
				revision: &filter[uint16]{
					condition: database.NewNumberGreater[uint16](12),
					value:     toPtr(uint16(12)),
				},
			},
		},
		{
			name: "EventRevisionAtMost",
			args: args{
				opts: []EventFilterOpt{
					EventRevisionAtMost(12),
				},
			},
			want: &EventFilter{
				revision: &filter[uint16]{
					condition: database.NewNumberAtMost[uint16](12),
					value:     toPtr(uint16(12)),
				},
			},
		},
		{
			name: "EventRevisionLess",
			args: args{
				opts: []EventFilterOpt{
					EventRevisionLess(12),
				},
			},
			want: &EventFilter{
				revision: &filter[uint16]{
					condition: database.NewNumberLess[uint16](12),
					value:     toPtr(uint16(12)),
				},
			},
		},
		{
			name: "EventRevisionBetween",
			args: args{
				opts: []EventFilterOpt{
					EventRevisionBetween(12, 20),
				},
			},
			want: &EventFilter{
				revision: &filter[uint16]{
					condition: database.NewNumberBetween[uint16](12, 20),
					min:       toPtr(uint16(12)),
					max:       toPtr(uint16(20)),
				},
			},
		},
		{
			name: "EventCreatedAtEquals",
			args: args{
				opts: []EventFilterOpt{
					EventCreatedAtEquals(now),
				},
			},
			want: &EventFilter{
				createdAt: &filter[time.Time]{
					condition: database.NewNumberEquals(now),
					value:     toPtr(now),
				},
			},
		},
		{
			name: "EventCreatedAtAtLeast",
			args: args{
				opts: []EventFilterOpt{
					EventCreatedAtAtLeast(now),
				},
			},
			want: &EventFilter{
				createdAt: &filter[time.Time]{
					condition: database.NewNumberAtLeast(now),
					value:     toPtr(now),
				},
			},
		},
		{
			name: "EventCreatedAtGreater",
			args: args{
				opts: []EventFilterOpt{
					EventCreatedAtGreater(now),
				},
			},
			want: &EventFilter{
				createdAt: &filter[time.Time]{
					condition: database.NewNumberGreater(now),
					value:     toPtr(now),
				},
			},
		},
		{
			name: "EventCreatedAtAtMost",
			args: args{
				opts: []EventFilterOpt{
					EventCreatedAtAtMost(now),
				},
			},
			want: &EventFilter{
				createdAt: &filter[time.Time]{
					condition: database.NewNumberAtMost(now),
					value:     toPtr(now),
				},
			},
		},
		{
			name: "EventCreatedAtLess",
			args: args{
				opts: []EventFilterOpt{
					EventCreatedAtLess(now),
				},
			},
			want: &EventFilter{
				createdAt: &filter[time.Time]{
					condition: database.NewNumberLess(now),
					value:     toPtr(now),
				},
			},
		},
		{
			name: "EventCreatedAtBetween",
			args: args{
				opts: []EventFilterOpt{
					EventCreatedAtBetween(now, now.Add(1*time.Second)),
				},
			},
			want: &EventFilter{
				createdAt: &filter[time.Time]{
					condition: database.NewNumberBetween(now, now.Add(1*time.Second)),
					min:       toPtr(now),
					max:       toPtr(now.Add(1 * time.Second)),
				},
			},
		},
		{
			name: "EventSequenceEquals",
			args: args{
				opts: []EventFilterOpt{
					EventSequenceEquals(12),
				},
			},
			want: &EventFilter{
				sequence: &filter[uint32]{
					condition: database.NewNumberEquals[uint32](12),
					value:     toPtr(uint32(12)),
				},
			},
		},
		{
			name: "EventSequenceAtLeast",
			args: args{
				opts: []EventFilterOpt{
					EventSequenceAtLeast(12),
				},
			},
			want: &EventFilter{
				sequence: &filter[uint32]{
					condition: database.NewNumberAtLeast[uint32](12),
					value:     toPtr(uint32(12)),
				},
			},
		},
		{
			name: "EventSequenceGreater",
			args: args{
				opts: []EventFilterOpt{
					EventSequenceGreater(12),
				},
			},
			want: &EventFilter{
				sequence: &filter[uint32]{
					condition: database.NewNumberGreater[uint32](12),
					value:     toPtr(uint32(12)),
				},
			},
		},
		{
			name: "EventSequenceAtMost",
			args: args{
				opts: []EventFilterOpt{
					EventSequenceAtMost(12),
				},
			},
			want: &EventFilter{
				sequence: &filter[uint32]{
					condition: database.NewNumberAtMost[uint32](12),
					value:     toPtr(uint32(12)),
				},
			},
		},
		{
			name: "EventSequenceLess",
			args: args{
				opts: []EventFilterOpt{
					EventSequenceLess(12),
				},
			},
			want: &EventFilter{
				sequence: &filter[uint32]{
					condition: database.NewNumberLess[uint32](12),
					value:     toPtr(uint32(12)),
				},
			},
		},
		{
			name: "EventSequenceBetween",
			args: args{
				opts: []EventFilterOpt{
					EventSequenceBetween(12, 24),
				},
			},
			want: &EventFilter{
				sequence: &filter[uint32]{
					condition: database.NewNumberBetween[uint32](12, 24),
					min:       toPtr(uint32(12)),
					max:       toPtr(uint32(24)),
				},
			},
		},
		{
			name: "EventCreatorsEqual",
			args: args{
				opts: []EventFilterOpt{
					EventCreatorsEqual("cr", "ea", "tor"),
				},
			},
			want: &EventFilter{
				creators: &filter[[]string]{
					condition: database.NewListEquals("cr", "ea", "tor"),
					value:     toPtr([]string{"cr", "ea", "tor"}),
				},
			},
		},
		{
			name: "EventCreatorsEqual no params",
			args: args{
				opts: []EventFilterOpt{
					EventCreatorsEqual(),
				},
			},
			want: &EventFilter{},
		},
		{
			name: "EventCreatorsEqual one params",
			args: args{
				opts: []EventFilterOpt{
					EventCreatorsEqual("asdf"),
				},
			},
			want: &EventFilter{
				creators: &filter[[]string]{
					condition: database.NewTextEqual("asdf"),
					value:     toPtr([]string{"asdf"}),
				},
			},
		},
		{
			name: "EventCreatorsContains",
			args: args{
				opts: []EventFilterOpt{
					EventCreatorsContains("cr", "ea", "tor"),
				},
			},
			want: &EventFilter{
				creators: &filter[[]string]{
					condition: database.NewListContains("cr", "ea", "tor"),
					value:     toPtr([]string{"cr", "ea", "tor"}),
				},
			},
		},
		{
			name: "EventCreatorsContains no params",
			args: args{
				opts: []EventFilterOpt{
					EventCreatorsContains(),
				},
			},
			want: &EventFilter{},
		},
		{
			name: "EventCreatorsContains one params",
			args: args{
				opts: []EventFilterOpt{
					EventCreatorsContains("asdf"),
				},
			},
			want: &EventFilter{
				creators: &filter[[]string]{
					condition: database.NewTextEqual("asdf"),
					value:     toPtr([]string{"asdf"}),
				},
			},
		},
		{
			name: "EventCreatorsNotContains",
			args: args{
				opts: []EventFilterOpt{
					EventCreatorsNotContains("cr", "ea", "tor"),
				},
			},
			want: &EventFilter{
				creators: &filter[[]string]{
					condition: database.NewListNotContains("cr", "ea", "tor"),
					value:     toPtr([]string{"cr", "ea", "tor"}),
				},
			},
		},
		{
			name: "EventCreatorsNotContains no params",
			args: args{
				opts: []EventFilterOpt{
					EventCreatorsNotContains(),
				},
			},
			want: &EventFilter{},
		},
		{
			name: "EventCreatorsNotContains one params",
			args: args{
				opts: []EventFilterOpt{
					EventCreatorsNotContains("asdf"),
				},
			},
			want: &EventFilter{
				creators: &filter[[]string]{
					condition: database.NewTextUnequal("asdf"),
					value:     toPtr([]string{"asdf"}),
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := NewEventFilter(tt.args.opts...)

			if !reflect.DeepEqual(tt.want.Types(), got.Types()) {
				t.Errorf("unexpected types %v, want: %v", got.types, tt.want.types)
			}
			if !reflect.DeepEqual(tt.want.Revision(), got.Revision()) {
				t.Errorf("unexpected revision %v, want: %v", got.revision, tt.want.revision)
			}
			if !reflect.DeepEqual(tt.want.CreatedAt(), got.CreatedAt()) {
				t.Errorf("unexpected createdAt %v, want: %v", got.createdAt, tt.want.createdAt)
			}
			if !reflect.DeepEqual(tt.want.Sequence(), got.Sequence()) {
				t.Errorf("unexpected sequence %v, want: %v", got.sequence, tt.want.sequence)
			}
			if !reflect.DeepEqual(tt.want.Creators(), got.Creators()) {
				t.Errorf("unexpected creators %v, want: %v", got.creators, tt.want.creators)
			}
		})
	}
}

func TestAggregateFilter(t *testing.T) {
	type args struct {
		opts []AggregateFilterOpt
	}
	tests := []struct {
		name string
		args args
		want *AggregateFilter
	}{
		{
			name: "AggregateID",
			args: args{
				opts: []AggregateFilterOpt{
					SetAggregateID("asdf"),
				},
			},
			want: &AggregateFilter{
				ids: []string{"asdf"},
			},
		},
		{
			name: "AggregateIDs",
			args: args{
				opts: []AggregateFilterOpt{
					AggregateIDs("a", "s"),
					AggregateIDs("d", "f"),
				},
			},
			want: &AggregateFilter{
				ids: []string{"d", "f"},
			},
		},
		{
			name: "AggregateIDs",
			args: args{
				opts: []AggregateFilterOpt{
					AppendAggregateIDs("a", "s"),
					AppendAggregateIDs("d", "f"),
				},
			},
			want: &AggregateFilter{
				ids: []string{"a", "s", "d", "f"},
			},
		},
		{
			name: "AppendEvent",
			args: args{
				opts: []AggregateFilterOpt{
					AppendEvent(AppendEventTypes("asdf")),
					AppendEvent(AppendEventTypes("asdf")),
				},
			},
			want: &AggregateFilter{
				events: make([]*EventFilter, 2),
			},
		},
		{
			name: "AppendEvents",
			args: args{
				opts: []AggregateFilterOpt{
					AppendEvents(NewEventFilter()),
					AppendEvents(NewEventFilter()),
				},
			},
			want: &AggregateFilter{
				events: make([]*EventFilter, 2),
			},
		},
		{
			name: "Events",
			args: args{
				opts: []AggregateFilterOpt{
					SetEvents(NewEventFilter()),
					SetEvents(NewEventFilter()),
				},
			},
			want: &AggregateFilter{
				events: make([]*EventFilter, 1),
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := NewAggregateFilter("", tt.args.opts...)

			if tt.want.typ != got.typ {
				t.Errorf("unexpected typ %v, want: %v", got.typ, tt.want.typ)
			}
			if !reflect.DeepEqual(tt.want.Type(), got.Type()) {
				t.Errorf("unexpected typ %v, want: %v", got.typ, tt.want.typ)
			}
			if !reflect.DeepEqual(tt.want.IDs(), got.IDs()) {
				t.Errorf("unexpected ids %v, want: %v", got.ids, tt.want.ids)
			}
			if len(tt.want.Events()) != len(got.Events()) {
				t.Errorf("unexpected length of events %v, want: %v", len(got.events), len(tt.want.events))
			}
		})
	}
}

func TestFilterOpt(t *testing.T) {
	type args struct {
		opts []FilterOpt
	}
	tests := []struct {
		name string
		args args
		want *Filter
	}{
		{
			name: "limit 1",
			args: args{
				opts: []FilterOpt{
					FilterPagination(Limit(10)),
					FilterPagination(Limit(1)),
				},
			},
			want: &Filter{
				pagination: &Pagination{
					pagination: &database.Pagination{
						Limit: 1,
					},
				},
			},
		},
		{
			name: "AppendAggregateFilter",
			args: args{
				opts: []FilterOpt{
					AppendAggregateFilter("typ"),
					AppendAggregateFilter("typ2"),
				},
			},
			want: &Filter{
				aggregateFilters: make([]*AggregateFilter, 2),
			},
		},
		{
			name: "AppendAggregateFilters",
			args: args{
				opts: []FilterOpt{
					AppendAggregateFilters(NewAggregateFilter("typ")),
					AppendAggregateFilters(NewAggregateFilter("typ2")),
				},
			},
			want: &Filter{
				aggregateFilters: make([]*AggregateFilter, 2),
			},
		},
		{
			name: "AggregateFilters",
			args: args{
				opts: []FilterOpt{
					SetAggregateFilters(NewAggregateFilter("typ")),
					SetAggregateFilters(NewAggregateFilter("typ2")),
				},
			},
			want: &Filter{
				aggregateFilters: make([]*AggregateFilter, 1),
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := NewFilter(tt.args.opts...)
			parent := NewQuery("instance", nil)
			got.parent = parent
			tt.want.parent = parent

			if !reflect.DeepEqual(tt.want.Pagination(), got.Pagination()) {
				t.Errorf("unexpected pagination %v, want: %v", got.pagination, tt.want.pagination)
			}
			if len(tt.want.AggregateFilters()) != len(got.AggregateFilters()) {
				t.Errorf("unexpected length of aggregateFilters %v, want: %v", len(got.aggregateFilters), len(tt.want.aggregateFilters))
			}
		})
	}
}

func TestQueryOpt(t *testing.T) {
	type args struct {
		opts []QueryOpt
	}
	var tx sql.Tx
	tests := []struct {
		name string
		args args
		want *Query
	}{
		{
			name: "limit 1",
			args: args{
				opts: []QueryOpt{
					QueryPagination(Limit(10)),
					QueryPagination(Limit(1)),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewTextEqual("instance"),
					value:     toPtr([]string{"instance"}),
				},
				pagination: &Pagination{
					pagination: &database.Pagination{
						Limit: 1,
					},
				},
			},
		},
		{
			name: "with tx",
			args: args{
				opts: []QueryOpt{
					SetQueryTx(&tx),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewTextEqual("instance"),
					value:     toPtr([]string{"instance"}),
				},
				tx: &tx,
			},
		},
		{
			name: "instance",
			args: args{
				opts: []QueryOpt{
					SetInstance("instance2"),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewTextEqual("instance2"),
					value:     toPtr([]string{"instance2"}),
				},
			},
		},
		{
			name: "InstanceEqual no param",
			args: args{
				opts: []QueryOpt{
					InstancesEqual(),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewTextEqual("instance"),
					value:     toPtr([]string{"instance"}),
				},
			},
		},
		{
			name: "InstanceEqual 1 param",
			args: args{
				opts: []QueryOpt{
					InstancesEqual("instance2"),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewTextEqual("instance2"),
					value:     toPtr([]string{"instance2"}),
				},
			},
		},
		{
			name: "InstanceEqual 2 params",
			args: args{
				opts: []QueryOpt{
					InstancesEqual("instance2"),
					InstancesEqual("inst", "ancestor"),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewListEquals("inst", "ancestor"),
					value:     toPtr([]string{"inst", "ancestor"}),
				},
			},
		},
		{
			name: "InstancesContains no param",
			args: args{
				opts: []QueryOpt{
					InstancesContains(),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewTextEqual("instance"),
					value:     toPtr([]string{"instance"}),
				},
			},
		},
		{
			name: "InstancesContains 1 param",
			args: args{
				opts: []QueryOpt{
					InstancesContains("instance2"),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewTextEqual("instance2"),
					value:     toPtr([]string{"instance2"}),
				},
			},
		},
		{
			name: "InstancesContains 2 params",
			args: args{
				opts: []QueryOpt{
					InstancesContains("instance2"),
					InstancesContains("inst", "ancestor"),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewListContains("inst", "ancestor"),
					value:     toPtr([]string{"inst", "ancestor"}),
				},
			},
		},
		{
			name: "InstancesNotContains no param",
			args: args{
				opts: []QueryOpt{
					InstancesNotContains(),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewTextEqual("instance"),
					value:     toPtr([]string{"instance"}),
				},
			},
		},
		{
			name: "InstancesNotContains 1 param",
			args: args{
				opts: []QueryOpt{
					InstancesNotContains("instance2"),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewTextUnequal("instance2"),
					value:     toPtr([]string{"instance2"}),
				},
			},
		},
		{
			name: "InstancesNotContains 2 params",
			args: args{
				opts: []QueryOpt{
					InstancesNotContains("instance2"),
					InstancesNotContains("inst", "ancestor"),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewListNotContains("inst", "ancestor"),
					value:     toPtr([]string{"inst", "ancestor"}),
				},
			},
		},
		{
			name: "AppendFilters",
			args: args{
				opts: []QueryOpt{
					AppendFilters(NewFilter()),
					AppendFilters(NewFilter()),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewTextEqual("instance"),
					value:     toPtr([]string{"instance"}),
				},
				filters: make([]*Filter, 2),
			},
		},
		{
			name: "AppendFilter",
			args: args{
				opts: []QueryOpt{
					AppendFilter(),
					AppendFilter(),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewTextEqual("instance"),
					value:     toPtr([]string{"instance"}),
				},
				filters: make([]*Filter, 2),
			},
		},
		{
			name: "Filter",
			args: args{
				opts: []QueryOpt{
					SetFilters(NewFilter()),
					SetFilters(NewFilter()),
				},
			},
			want: &Query{
				instances: &filter[[]string]{
					condition: database.NewTextEqual("instance"),
					value:     toPtr([]string{"instance"}),
				},
				filters: make([]*Filter, 1),
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := NewQuery("instance", nil, tt.args.opts...)

			if !reflect.DeepEqual(tt.want.Instance(), got.Instance()) {
				t.Errorf("unexpected instances %v, want: %v", got.instances, tt.want.instances)
			}
			if len(tt.want.Filters()) != len(got.Filters()) {
				t.Errorf("unexpected length of filters %v, want: %v", len(got.filters), len(tt.want.filters))
			}
			if !reflect.DeepEqual(tt.want.Tx(), got.Tx()) {
				t.Errorf("unexpected tx %v, want: %v", got.tx, tt.want.tx)
			}
			if !reflect.DeepEqual(tt.want.Pagination(), got.Pagination()) {
				t.Errorf("unexpected pagination %v, want: %v", got.pagination, tt.want.pagination)
			}
		})
	}
}

func toPtr[T any](value T) *T {
	return &value
}