package updates

import "testing"

func TestNormalizeVersionString(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "release version stays the same",
			input:    "4.24.0-rc.3",
			expected: "4.24.0-rc.3",
		},
		{
			name:     "git describe converts to build metadata",
			input:    "4.24.0-rc.3-45-gABCDEF",
			expected: "4.24.0-rc.3+git.45.gabcdef",
		},
		{
			name:     "git describe dirty includes dirty flag",
			input:    "4.24.0-rc.3-45-gabc123-dirty",
			expected: "4.24.0-rc.3+git.45.gabc123.dirty",
		},
		{
			name:     "branch name falls back to prerelease",
			input:    "issue-551",
			expected: "0.0.0-issue-551",
		},
		{
			name:     "branch with slashes sanitized",
			input:    "feature/new-api",
			expected: "0.0.0-feature-new-api",
		},
		{
			name:     "empty input defaults to dev",
			input:    "",
			expected: "0.0.0-dev",
		},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if got := normalizeVersionString(tc.input); got != tc.expected {
				t.Fatalf("normalizeVersionString(%q) = %q, expected %q", tc.input, got, tc.expected)
			}
		})
	}
}

func TestParseVersion(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name       string
		input      string
		wantErr    bool
		wantMajor  int
		wantMinor  int
		wantPatch  int
		wantPrerel string
		wantBuild  string
	}{
		{
			name:      "basic version",
			input:     "4.24.0",
			wantMajor: 4, wantMinor: 24, wantPatch: 0,
		},
		{
			name:      "with v prefix",
			input:     "v4.24.0",
			wantMajor: 4, wantMinor: 24, wantPatch: 0,
		},
		{
			name:       "with prerelease",
			input:      "4.24.0-rc.3",
			wantMajor:  4, wantMinor: 24, wantPatch: 0,
			wantPrerel: "rc.3",
		},
		{
			name:      "with build metadata",
			input:     "4.24.0+build.123",
			wantMajor: 4, wantMinor: 24, wantPatch: 0,
			wantBuild: "build.123",
		},
		{
			name:       "with prerelease and build",
			input:      "4.24.0-rc.3+build.123",
			wantMajor:  4, wantMinor: 24, wantPatch: 0,
			wantPrerel: "rc.3",
			wantBuild:  "build.123",
		},
		{
			name:    "invalid format",
			input:   "not-a-version",
			wantErr: true,
		},
		{
			name:    "empty string",
			input:   "",
			wantErr: true,
		},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			got, err := ParseVersion(tc.input)
			if tc.wantErr {
				if err == nil {
					t.Fatalf("ParseVersion(%q) expected error, got nil", tc.input)
				}
				return
			}
			if err != nil {
				t.Fatalf("ParseVersion(%q) unexpected error: %v", tc.input, err)
			}
			if got.Major != tc.wantMajor || got.Minor != tc.wantMinor || got.Patch != tc.wantPatch {
				t.Fatalf("ParseVersion(%q) = %d.%d.%d, want %d.%d.%d",
					tc.input, got.Major, got.Minor, got.Patch, tc.wantMajor, tc.wantMinor, tc.wantPatch)
			}
			if got.Prerelease != tc.wantPrerel {
				t.Fatalf("ParseVersion(%q).Prerelease = %q, want %q", tc.input, got.Prerelease, tc.wantPrerel)
			}
			if got.Build != tc.wantBuild {
				t.Fatalf("ParseVersion(%q).Build = %q, want %q", tc.input, got.Build, tc.wantBuild)
			}
		})
	}
}

func TestDetectChannelFromVersion(t *testing.T) {
	t.Parallel()

	if got := detectChannelFromVersion("4.24.0-rc.3"); got != "rc" {
		t.Fatalf("detectChannelFromVersion rc = %s, expected rc", got)
	}

	if got := detectChannelFromVersion("0.0.0-feature-x"); got != "stable" {
		t.Fatalf("detectChannelFromVersion stable = %s, expected stable", got)
	}
}

