package common

import (
	"strings"
	"testing"
)

func Test_IsVersionLower(t *testing.T) {
	tests := []struct {
		name           string
		curVer         string
		desiredVer     string
		wantLower      bool
		wantErr        bool
		wantErrContain string
	}{
		{name: "lower single-digit minor", curVer: "1.9", desiredVer: "1.10", wantLower: true},
		{name: "lower two-digit minor", curVer: "1.10", desiredVer: "1.27", wantLower: true},
		{name: "lower patch-style semver still compares as x.y", curVer: "1.26.3", desiredVer: "1.27", wantLower: true},
		{name: "equal", curVer: "1.27", desiredVer: "1.27", wantLower: false},
		{name: "higher minor", curVer: "1.28", desiredVer: "1.27", wantLower: false},
		{name: "lower major", curVer: "1.29", desiredVer: "1.30", wantLower: true},
		{name: "higher major", curVer: "2.0", desiredVer: "1.27", wantLower: false},
		{name: "two-digit minor higher than single-digit minor", curVer: "1.10", desiredVer: "1.9", wantLower: false},
		{name: "single-digit minor lower than two-digit minor", curVer: "1.9", desiredVer: "1.10", wantLower: true},
		{name: "v prefix on current", curVer: "v1.26", desiredVer: "1.27", wantLower: true},
		{name: "v prefix on desired", curVer: "1.26", desiredVer: "v1.27", wantLower: true},
		{name: "both empty", curVer: "", desiredVer: "", wantErr: true, wantErrContain: "cannot compare versions"},
		{name: "empty current", curVer: "", desiredVer: "1.27", wantErr: true, wantErrContain: "cannot compare versions"},
		{name: "empty desired", curVer: "1.27", desiredVer: "", wantErr: true, wantErrContain: "cannot compare versions"},
		{name: "invalid current", curVer: "not-a-version", desiredVer: "1.27", wantErr: true, wantErrContain: "compare versions"},
		{name: "invalid desired", curVer: "1.27", desiredVer: "bad", wantErr: true, wantErrContain: "compare versions"},
		{name: "whitespace current", curVer: " ", desiredVer: "1.27", wantErr: true, wantErrContain: "compare versions"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := IsVersionLower(tt.curVer, tt.desiredVer)
			if tt.wantErr {
				if err == nil {
					t.Fatal("IsVersionLower() expected error, got nil")
				}
				if tt.wantErrContain != "" && !strings.Contains(err.Error(), tt.wantErrContain) {
					t.Fatalf("IsVersionLower() error = %q, want substring %q", err.Error(), tt.wantErrContain)
				}
				if got {
					t.Errorf("IsVersionLower() = true on error, want false")
				}
				return
			}
			if err != nil {
				t.Fatalf("IsVersionLower() unexpected error: %v", err)
			}
			if got != tt.wantLower {
				t.Errorf("IsVersionLower(%q, %q) = %v, want %v", tt.curVer, tt.desiredVer, got, tt.wantLower)
			}
		})
	}
}

func Test_IsVersionLower_withFormattedMajorMinor(t *testing.T) {
	tests := []struct {
		name       string
		rawVersion string
		desiredVer string
		wantLower  bool
	}{
		{
			name:       "formatted full GKE version lower than desired",
			rawVersion: "1.26.5-gke.1200",
			desiredVer: "1.27",
			wantLower:  true,
		},
		{
			name:       "formatted full GKE version equal to desired",
			rawVersion: "1.27.3-gke.100",
			desiredVer: "1.27",
			wantLower:  false,
		},
		{
			name:       "formatted full GKE version higher than desired",
			rawVersion: "1.28.1-gke.100",
			desiredVer: "1.27",
			wantLower:  false,
		},
		{
			name:       "unformattable version returns error",
			rawVersion: "gke-only",
			desiredVer: "1.27",
			wantLower:  false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cur := FormatMajorMinorVersion(tt.rawVersion)
			got, err := IsVersionLower(cur, tt.desiredVer)
			if cur == "" {
				if err == nil {
					t.Fatal("expected error when FormatMajorMinorVersion returns empty")
				}
				if got {
					t.Error("IsVersionLower() = true when current version is empty")
				}
				return
			}
			if err != nil {
				t.Fatalf("IsVersionLower() unexpected error: %v", err)
			}
			if got != tt.wantLower {
				t.Errorf("IsVersionLower(FormatMajorMinorVersion(%q), %q) = %v, want %v", tt.rawVersion, tt.desiredVer, got, tt.wantLower)
			}
		})
	}
}

func Test_FormatMajorMinorVersion(t *testing.T) {
	tests := []struct {
		version string
		want    string
	}{
		{version: "1.27.3-gke.100", want: "1.27"},
		{version: "1.24.14-gke.1200", want: "1.24"},
		{version: "1.23.17-gke.10000", want: "1.23"},
		{version: "1.22.17-gke.14100", want: "1.22"},
		{version: "gke-only", want: ""},
	}
	for _, tt := range tests {
		t.Run(tt.version, func(t *testing.T) {
			if got := FormatMajorMinorVersion(tt.version); got != tt.want {
				t.Errorf("FormatMajorMinorVersion() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_ExpectToBeUpgraded(t *testing.T) {
	tests := []struct {
		name       string
		desiredVer string
		curVer     string
		want       bool
	}{
		{name: "both empty", want: false},
		{name: "desired version is empty", curVer: "1.24", want: false},
		{name: "current version is empty", desiredVer: "1.24", want: false},
		{name: "same version", desiredVer: "1.24", curVer: "1.24", want: false},
		{name: "one minor ahead", desiredVer: "1.25", curVer: "1.24", want: true},
		{name: "two minors ahead", desiredVer: "1.26", curVer: "1.24", want: false},
		{name: "desired lower than current", desiredVer: "1.24", curVer: "1.25", want: false},
		{name: "desired much lower than current", desiredVer: "1.24", curVer: "1.26", want: false},
		{name: "two-digit minor bump", desiredVer: "1.10", curVer: "1.9", want: true},
		{name: "not one minor when two-digit current", desiredVer: "1.11", curVer: "1.9", want: false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := ExpectToBeUpgraded(tt.desiredVer, tt.curVer); got != tt.want {
				t.Errorf("ExpectToBeUpgraded() = %v, want %v", got, tt.want)
			}
		})
	}
}
