package tidbmanager

import (
	"testing"

	. "github.com/onsi/gomega"
	"github.com/tidbcloud/aws-shared-provider/pkg/types"
)

func Test_newTidbInfo(t *testing.T) {
	type args struct {
		endpoints string
		replicas  int
	}
	tests := []struct {
		name string
		args args
		want tidbInfo
	}{
		{
			name: "Test with empty endpoints without comment",
			args: args{
				endpoints: "",
				replicas:  0,
			},
			want: tidbInfo{
				endpoints: nil,
				replicas:  0,
				comment:   "",
			},
		},
		{
			name: "Test with empty endpoints with comment",
			args: args{
				endpoints: "//comment1",
				replicas:  0,
			},
			want: tidbInfo{
				endpoints: nil,
				replicas:  0,
				comment:   "comment1",
			},
		},
		{
			name: "Test with one endpoint without comment",
			args: args{
				endpoints: "name1,ip1",
				replicas:  1,
			},
			want: tidbInfo{
				endpoints: []string{"name1,ip1"},
				replicas:  1,
				comment:   "",
			},
		},
		{
			name: "Test with one endpoint with comment",
			args: args{
				endpoints: "name1,ip1//comment1",
				replicas:  1,
			},
			want: tidbInfo{
				endpoints: []string{"name1,ip1"},
				replicas:  1,
				comment:   "comment1",
			},
		},
		{
			name: "Test with multiple endpoints without comment",
			args: args{
				endpoints: "name1,ip1;name2,ip2",
				replicas:  2,
			},
			want: tidbInfo{
				endpoints: []string{"name1,ip1", "name2,ip2"},
				replicas:  2,
				comment:   "",
			},
		},
		{
			name: "Test with multiple endpoints with comment",
			args: args{
				endpoints: "name1,ip1;name2,ip2//name3,ip3//comment1",
				replicas:  2,
			},
			want: tidbInfo{
				endpoints: []string{"name1,ip1", "name2,ip2"},
				replicas:  2,
				comment:   "name3,ip3//comment1",
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			got := newTidbInfo(tt.args.endpoints, tt.args.replicas, 0)
			g.Expect(got).To(Equal(tt.want))
		})
	}
}

func Test_tidbInfo_setEndpoints(t *testing.T) {
	tests := []struct {
		name      string
		endpoints string
		original  tidbInfo
		want      tidbInfo
	}{
		{
			name:      "Test with empty endpoints without comment",
			endpoints: "",
			original:  tidbInfo{endpoints: []string{"name0,ip0"}, comment: "original comment"},
			want:      tidbInfo{endpoints: nil, comment: ""},
		},
		{
			name:      "Test with empty endpoints with comment",
			endpoints: "//comment1",
			original:  tidbInfo{endpoints: []string{"name0,ip0"}, comment: "original comment"},
			want:      tidbInfo{endpoints: nil, comment: "comment1"},
		},
		{
			name:      "Test with one endpoint without comment on empty original",
			endpoints: "name1,ip1",
			want:      tidbInfo{endpoints: []string{"name1,ip1"}, comment: ""},
		},
		{
			name:      "Test with one endpoint with comment on empty original",
			endpoints: "name1,ip1//comment1",
			want:      tidbInfo{endpoints: []string{"name1,ip1"}, comment: "comment1"},
		},
		{
			name:      "Test with one endpoint without comment",
			endpoints: "name1,ip1",
			original:  tidbInfo{endpoints: []string{"name0,ip0"}, comment: "original comment"},
			want:      tidbInfo{endpoints: []string{"name1,ip1"}, comment: ""},
		},
		{
			name:      "Test with one endpoint with comment",
			endpoints: "name1,ip1//comment1",
			original:  tidbInfo{endpoints: []string{"name0,ip0"}, comment: "original comment"},
			want:      tidbInfo{endpoints: []string{"name1,ip1"}, comment: "comment1"},
		},
		{
			name:      "Test with multiple endpoints without comment",
			endpoints: "name1,ip1;name2,ip2",
			original:  tidbInfo{endpoints: []string{"name0,ip0"}, comment: "original comment"},
			want:      tidbInfo{endpoints: []string{"name1,ip1", "name2,ip2"}, comment: ""},
		},
		{
			name:      "Test with multiple endpoints with comment",
			endpoints: "name1,ip1;name2,ip2//comment1",
			original:  tidbInfo{endpoints: []string{"name0,ip0"}, comment: "original comment"},
			want:      tidbInfo{endpoints: []string{"name1,ip1", "name2,ip2"}, comment: "comment1"},
		},
		{
			name:      "Test with same endpoints with different comment",
			endpoints: "name0,ip0//comment1",
			original:  tidbInfo{endpoints: []string{"name0,ip0"}, comment: "original comment"},
			want:      tidbInfo{endpoints: []string{"name0,ip0"}, comment: "comment1"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			tt.original.setEndpoints(tt.endpoints, 0)
			g.Expect(tt.original).To(Equal(tt.want))
		})
	}
}