func TestVersionString(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		version  Version
		expected string
	}{
		{
			name:     "basic version",
			version:  Version{Major: 4, Minor: 24, Patch: 0},
			expected: "4.24.0",
		},
		{
			name:     "with prerelease",
			version:  Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc.3"},
			expected: "4.24.0-rc.3",
		},
		{
			name:     "with build metadata",
			version:  Version{Major: 4, Minor: 24, Patch: 0, Build: "build.123"},
			expected: "4.24.0+build.123",
		},
		{
			name:     "with prerelease and build",
			version:  Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc.3", Build: "build.123"},
			expected: "4.24.0-rc.3+build.123",
		},
		{
			name:     "zero version",
			version:  Version{Major: 0, Minor: 0, Patch: 0},
			expected: "0.0.0",
		},
		{
			name:     "large numbers",
			version:  Version{Major: 100, Minor: 200, Patch: 300},
			expected: "100.200.300",
		},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if got := tc.version.String(); got != tc.expected {
				t.Fatalf("Version.String() = %q, expected %q", got, tc.expected)
			}
		})
	}
}

func TestVersionCompare(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		v1       Version
		v2       Version
		expected int
	}{
		{
			name:     "equal versions",
			v1:       Version{Major: 4, Minor: 24, Patch: 0},
			v2:       Version{Major: 4, Minor: 24, Patch: 0},
			expected: 0,
		},
		{
			name:     "v1 major greater",
			v1:       Version{Major: 5, Minor: 0, Patch: 0},
			v2:       Version{Major: 4, Minor: 24, Patch: 0},
			expected: 1,
		},
		{
			name:     "v1 major less",
			v1:       Version{Major: 3, Minor: 99, Patch: 99},
			v2:       Version{Major: 4, Minor: 0, Patch: 0},
			expected: -1,
		},
		{
			name:     "v1 minor greater",
			v1:       Version{Major: 4, Minor: 25, Patch: 0},
			v2:       Version{Major: 4, Minor: 24, Patch: 0},
			expected: 1,
		},
		{
			name:     "v1 minor less",
			v1:       Version{Major: 4, Minor: 23, Patch: 99},
			v2:       Version{Major: 4, Minor: 24, Patch: 0},
			expected: -1,
		},
		{
			name:     "v1 patch greater",
			v1:       Version{Major: 4, Minor: 24, Patch: 1},
			v2:       Version{Major: 4, Minor: 24, Patch: 0},
			expected: 1,
		},
		{
			name:     "v1 patch less",
			v1:       Version{Major: 4, Minor: 24, Patch: 0},
			v2:       Version{Major: 4, Minor: 24, Patch: 1},
			expected: -1,
		},
		{
			name:     "release > prerelease",
			v1:       Version{Major: 4, Minor: 24, Patch: 0},
			v2:       Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc.1"},
			expected: 1,
		},
		{
			name:     "prerelease < release",
			v1:       Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc.1"},
			v2:       Version{Major: 4, Minor: 24, Patch: 0},
			expected: -1,
		},
		{
			name:     "RC comparison - rc.9 > rc.1",
			v1:       Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc.9"},
			v2:       Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc.1"},
			expected: 1,
		},
		{
			name:     "RC comparison - rc.1 < rc.9",
			v1:       Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc.1"},
			v2:       Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc.9"},
			expected: -1,
		},
		{
			name:     "RC comparison - rc9 format",
			v1:       Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc9"},
			v2:       Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc1"},
			expected: 1,
		},
		{
			name:     "equal prereleases",
			v1:       Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc.3"},
			v2:       Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc.3"},
			expected: 0,
		},
		{
			name:     "string prerelease comparison",
			v1:       Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "beta"},
			v2:       Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "alpha"},
			expected: 1, // "beta" > "alpha" lexically
		},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if got := tc.v1.Compare(&tc.v2); got != tc.expected {
				t.Fatalf("(%v).Compare(%v) = %d, expected %d", tc.v1, tc.v2, got, tc.expected)
			}
		})
	}
}

