package search

import (
	"testing"

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

var trueBool = true

func TestStandardSearchString(t *testing.T) {
	tests := []struct {
		name  string
		query Query
		out   string
	}{
		{
			name: "empty query",
			out:  "",
		},
		{
			name: "converts query to string",
			query: Query{
				Keywords: []string{"some", "keywords"},
				Qualifiers: Qualifiers{
					Archived:         &trueBool,
					AuthorEmail:      "foo@example.com",
					CommitterDate:    "2021-02-28",
					Created:          "created",
					Extension:        "go",
					Filename:         ".vimrc",
					Followers:        "1",
					Fork:             "true",
					Forks:            "2",
					GoodFirstIssues:  "3",
					HelpWantedIssues: "4",
					In:               []string{"description", "readme"},
					Language:         "language",
					License:          []string{"license"},
					Pushed:           "updated",
					Size:             "5",
					Stars:            "6",
					Topic:            []string{"topic"},
					Topics:           "7",
					User:             []string{"user1", "user2"},
					Is:               []string{"public"},
				},
			},
			out: "some keywords archived:true author-email:foo@example.com committer-date:2021-02-28 " +
				"created:created extension:go filename:.vimrc followers:1 fork:true forks:2 good-first-issues:3 help-wanted-issues:4 " +
				"in:description in:readme is:public language:language license:license pushed:updated size:5 " +
				"stars:6 topic:topic topics:7 user:user1 user:user2",
		},
		{
			name: "quotes keywords",
			query: Query{
				Keywords: []string{"quote keywords"},
			},
			out: `"quote keywords"`,
		},
		{
			name: "quotes keywords that are qualifiers",
			query: Query{
				Keywords: []string{"quote:keywords", "quote:multiword keywords"},
			},
			out: `quote:keywords quote:"multiword keywords"`,
		},
		{
			name: "quotes qualifiers",
			query: Query{
				Qualifiers: Qualifiers{
					Topic: []string{"quote qualifier"},
				},
			},
			out: `topic:"quote qualifier"`,
		},
		{
			name: "respects immutable keywords",
			query: Query{
				ImmutableKeywords: "immutable keyword that should be left as is",
			},
			out: `immutable keyword that should be left as is`,
		},
		{
			name: "respects immutable keywords, with qualifiers",
			query: Query{
				ImmutableKeywords: "immutable keyword that should be left as is",
				Qualifiers: Qualifiers{
					Topic: []string{"quote qualifier"},
				},
			},
			out: `immutable keyword that should be left as is topic:"quote qualifier"`,
		},
		{
			name: "prioritises immutable keywords over keywords slice",
			query: Query{
				Keywords:          []string{"foo", "bar"},
				ImmutableKeywords: "immutable keyword",
			},
			out: `immutable keyword`,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.out, tt.query.StandardSearchString())
		})
	}
}

func TestAdvancedIssueSearchString(t *testing.T) {
	tests := []struct {
		name  string
		query Query
		out   string
	}{
		{
			name: "empty query",
			out:  "",
		},
		{
			name: "quotes keywords",
			query: Query{
				Keywords: []string{"quote keywords"},
			},
			out: `"quote keywords"`,
		},
		{
			name: "quotes keywords that are qualifiers",
			query: Query{
				Keywords: []string{"quote:keywords", "quote:multiword keywords"},
			},
			out: `quote:keywords quote:"multiword keywords"`,
		},
		{
			name: "quotes qualifiers",
			query: Query{
				Qualifiers: Qualifiers{
					Label: []string{"quote qualifier"},
				},
			},
			out: `label:"quote qualifier"`,
		},
		{
			name: "respects immutable keywords",
			query: Query{
				ImmutableKeywords: "immutable keyword that should be left as is",
			},
			out: `immutable keyword that should be left as is`,
		},
		{
			name: "respects immutable keywords, with qualifiers",
			query: Query{
				ImmutableKeywords: "immutable keyword that should be left as is",
				Qualifiers: Qualifiers{
					Topic: []string{"quote qualifier"},
				},
			},
			out: `( immutable keyword that should be left as is ) topic:"quote qualifier"`,
		},
		{
			name: "prioritises immutable keywords over keywords slice",
			query: Query{
				Keywords:          []string{"foo", "bar"},
				ImmutableKeywords: "immutable keyword",
			},
			out: `immutable keyword`,
		},
		{
			name: "unused qualifiers should not appear in query",
			query: Query{
				Keywords: []string{"keyword"},
				Qualifiers: Qualifiers{
					Label: []string{"foo", "bar"},
				},
			},
			out: `( keyword ) label:bar label:foo`,
		},
		{
			name: "special qualifiers when used once",
			query: Query{
				Keywords: []string{"keyword"},
				Qualifiers: Qualifiers{
					Repo: []string{"foo/bar"},
					Is:   []string{"private"},
					User: []string{"johndoe"},
					In:   []string{"title"},
				},
			},
			out: `( keyword ) in:title is:private repo:foo/bar user:johndoe`,
		},
		{
			name: "special qualifiers are OR-ed when used multiple times",
			query: Query{
				Keywords: []string{"keyword"},
				Qualifiers: Qualifiers{
					Repo: []string{"foo/bar", "foo/baz"},
					Is:   []string{"private", "public", "issue", "pr", "open", "closed", "locked", "unlocked", "merged", "unmerged", "blocked", "blocking", "foo"}, // "foo" is to ensure only "public" and "private" are grouped
					User: []string{"johndoe", "janedoe"},
					In:   []string{"title", "body", "comments", "foo"}, // "foo" is to ensure only "title", "body", and "comments" are grouped
				},
			},
			out: `( keyword ) (in:body OR in:comments OR in:title) in:foo (is:blocked OR is:blocking) (is:closed OR is:open) (is:issue OR is:pr) (is:locked OR is:unlocked) (is:merged OR is:unmerged) (is:private OR is:public) is:foo (repo:foo/bar OR repo:foo/baz) (user:janedoe OR user:johndoe)`,
		},
		{
			// Since this is a general purpose package, we can't assume with know all
			// use cases of special qualifiers. So, here we ensure unknown values are
			// not OR-ed by default.
			name: "special qualifiers without special values",
			query: Query{
				Keywords: []string{"keyword"},
				Qualifiers: Qualifiers{
					Is: []string{"foo", "bar"},
					In: []string{"foo", "bar"},
				},
			},
			out: `( keyword ) in:bar in:foo is:bar is:foo`,
		},
		{
			name: "non-special qualifiers used multiple times",
			query: Query{
				Keywords: []string{"keyword"},
				Qualifiers: Qualifiers{
					In:      []string{"foo", "bar"}, // "in:" is a special qualifier but its values here are not special
					Is:      []string{"foo", "bar"}, // "is:" is a special qualifier but its values here are not special
					Label:   []string{"foo", "bar"},
					License: []string{"foo", "bar"},
					No:      []string{"foo", "bar"},
					Topic:   []string{"foo", "bar"},
				},
			},
			out: `( keyword ) in:bar in:foo is:bar is:foo label:bar label:foo license:bar license:foo no:bar no:foo topic:bar topic:foo`,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.out, tt.query.AdvancedIssueSearchString())
		})
	}
}