func Test_tidbInfo_getRandPod(t *testing.T) {
	g := NewGomegaWithT(t)
	tidbInfoEmpty := newTidbInfo("", 1, 0)
	_, err := tidbInfoEmpty.getRandEndpoint()
	g.Expect(err).To(HaveOccurred())

	tidbInfoOne := newTidbInfo("name1,ip1", 0, 0)
	endpoint, err := tidbInfoOne.getRandEndpoint()
	g.Expect(err).NotTo(HaveOccurred())
	g.Expect(endpoint).To(Equal("name1,ip1"))

	tidbInfo3 := newTidbInfo("name1,ip1;name2,ip1;name3,ip3", 0, 0)
	endpoint, err = tidbInfo3.getRandEndpoint()
	g.Expect(err).NotTo(HaveOccurred())
	for try := 0; try < 8; try++ {
		endpointN, err := tidbInfo3.getRandEndpoint()
		g.Expect(err).NotTo(HaveOccurred())
		if endpointN != endpoint {
			return
		}
	}
	g.Fail("getRandEndpoint get same result, try 6")
}

func Test_tidbInfo_removeEndpoint(t *testing.T) {
	type fields struct {
		endpoints []string
	}
	type args struct {
		endpoint string
	}
	tests := []struct {
		name   string
		fields fields
		args   args
		want   bool
	}{
		{
			name:   "endpoints is nil",
			fields: fields{endpoints: nil},
			args:   args{},
			want:   false,
		},
		{
			name:   "remove empty endpoint",
			fields: fields{endpoints: []string{"name1,ip1"}},
			args:   args{},
			want:   false,
		},
		{
			name:   "remove exist endpoint",
			fields: fields{endpoints: []string{"name1,ip1", "name2,ip2", "name3,ip3"}},
			args:   args{endpoint: "name2,ip2"},
			want:   true,
		},
		{
			name:   "remove not exist endpoint",
			fields: fields{endpoints: []string{"name1,ip1"}},
			args:   args{endpoint: "name2,ip2"},
			want:   false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			tInfo := &tidbInfo{
				endpoints: tt.fields.endpoints,
			}
			g.Expect(tInfo.removeEndpoint(tt.args.endpoint)).To(Equal(tt.want))
		})
	}
}

func Test_tidbInfo_diffEndpoint(t *testing.T) {
	type fields struct {
		endpoints []string
	}
	type args struct {
		pre tidbInfo
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		wantInc []string
		wantDec []string
	}{
		{
			name:    "diff pre is empty",
			fields:  fields{endpoints: []string{"name1,ip1"}},
			args:    args{},
			wantInc: []string{"name1,ip1"},
			wantDec: nil,
		},
		{
			name:    "diff current is empty",
			fields:  fields{},
			args:    args{pre: tidbInfo{endpoints: []string{"name1,ip1"}}},
			wantInc: nil,
			wantDec: []string{"name1,ip1"},
		},
		{
			name:    "diff result is empty",
			fields:  fields{endpoints: []string{"name1,ip1", "name2,ip2"}},
			args:    args{pre: tidbInfo{endpoints: []string{"name2,ip2", "name1,ip1"}}},
			wantInc: nil,
			wantDec: nil,
		},
		{
			name:    "diff result have Inc and Dec",
			fields:  fields{endpoints: []string{"name1,ip1", "name2,ip2", "name3,ip3"}},
			args:    args{pre: tidbInfo{endpoints: []string{"name3,ip3", "name4,ip4"}}},
			wantInc: []string{"name1,ip1", "name2,ip2"},
			wantDec: []string{"name4,ip4"},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			tInfo := &tidbInfo{
				endpoints: tt.fields.endpoints,
			}
			gotInc, gotDec := tInfo.diffEndpoint(tt.args.pre)
			g.Expect(gotInc).To(Equal(tt.wantInc))
			g.Expect(gotDec).To(Equal(tt.wantDec))
		})
	}
}