func TestVersionIsNewerThan(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		v1       Version
		v2       Version
		expected bool
	}{
		{
			name:     "newer version",
			v1:       Version{Major: 5, Minor: 0, Patch: 0},
			v2:       Version{Major: 4, Minor: 24, Patch: 0},
			expected: true,
		},
		{
			name:     "older version",
			v1:       Version{Major: 4, Minor: 0, Patch: 0},
			v2:       Version{Major: 4, Minor: 24, Patch: 0},
			expected: false,
		},
		{
			name:     "equal versions",
			v1:       Version{Major: 4, Minor: 24, Patch: 0},
			v2:       Version{Major: 4, Minor: 24, Patch: 0},
			expected: false,
		},
		{
			name:     "release newer than prerelease",
			v1:       Version{Major: 4, Minor: 24, Patch: 0},
			v2:       Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc.1"},
			expected: true,
		},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if got := tc.v1.IsNewerThan(&tc.v2); got != tc.expected {
				t.Fatalf("(%v).IsNewerThan(%v) = %v, expected %v", tc.v1, tc.v2, got, tc.expected)
			}
		})
	}
}

func TestVersionIsPrerelease(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		version  Version
		expected bool
	}{
		{
			name:     "release version",
			version:  Version{Major: 4, Minor: 24, Patch: 0},
			expected: false,
		},
		{
			name:     "prerelease version",
			version:  Version{Major: 4, Minor: 24, Patch: 0, Prerelease: "rc.1"},
			expected: true,
		},
		{
			name:     "alpha prerelease",
			version:  Version{Major: 1, Minor: 0, Patch: 0, Prerelease: "alpha"},
			expected: true,
		},
		{
			name:     "build metadata only is not prerelease",
			version:  Version{Major: 4, Minor: 24, Patch: 0, Build: "build.123"},
			expected: false,
		},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if got := tc.version.IsPrerelease(); got != tc.expected {
				t.Fatalf("(%v).IsPrerelease() = %v, expected %v", tc.version, got, tc.expected)
			}
		})
	}
}

func TestCompareInts(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		a        int
		b        int
		expected int
	}{
		{name: "a < b", a: 1, b: 2, expected: -1},
		{name: "a > b", a: 2, b: 1, expected: 1},
		{name: "a == b", a: 1, b: 1, expected: 0},
		{name: "negative numbers a < b", a: -2, b: -1, expected: -1},
		{name: "negative numbers a > b", a: -1, b: -2, expected: 1},
		{name: "zero comparisons", a: 0, b: 1, expected: -1},
		{name: "large numbers", a: 1000000, b: 1000001, expected: -1},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if got := compareInts(tc.a, tc.b); got != tc.expected {
				t.Fatalf("compareInts(%d, %d) = %d, expected %d", tc.a, tc.b, got, tc.expected)
			}
		})
	}
}

func TestExtractRCNumber(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name       string
		prerelease string
		expected   int
	}{
		{name: "rc.1", prerelease: "rc.1", expected: 1},
		{name: "rc.9", prerelease: "rc.9", expected: 9},
		{name: "rc1", prerelease: "rc1", expected: 1},
		{name: "rc9", prerelease: "rc9", expected: 9},
		{name: "RC.3 uppercase", prerelease: "RC.3", expected: 3},
		{name: "RC5 uppercase no dot", prerelease: "RC5", expected: 5},
		{name: "rc.10 double digit", prerelease: "rc.10", expected: 10},
		{name: "rc.123 triple digit", prerelease: "rc.123", expected: 123},
		{name: "alpha no RC", prerelease: "alpha", expected: -1},
		{name: "beta.1 no RC", prerelease: "beta.1", expected: -1},
		{name: "empty string", prerelease: "", expected: -1},
		{name: "just rc no number", prerelease: "rc", expected: -1},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if got := extractRCNumber(tc.prerelease); got != tc.expected {
				t.Fatalf("extractRCNumber(%q) = %d, expected %d", tc.prerelease, got, tc.expected)
			}
		})
	}
}