func TestQualifiersMap(t *testing.T) {
	tests := []struct {
		name       string
		qualifiers Qualifiers
		out        map[string][]string
	}{
		{
			name: "changes qualifiers to map",
			qualifiers: Qualifiers{
				Archived:         &trueBool,
				AuthorEmail:      "foo@example.com",
				CommitterDate:    "2021-02-28",
				Created:          "created",
				Extension:        "go",
				Filename:         ".vimrc",
				Followers:        "1",
				Fork:             "true",
				Forks:            "2",
				GoodFirstIssues:  "3",
				HelpWantedIssues: "4",
				In:               []string{"readme"},
				Is:               []string{"public"},
				Language:         "language",
				License:          []string{"license"},
				Pushed:           "updated",
				Size:             "5",
				Stars:            "6",
				Topic:            []string{"topic"},
				Topics:           "7",
				User:             []string{"user1", "user2"},
			},
			out: map[string][]string{
				"archived":           {"true"},
				"author-email":       {"foo@example.com"},
				"committer-date":     {"2021-02-28"},
				"created":            {"created"},
				"extension":          {"go"},
				"filename":           {".vimrc"},
				"followers":          {"1"},
				"fork":               {"true"},
				"forks":              {"2"},
				"good-first-issues":  {"3"},
				"help-wanted-issues": {"4"},
				"in":                 {"readme"},
				"is":                 {"public"},
				"language":           {"language"},
				"license":            {"license"},
				"pushed":             {"updated"},
				"size":               {"5"},
				"stars":              {"6"},
				"topic":              {"topic"},
				"topics":             {"7"},
				"user":               {"user1", "user2"},
			},
		},
		{
			name: "excludes unset qualifiers from map",
			qualifiers: Qualifiers{
				Pushed: "updated",
				Size:   "5",
				Stars:  "6",
				User:   []string{"user"},
			},
			out: map[string][]string{
				"pushed": {"updated"},
				"size":   {"5"},
				"stars":  {"6"},
				"user":   {"user"},
			},
		},
		{
			name: "concatenates fields that share a qualifier key",
			qualifiers: Qualifiers{
				Type:      "issue",
				IssueType: "Bug",
			},
			out: map[string][]string{
				"type": {"issue", "Bug"},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.out, tt.qualifiers.Map())
		})
	}
}

func TestCamelToKebab(t *testing.T) {
	tests := []struct {
		name string
		in   string
		out  string
	}{
		{
			name: "single lowercase word",
			in:   "test",
			out:  "test",
		},
		{
			name: "multiple mixed words",
			in:   "testTestTest",
			out:  "test-test-test",
		},
		{
			name: "multiple uppercase words",
			in:   "TestTest",
			out:  "test-test",
		},
		{
			name: "multiple lowercase words",
			in:   "testtest",
			out:  "testtest",
		},
		{
			name: "multiple mixed words with number",
			in:   "test2Test",
			out:  "test2-test",
		},
		{
			name: "multiple lowercase words with number",
			in:   "test2test",
			out:  "test2test",
		},
		{
			name: "multiple lowercase words with dash",
			in:   "test-test",
			out:  "test-test",
		},
		{
			name: "multiple uppercase words with dash",
			in:   "Test-Test",
			out:  "test--test",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.out, camelToKebab(tt.in))
		})
	}
}