func Test_assignCache_modifyTidbInfoEndpoints(t *testing.T) {
	type args struct {
		token     string
		endpoints string
	}
	tests := []struct {
		name            string
		tokenMap        map[string]tidbInfo
		args            args
		wantPreTidbInfo tidbInfo
		wantExist       bool
	}{
		{
			name: "normal, result success",
			tokenMap: map[string]tidbInfo{
				"token1": {
					endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "123-name2,ip2"},
					comment:   "old comment",
				},
			},
			args: args{token: "token1", endpoints: types.TidbServerNamePrefix + "456-name3,ip3;name4,ip4//new comment"},
			wantPreTidbInfo: tidbInfo{
				endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "123-name2,ip2"},
				comment:   "old comment",
			},
			wantExist: true,
		},
		{
			name: "empty endpoints, result success",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "123-name2,ip2"}},
			},
			args:            args{token: "token1", endpoints: ""},
			wantPreTidbInfo: tidbInfo{endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "123-name2,ip2"}},
			wantExist:       true,
		},
		{
			name: "token not match, result fail",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "111-name2,ip2"}},
			},
			args:      args{token: "token2", endpoints: types.TidbServerNamePrefix + "111-name1,ip1;name2,ip2"},
			wantExist: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			ac := &assignCache{}
			ac.init(tt.tokenMap)
			gotPreTidbInfo, gotExist := ac.modifyTidbInfoEndpoints(tt.args.token, tt.args.endpoints, 0)
			g.Expect(gotPreTidbInfo).To(Equal(tt.wantPreTidbInfo))
			g.Expect(gotExist).To(Equal(tt.wantExist))
			if gotExist {
				for _, endpoint := range newTidbInfo(tt.args.endpoints, 0, 0).endpoints {
					g.Expect(ac.findTokenByEndpoint(endpoint)).To(Equal(tt.args.token))
				}
				for _, endpoint := range gotPreTidbInfo.endpoints {
					g.Expect(ac.findTokenByEndpoint(endpoint)).To(Equal(""))
				}
			} else {
				for _, endpoint := range newTidbInfo(tt.args.endpoints, 0, 0).endpoints {
					g.Expect(ac.findTokenByEndpoint(endpoint)).NotTo(Equal(tt.args.token))
				}
			}
		})
	}
}

func Test_assignCache_setTidbInfoEndpoints(t *testing.T) {
	type args struct {
		token     string
		endpoints string
	}
	tests := []struct {
		name            string
		tokenMap        map[string]tidbInfo
		args            args
		wantPreTidbInfo tidbInfo
	}{
		{
			name: "set new token",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "111-name2,ip2"}},
			},
			args: args{token: "token2", endpoints: types.TidbServerNamePrefix + "111-name1,ip1;name2,ip2"},
		},
		{
			name: "overwrite old token",
			tokenMap: map[string]tidbInfo{
				"token1": {
					endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "123-name2,ip2"},
					comment:   "old comment",
				},
			},
			args: args{token: "token1", endpoints: types.TidbServerNamePrefix + "456-name3,ip3;name4,ip4//new comment"},
			wantPreTidbInfo: tidbInfo{
				endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "123-name2,ip2"},
				comment:   "old comment",
			},
		},
		{
			name: "set empty endpoints",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "123-name2,ip2"}},
			},
			args:            args{token: "token1", endpoints: ""},
			wantPreTidbInfo: tidbInfo{endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "123-name2,ip2"}},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			ac := &assignCache{}
			ac.init(tt.tokenMap)
			gotPreTidbInfo := ac.setTidbInfoEndpoints(tt.args.token, tt.args.endpoints, 0)
			g.Expect(gotPreTidbInfo).To(Equal(tt.wantPreTidbInfo))
			for _, endpoint := range newTidbInfo(tt.args.endpoints, 0, 0).endpoints {
				g.Expect(ac.findTokenByEndpoint(endpoint)).To(Equal(tt.args.token))
			}
			for _, endpoint := range gotPreTidbInfo.endpoints {
				g.Expect(ac.findTokenByEndpoint(endpoint)).To(Equal(""))
			}
		})
	}
}