func TestEnvBool(t *testing.T) {
	tests := []struct {
		name     string
		value    string
		expected bool
	}{
		{name: "1 is true", value: "1", expected: true},
		{name: "true is true", value: "true", expected: true},
		{name: "TRUE is true", value: "TRUE", expected: true},
		{name: "True is true", value: "True", expected: true},
		{name: "yes is true", value: "yes", expected: true},
		{name: "YES is true", value: "YES", expected: true},
		{name: "on is true", value: "on", expected: true},
		{name: "ON is true", value: "ON", expected: true},
		{name: "0 is false", value: "0", expected: false},
		{name: "false is false", value: "false", expected: false},
		{name: "FALSE is false", value: "FALSE", expected: false},
		{name: "no is false", value: "no", expected: false},
		{name: "off is false", value: "off", expected: false},
		{name: "empty is false", value: "", expected: false},
		{name: "random string is false", value: "random", expected: false},
		{name: "whitespace true", value: "  true  ", expected: true},
		{name: "whitespace 1", value: "  1  ", expected: true},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			key := "TEST_ENV_BOOL_" + tc.name
			t.Setenv(key, tc.value)
			if got := envBool(key); got != tc.expected {
				t.Fatalf("envBool(%q) with value %q = %v, expected %v", key, tc.value, got, tc.expected)
			}
		})
	}
}

func TestSanitizePrereleaseIdentifier(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{name: "simple branch", input: "feature-x", expected: "feature-x"},
		{name: "branch with slash", input: "feature/new-api", expected: "feature-new-api"},
		{name: "branch with multiple slashes", input: "user/feature/test", expected: "user-feature-test"},
		{name: "uppercase converts to lowercase", input: "FEATURE-X", expected: "feature-x"},
		{name: "mixed case", input: "Feature-X", expected: "feature-x"},
		{name: "numbers preserved", input: "issue-123", expected: "issue-123"},
		{name: "dots preserved", input: "v1.2.3", expected: "v1.2.3"},
		{name: "special chars replaced", input: "feat@test#123", expected: "feat-test-123"},
		{name: "consecutive separators collapsed", input: "feature//test", expected: "feature-test"},
		{name: "leading separator trimmed", input: "-feature", expected: "feature"},
		{name: "trailing separator trimmed", input: "feature-", expected: "feature"},
		{name: "empty string", input: "", expected: "dev"},
		{name: "whitespace only", input: "   ", expected: "dev"},
		{name: "only special chars", input: "@#$%", expected: "dev"},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if got := sanitizePrereleaseIdentifier(tc.input); got != tc.expected {
				t.Fatalf("sanitizePrereleaseIdentifier(%q) = %q, expected %q", tc.input, got, tc.expected)
			}
		})
	}
}

func TestNormalizeGitDescribeVersion(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		input    string
		expected string
		ok       bool
	}{
		// Valid git describe outputs
		{
			name:     "basic git describe",
			input:    "4.24.0-45-gabcdef",
			expected: "4.24.0+git.45.gabcdef",
			ok:       true,
		},
		{
			name:     "with prerelease",
			input:    "4.24.0-rc.3-45-gABCDEF",
			expected: "4.24.0-rc.3+git.45.gabcdef",
			ok:       true,
		},
		{
			name:     "with dirty flag",
			input:    "4.24.0-rc.3-45-gabc123-dirty",
			expected: "4.24.0-rc.3+git.45.gabc123.dirty",
			ok:       true,
		},
		{
			name:     "uppercase hash normalized",
			input:    "4.24.0-1-gABCDEF",
			expected: "4.24.0+git.1.gabcdef",
			ok:       true,
		},
		{
			name:     "dirty without prerelease",
			input:    "4.24.0-10-g1234567-dirty",
			expected: "4.24.0+git.10.g1234567.dirty",
			ok:       true,
		},

		// Invalid inputs
		{
			name:     "plain version",
			input:    "4.24.0",
			expected: "",
			ok:       false,
		},
		{
			name:     "version with prerelease only",
			input:    "4.24.0-rc.1",
			expected: "",
			ok:       false,
		},
		{
			name:     "empty string",
			input:    "",
			expected: "",
			ok:       false,
		},
		{
			name:     "branch name",
			input:    "feature-branch",
			expected: "",
			ok:       false,
		},
		{
			name:     "invalid base version",
			input:    "invalid-45-gabcdef",
			expected: "",
			ok:       false,
		},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			got, ok := normalizeGitDescribeVersion(tc.input)
			if ok != tc.ok {
				t.Fatalf("normalizeGitDescribeVersion(%q) ok = %v, expected %v", tc.input, ok, tc.ok)
			}
			if got != tc.expected {
				t.Fatalf("normalizeGitDescribeVersion(%q) = %q, expected %q", tc.input, got, tc.expected)
			}
		})
	}
}
