package meilisearch

import (
	"net/url"
	"reflect"
	"strconv"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func (req *internalRequest) init() {
	req.withQueryParams = make(map[string]string)
}

func formatDateForComparison(date time.Time) string {
	const format = "2006-01-02T15:04:05Z"
	return date.Format(format)
}

func TestConvertKeyToParsedKey(t *testing.T) {
	key := Key{
		Name:        "test",
		Description: "test description",
		UID:         "123",
		Actions:     []string{"read", "write"},
		Indexes:     []string{"index1", "index2"},
		ExpiresAt:   time.Now(),
	}

	expectedExpiresAt := formatDateForComparison(key.ExpiresAt)
	parsedKey := convertKeyToParsedKey(key)

	if parsedKey.Name != key.Name ||
		parsedKey.Description != key.Description ||
		parsedKey.UID != key.UID ||
		!reflect.DeepEqual(parsedKey.Actions, key.Actions) ||
		!reflect.DeepEqual(parsedKey.Indexes, key.Indexes) ||
		parsedKey.ExpiresAt == nil || *parsedKey.ExpiresAt != expectedExpiresAt {
		t.Errorf("convertKeyToParsedKey(%v) = %v; want %v", key, parsedKey, key)
	}
}

func TestEncodeTasksQuery(t *testing.T) {
	param := &TasksQuery{
		Limit:            10,
		From:             5,
		Statuses:         []TaskStatus{"queued", "running"},
		Types:            []TaskType{"type1", "type2"},
		IndexUIDS:        []string{"uid1", "uid2"},
		UIDS:             []int64{1, 2, 3},
		CanceledBy:       []int64{4, 5},
		BeforeEnqueuedAt: time.Now().Add(-10 * time.Hour),
		AfterEnqueuedAt:  time.Now().Add(-20 * time.Hour),
		BeforeStartedAt:  time.Now().Add(-30 * time.Hour),
		AfterStartedAt:   time.Now().Add(-40 * time.Hour),
		BeforeFinishedAt: time.Now().Add(-50 * time.Hour),
		AfterFinishedAt:  time.Now().Add(-60 * time.Hour),
	}
	req := &internalRequest{}
	req.init()

	encodeTasksQuery(param, req)

	expectedParams := map[string]string{
		"limit":            strconv.FormatInt(param.Limit, 10),
		"from":             strconv.FormatInt(param.From, 10),
		"statuses":         "queued,running",
		"types":            "type1,type2",
		"indexUids":        "uid1,uid2",
		"uids":             "1,2,3",
		"canceledBy":       "4,5",
		"beforeEnqueuedAt": formatDateForComparison(param.BeforeEnqueuedAt),
		"afterEnqueuedAt":  formatDateForComparison(param.AfterEnqueuedAt),
		"beforeStartedAt":  formatDateForComparison(param.BeforeStartedAt),
		"afterStartedAt":   formatDateForComparison(param.AfterStartedAt),
		"beforeFinishedAt": formatDateForComparison(param.BeforeFinishedAt),
		"afterFinishedAt":  formatDateForComparison(param.AfterFinishedAt),
	}

	for k, v := range expectedParams {
		if req.withQueryParams[k] != v {
			t.Errorf("encodeTasksQuery() param %v = %v; want %v", k, req.withQueryParams[k], v)
		}
	}
}

func TestTransformStringVariadicToMap(t *testing.T) {
	toStringPointer := func(input string) *string { return &input }
	tests := []struct {
		input  *string
		expect map[string]string
	}{
		{toStringPointer("primaryKey1"), map[string]string{"primaryKey": "primaryKey1"}},
		{nil, nil},
	}

	for _, test := range tests {
		result := transformStringToMap(test.input)
		if !reflect.DeepEqual(result, test.expect) {
			t.Errorf("transformStringVariadicToMap(%v) = %v; want %v", test.input, result, test.expect)
		}
	}
}

func TestGenerateQueryForOptions(t *testing.T) {
	options := map[string]string{
		"key1": "value1",
		"key2": "value2",
	}

	expected := url.Values{}
	expected.Add("key1", "value1")
	expected.Add("key2", "value2")

	result := generateQueryForOptions(options)
	if result != expected.Encode() {
		t.Errorf("generateQueryForOptions(%v) = %v; want %v", options, result, expected.Encode())
	}
}

func TestJoinInt64(t *testing.T) {
	tests := []struct {
		name  string
		input []int64
		want  string
	}{
		{"empty slice", []int64{}, ""},
		{"single value", []int64{42}, "42"},
		{"multiple values", []int64{1, 2, 3}, "1,2,3"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := joinInt64(tt.input)
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestJoinString(t *testing.T) {
	tests := []struct {
		name  string
		input []string
		want  string
	}{
		{"empty slice", []string{}, ""},
		{"single value", []string{"foo"}, "foo"},
		{"multiple values", []string{"a", "b", "c"}, "a,b,c"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := joinString(tt.input)
			assert.Equal(t, tt.want, got)
		})
	}
}
func TestTransformDocumentOptionsToMap(t *testing.T) {
	primaryKey := "id"
	tests := []struct {
		name     string
		input    *DocumentOptions
		expected map[string]string
	}{
		{
			name:     "Nil input",
			input:    nil,
			expected: nil,
		},
		{
			name:     "Empty input",
			input:    &DocumentOptions{},
			expected: map[string]string{},
		},
		{
			name: "With PrimaryKey",
			input: &DocumentOptions{
				PrimaryKey: &primaryKey,
			},
			expected: map[string]string{
				"primaryKey": "id",
			},
		},
		{
			name: "With TaskCustomMetadata",
			input: &DocumentOptions{
				TaskCustomMetadata: "meta-123",
			},
			expected: map[string]string{
				"customMetadata": "meta-123",
			},
		},
		{
			name: "With SkipCreation true",
			input: &DocumentOptions{
				SkipCreation: true,
			},
			expected: map[string]string{
				"skipCreation": "true",
			},
		},
		{
			name: "With SkipCreation false",
			input: &DocumentOptions{
				SkipCreation: false,
			},
			expected: map[string]string{},
		},
		{
			name: "With All",
			input: &DocumentOptions{
				SkipCreation:       true,
				PrimaryKey:         &primaryKey,
				TaskCustomMetadata: "meta-123",
			},
			expected: map[string]string{
				"primaryKey":     "id",
				"skipCreation":   "true",
				"customMetadata": "meta-123",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := transformDocumentOptionsToMap(tt.input)
			assert.Equal(t, tt.expected, got)
		})
	}
}

func TestTransformCsvDocumentsQueryToMap(t *testing.T) {
	tests := []struct {
		name     string
		input    *CsvDocumentsQuery
		expected map[string]string
	}{
		{
			name:     "Nil input",
			input:    nil,
			expected: nil,
		},
		{
			name:     "Empty input",
			input:    &CsvDocumentsQuery{},
			expected: map[string]string{},
		},
		{
			name: "With PrimaryKey",
			input: &CsvDocumentsQuery{
				PrimaryKey: "id",
			},
			expected: map[string]string{
				"primaryKey": "id",
			},
		},
		{
			name: "With CsvDelimiter",
			input: &CsvDocumentsQuery{
				CsvDelimiter: ",",
			},
			expected: map[string]string{
				"csvDelimiter": ",",
			},
		},
		{
			name: "With TaskCustomMetadata",
			input: &CsvDocumentsQuery{
				TaskCustomMetadata: "meta-csv",
			},
			expected: map[string]string{
				"customMetadata": "meta-csv",
			},
		},
		{
			name: "With SkipCreation true",
			input: &CsvDocumentsQuery{
				SkipCreation: true,
			},
			expected: map[string]string{
				"skipCreation": "true",
			},
		},
		{
			name: "With SkipCreation false",
			input: &CsvDocumentsQuery{
				SkipCreation: false,
			},
			expected: map[string]string{},
		},
		{
			name: "With All",
			input: &CsvDocumentsQuery{
				PrimaryKey:         "id",
				CsvDelimiter:       ";",
				SkipCreation:       true,
				TaskCustomMetadata: "meta-csv-all",
			},
			expected: map[string]string{
				"primaryKey":     "id",
				"csvDelimiter":   ";",
				"skipCreation":   "true",
				"customMetadata": "meta-csv-all",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := transformCsvDocumentsQueryToMap(tt.input)
			assert.Equal(t, tt.expected, got)
		})
	}
}