func Test_assignCache_removeToken(t *testing.T) {
	type args struct {
		token string
	}
	tests := []struct {
		name            string
		tokenMap        map[string]tidbInfo
		args            args
		wantPreTidbInfo tidbInfo
		wantRemoved     bool
	}{
		{
			name: "remove exist token",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "111-name2,ip2"}},
			},
			args:            args{token: "token1"},
			wantPreTidbInfo: tidbInfo{endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "111-name2,ip2"}},
			wantRemoved:     true,
		},
		{
			name: "remove not exist token",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "111-name2,ip2"}},
			},
			args:        args{token: "token2"},
			wantRemoved: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			ac := &assignCache{}
			ac.init(tt.tokenMap)
			gotPreTidbInfo, gotRemoved := ac.removeToken(tt.args.token)
			g.Expect(gotPreTidbInfo).To(Equal(tt.wantPreTidbInfo))
			g.Expect(gotRemoved).To(Equal(tt.wantRemoved))
			if gotRemoved {
				for _, endpoint := range gotPreTidbInfo.endpoints {
					g.Expect(ac.findTokenByEndpoint(endpoint)).To(Equal(""))
				}
			}
		})
	}
}

func Test_assignCache_removeEndpoint(t *testing.T) {
	type args struct {
		endpoint string
	}
	tests := []struct {
		name     string
		tokenMap map[string]tidbInfo
		args     args
		want     bool
	}{
		{
			name: "remove exist endpoint",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "111-name2,ip2"}},
			},
			args: args{endpoint: "name1,ip1"},
			want: true,
		},
		{
			name: "remove exist TidbServerNamePrefix endpoint",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "111-name2,ip2"}},
			},
			args: args{endpoint: types.TidbServerNamePrefix + "111-name2,ip2"},
			want: true,
		},
		{
			name: "remove not exist endpoint",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "111-name2,ip2"}},
			},
			args: args{endpoint: "name2,ip2"},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			ac := &assignCache{}
			ac.init(tt.tokenMap)
			removed := ac.removeEndpoint(tt.args.endpoint)
			g.Expect(removed).To(Equal(tt.want))
			g.Expect(ac.findTokenByEndpoint(tt.args.endpoint)).To(Equal(""))
		})
	}
}

func Test_assignCache_removeEndpointByToken(t *testing.T) {
	type args struct {
		token    string
		endpoint string
	}
	tests := []struct {
		name         string
		tokenMap     map[string]tidbInfo
		args         args
		wantOldToken string
		want         bool
	}{
		{
			name: "remove exist endpoint",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "111-name2,ip2"}},
			},
			args: args{token: "token1", endpoint: "name1,ip1"},
			want: true,
		},
		{
			name: "remove exist not duplicate endpoint",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"duplicate_name,ip", types.TidbServerNamePrefix + "111-name2,ip2"}},
			},
			args: args{token: "token1", endpoint: types.TidbServerNamePrefix + "111-name2,ip2"},
			want: true,
		},
		{
			name: "remove exist duplicate endpoint",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"duplicate_name,ip", types.TidbServerNamePrefix + "111-name2,ip2"}},
			},
			args:         args{token: "token1", endpoint: "duplicate_name,ip"},
			wantOldToken: "token2",
			want:         true,
		},
		{
			name: "remove exist TidbServerNamePrefix endpoint",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "111-name2,ip2"}},
			},
			args: args{token: "token1", endpoint: types.TidbServerNamePrefix + "111-name2,ip2"},
			want: true,
		},
		{
			name: "remove not exist endpoint",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "111-name2,ip2"}},
			},
			args: args{token: "token1", endpoint: "name2,ip2"},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			ac := &assignCache{}
			ac.init(tt.tokenMap)
			ac.setTidbInfoEndpoints("token2", "duplicate_name,ip;name3.ip3", 0)
			g.Expect(ac.removeEndpointByToken(tt.args.token, tt.args.endpoint)).To(Equal(tt.want))
			g.Expect(ac.findTokenByEndpoint(tt.args.endpoint)).To(Equal(tt.wantOldToken))
		})
	}
}

func Test_assignCache_getActiveTokenInfo(t *testing.T) {
	tests := []struct {
		name        string
		tokenMap    map[string]tidbInfo
		deleteToken string
		want        map[string]tidbInfo
	}{
		{
			name: "normal",
			tokenMap: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "111-name2,ip2"}, replicas: 2},
				"token2": {endpoints: []string{"name2,ip2"}},
			},
			want: map[string]tidbInfo{
				"token1": {endpoints: []string{"name1,ip1", types.TidbServerNamePrefix + "111-name2,ip2"}, replicas: 2},
				"token2": {endpoints: []string{"name2,ip2"}},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			ac := &assignCache{}
			ac.init(tt.tokenMap)
			if tt.deleteToken != "" {
				delete(ac.tokenMap, tt.deleteToken)
			}
			g.Expect(ac.getActiveTokenInfo()).To(Equal(tt.want))
		})
	}
}
