package util_test

import (
	"testing"

	"galaxy/util"

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

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

	tests := []struct {
		name    string
		input   []uint
		want    util.SemVer
		wantErr string
	}{
		{
			name:  "major only",
			input: []uint{1},
			want:  util.SemVer{Major: 1},
		},
		{
			name:  "major and minor",
			input: []uint{1, 2},
			want:  util.SemVer{Major: 1, Minor: 2},
		},
		{
			name:  "major minor and patch",
			input: []uint{1, 2, 3},
			want:  util.SemVer{Major: 1, Minor: 2, Patch: 3},
		},
		{
			name:  "all components",
			input: []uint{1, 2, 3, 4},
			want:  util.SemVer{Major: 1, Minor: 2, Patch: 3, Build: 4},
		},
		{
			name:    "missing major",
			input:   nil,
			wantErr: "incorrect args count: 0",
		},
		{
			name:    "too many components",
			input:   []uint{1, 2, 3, 4, 5},
			wantErr: "incorrect args count: 5",
		},
	}

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

			got, err := util.NewSemver(tt.input...)
			if tt.wantErr != "" {
				require.Error(t, err)
				assert.Contains(t, err.Error(), tt.wantErr)
				assert.Equal(t, util.SemVer{}, got)
				return
			}

			require.NoError(t, err)
			assert.Equal(t, tt.want, got)
		})
	}
}

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

	t.Run("returns version", func(t *testing.T) {
		t.Parallel()

		assert.Equal(t, util.SemVer{Major: 1, Minor: 2, Patch: 3, Build: 4}, util.MustSemver(1, 2, 3, 4))
	})

	t.Run("panics on invalid input", func(t *testing.T) {
		t.Parallel()

		var recovered any

		func() {
			defer func() {
				recovered = recover()
			}()

			util.MustSemver()
		}()

		require.NotNil(t, recovered)

		err, ok := recovered.(error)
		require.True(t, ok)
		assert.EqualError(t, err, "new semver: incorrect args count: 0")
	})
}

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

	tests := []struct {
		name    string
		input   string
		want    util.SemVer
		wantErr string
	}{
		{
			name:  "major only",
			input: "1",
			want:  util.SemVer{Major: 1},
		},
		{
			name:  "major and minor",
			input: "1.2",
			want:  util.SemVer{Major: 1, Minor: 2},
		},
		{
			name:  "major minor and patch",
			input: "1.2.3",
			want:  util.SemVer{Major: 1, Minor: 2, Patch: 3},
		},
		{
			name:  "all components",
			input: "1.2.3.4",
			want:  util.SemVer{Major: 1, Minor: 2, Patch: 3, Build: 4},
		},
		{
			name:  "v prefix",
			input: "v2.3.4.5",
			want:  util.SemVer{Major: 2, Minor: 3, Patch: 4, Build: 5},
		},
		{
			name:  "v dot prefix",
			input: "v.6.7.8.9",
			want:  util.SemVer{Major: 6, Minor: 7, Patch: 8, Build: 9},
		},
		{
			name:  "leading zeros",
			input: "v.01.002.0003.0004",
			want:  util.SemVer{Major: 1, Minor: 2, Patch: 3, Build: 4},
		},
		{
			name:    "empty input",
			input:   "",
			wantErr: "missing major version",
		},
		{
			name:    "prefix without version",
			input:   "v",
			wantErr: "missing major version",
		},
		{
			name:    "prefix with dot without version",
			input:   "v.",
			wantErr: "missing major version",
		},
		{
			name:    "leading dot",
			input:   ".1",
			wantErr: "empty version part at position 1",
		},
		{
			name:    "trailing dot",
			input:   "1.",
			wantErr: "empty version part at position 2",
		},
		{
			name:    "empty middle part",
			input:   "1..2",
			wantErr: "empty version part at position 2",
		},
		{
			name:    "too many parts",
			input:   "1.2.3.4.5",
			wantErr: "too many version parts: 5",
		},
		{
			name:    "non numeric part",
			input:   "1.2.beta",
			wantErr: `parse part "beta"`,
		},
		{
			name:    "negative part",
			input:   "1.-2",
			wantErr: `parse part "-2"`,
		},
		{
			name:    "spaces are not accepted",
			input:   " 1.2 ",
			wantErr: `parse part " 1"`,
		},
	}

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

			got, err := util.ParseSemver(tt.input)
			if tt.wantErr != "" {
				require.Error(t, err)
				assert.Contains(t, err.Error(), tt.wantErr)
				assert.Equal(t, util.SemVer{}, got)
				return
			}

			require.NoError(t, err)
			assert.Equal(t, tt.want, got)
		})
	}
}

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

	tests := []struct {
		name string
		x    util.SemVer
		y    util.SemVer
		want int
	}{
		{
			name: "equal versions",
			x:    util.SemVer{Major: 1, Minor: 2, Patch: 3, Build: 4},
			y:    util.SemVer{Major: 1, Minor: 2, Patch: 3, Build: 4},
			want: 0,
		},
		{
			name: "x less by major",
			x:    util.SemVer{Major: 1, Minor: 9, Patch: 9, Build: 9},
			y:    util.SemVer{Major: 2},
			want: 1,
		},
		{
			name: "x greater by major",
			x:    util.SemVer{Major: 2},
			y:    util.SemVer{Major: 1, Minor: 9, Patch: 9, Build: 9},
			want: -1,
		},
		{
			name: "x less by minor",
			x:    util.SemVer{Major: 1, Minor: 1, Patch: 9, Build: 9},
			y:    util.SemVer{Major: 1, Minor: 2},
			want: 1,
		},
		{
			name: "x less by patch",
			x:    util.SemVer{Major: 1, Minor: 2, Patch: 3, Build: 9},
			y:    util.SemVer{Major: 1, Minor: 2, Patch: 4},
			want: 1,
		},
		{
			name: "x less by build",
			x:    util.SemVer{Major: 1, Minor: 2, Patch: 3, Build: 4},
			y:    util.SemVer{Major: 1, Minor: 2, Patch: 3, Build: 5},
			want: 1,
		},
	}

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

			assert.Equal(t, tt.want, util.CompareSemver(tt.x, tt.y))
		})
	}
}
