package version

import (
	"fmt"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
)

func Test_version_NewVersion(t *testing.T) {
	// custom options for comparer
	opts := cmp.Options{
		cmp.Comparer(func(left, right *Version) bool {
			if left == nil && right == nil {
				return true
			}
			if left.major == right.major &&
				left.minor == right.minor &&
				left.patch == right.patch {
				return true
			}

			return false
		}),
	}

	type args struct {
		versionString string
	}

	tests := []struct {
		name    string
		args    args
		want    *Version
		wantErr bool
	}{
		{
			name: "default",
			args: args{},
			want: &Version{
				major:            0,
				minor:            0,
				patch:            0,
				BuildInformation: BuildInformation{},
			},
			wantErr: false,
		},
		{
			name: "faulty version string",
			args: args{
				versionString: "faulty.version.string",
			},
			want:    nil,
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// This is not run in parallel since we change the versionString
			// and this would therefore result in a data race.
			//
			// versionString is and should only be touched through ldflags with
			// `go build`.
			if tt.name != "default" {
				versionString = tt.args.versionString
			}

			got, err := NewVersion()
			if (err != nil) != tt.wantErr {
				t.Errorf("NewVersion() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if !cmp.Equal(got, tt.want, opts...) {
				t.Errorf("NewVersion() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_version_Compare(t *testing.T) {
	timeNormal, err := time.Parse(time.RFC3339, "2022-08-19T11:11:02+00:00")
	if err != nil {
		t.Errorf("Compare() error = %v", err)
	}

	timeBefore, err := time.Parse(time.RFC3339, "2022-08-18T11:11:02+00:00")
	if err != nil {
		t.Errorf("Compare() error = %v", err)
	}

	timeAfter, err := time.Parse(time.RFC3339, "2022-08-22T11:11:02+00:00")
	if err != nil {
		t.Errorf("Compare() error = %v", err)
	}

	v := &Version{
		major: 1,
		minor: 3,
		patch: 101,
		BuildInformation: BuildInformation{
			goos:     "linux",
			goarch:   "amd64",
			revision: "34979478a8c56269dd59419d04d9080c1103174d",
			time:     timeNormal,
			modified: false,
		},
	}

	type args struct {
		inputVersion *Version
	}

	tests := []struct {
		name string
		args args
		want int
	}{
		{
			name: "input version is equal",
			args: args{
				inputVersion: &Version{
					major: 1,
					minor: 3,
					patch: 101,
					BuildInformation: BuildInformation{
						goos:     "linux",
						goarch:   "amd64",
						revision: "34979478a8c56269dd59419d04d9080c1103174d",
						time:     timeNormal,
						modified: false,
					},
				},
			},
			want: 0,
		},
		{
			name: "input version is smaller based on major",
			args: args{
				inputVersion: &Version{
					major: 0,
					minor: 0,
					patch: 101,
					BuildInformation: BuildInformation{
						goos:     "linux",
						goarch:   "amd64",
						revision: "34979478a8c56269dd59419d04d9080c1103174d",
						time:     timeNormal,
						modified: false,
					},
				},
			},
			want: 1,
		},
		{
			name: "input version is smaller based on minor",
			args: args{
				inputVersion: &Version{
					major: 1,
					minor: 0,
					patch: 101,
					BuildInformation: BuildInformation{
						goos:     "linux",
						goarch:   "amd64",
						revision: "34979478a8c56269dd59419d04d9080c1103174d",
						time:     timeNormal,
						modified: false,
					},
				},
			},
			want: 1,
		},
		{
			name: "input version is smaller based on patch",
			args: args{
				inputVersion: &Version{
					major: 1,
					minor: 3,
					patch: 78,
					BuildInformation: BuildInformation{
						goos:     "linux",
						goarch:   "amd64",
						revision: "34979478a8c56269dd59419d04d9080c1103174d",
						time:     timeNormal,
						modified: false,
					},
				},
			},
			want: 1,
		},
		{
			name: "input version is smaller based on commit",
			args: args{
				inputVersion: &Version{
					major: 1,
					minor: 3,
					patch: 101,
					BuildInformation: BuildInformation{
						goos:     "linux",
						goarch:   "amd64",
						revision: "14379478a8c56269dd59419d04c9080c1103174d",
						time:     timeBefore,
						modified: false,
					},
				},
			},
			want: 1,
		},
		{
			name: "input version is larger based on major",
			args: args{
				inputVersion: &Version{
					major: 2,
					minor: 3,
					patch: 78,
					BuildInformation: BuildInformation{
						goos:     "linux",
						goarch:   "amd64",
						revision: "34979478a8c56269dd59419d04d9080c1103174d",
						time:     timeNormal,
						modified: false,
					},
				},
			},
			want: -1,
		},
		{
			name: "input version is larger based on minor",
			args: args{
				inputVersion: &Version{
					major: 1,
					minor: 4,
					patch: 78,
					BuildInformation: BuildInformation{
						goos:     "linux",
						goarch:   "amd64",
						revision: "34979478a8c56269dd59419d04d9080c1103174d",
						time:     timeNormal,
						modified: false,
					},
				},
			},
			want: -1,
		},
		{
			name: "input version is larger based on patch",
			args: args{
				inputVersion: &Version{
					major: 1,
					minor: 3,
					patch: 103,
					BuildInformation: BuildInformation{
						goos:     "linux",
						goarch:   "amd64",
						revision: "34979478a8c56269dd59419d04d9080c1103174d",
						time:     timeNormal,
						modified: false,
					},
				},
			},
			want: -1,
		},
		{
			name: "input version is larger based on commit",
			args: args{
				inputVersion: &Version{
					major: 1,
					minor: 3,
					patch: 101,
					BuildInformation: BuildInformation{
						goos:     "linux",
						goarch:   "amd64",
						revision: "14379478a8c56269dd59419d04c9080c1103174d",
						time:     timeAfter,
						modified: false,
					},
				},
			},
			want: -1,
		},
		{
			name: "one of the versions is modified",
			args: args{
				inputVersion: &Version{
					BuildInformation: BuildInformation{
						modified: true,
					},
				},
			},
			want: -2,
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			got := v.Compare(tt.args.inputVersion)

			if !cmp.Equal(got, tt.want) {
				t.Errorf("Compare() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_version_String(t *testing.T) {
	timeNow := time.Now()
	v := &Version{
		major: 1,
		minor: 3,
		patch: 101,
		BuildInformation: BuildInformation{
			goos:     "linux",
			goarch:   "amd64",
			revision: "34979478a8c56269dd59419d04d9080c1103174d",
			time:     timeNow,
			modified: false,
		},
	}

	tests := []struct {
		name string
		want string
	}{
		{
			name: "default",
			want: fmt.Sprintf(`Version: 1.3.101
Build Information:
    - GOOS: linux
    - GOARCH: amd64
    - Revision: 34979478a8c56269dd59419d04d9080c1103174d
    - Time: %s
    - Modified (dirty): false`, timeNow.String()),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := v.String()

			if !cmp.Equal(got, tt.want) {
				t.Errorf("String() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_version_compareVersionSegment(t *testing.T) {
	type args struct {
		left, right int
	}

	tests := []struct {
		name string
		args args
		want int
	}{
		{
			name: "left is smaller && right is larger",
			args: args{
				left:  5,
				right: 8,
			},
			want: -1,
		},
		{
			name: "left is larger && right is smaller",
			args: args{
				left:  9,
				right: 3,
			},
			want: 1,
		},
		{
			name: "left and right are equal",
			args: args{
				left:  4,
				right: 4,
			},
			want: 0,
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			got := compareVersionSegment(tt.args.left, tt.args.right)

			if !cmp.Equal(got, tt.want) {
				t.Errorf("compareVersionSegment() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_version_parseVersionString(t *testing.T) {
	type args struct {
		input string
	}

	tests := []struct {
		name    string
		args    args
		want    []int
		wantErr bool
	}{
		{
			name: "default",
			args: args{
				input: "1.0.4",
			},
			want:    []int{1, 0, 4},
			wantErr: false,
		},
		{
			name: "too many values in version string",
			args: args{
				input: "1.0.4.259",
			},
			want:    nil,
			wantErr: true,
		},
		{
			name: "faulty version string - no numbers",
			args: args{
				input: "this is wrong",
			},
			want:    nil,
			wantErr: true,
		},
		{
			name: "faulty version string - wrong delimiter",
			args: args{
				input: "1-2-187",
			},
			want:    nil,
			wantErr: true,
		},
		{
			name: "faulty version string - mixed",
			args: args{
				input: "1.abc.283",
			},
			want:    nil,
			wantErr: true,
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			got, err := parseVersionString(tt.args.input)
			if (err != nil) != tt.wantErr {
				t.Errorf("parseVersionString() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			if !cmp.Equal(got, tt.want) {
				t.Errorf("parseVersionString() got = %v, want %v", got, tt.want)
			}
		})
	}
}
