package image

import (
	"testing"
	"time"

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

	"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/options"
	"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
)

func newImageTagList(tagNames []string) *tag.ImageTagList {
	tagList := tag.NewImageTagList()
	for _, tagName := range tagNames {
		tagList.Add(tag.NewImageTag(tagName, time.Unix(0, 0), ""))
	}
	return tagList
}

func newImageTagListWithDate(tagNames []string) *tag.ImageTagList {
	tagList := tag.NewImageTagList()
	for i, t := range tagNames {
		tagList.Add(tag.NewImageTag(t, time.Unix(int64(i*5), 0), ""))
	}
	return tagList
}

func Test_LatestVersion(t *testing.T) {
	t.Run("Find the latest version without any constraint", func(t *testing.T) {
		tagList := newImageTagList([]string{"0.1", "0.5.1", "0.9", "1.0", "1.0.1", "1.1.2", "2.0.3"})
		img := NewFromIdentifier("jannfis/test:1.0")
		vc := VersionConstraint{}
		newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
		require.NoError(t, err)
		require.NotNil(t, newTag)
		assert.Equal(t, "2.0.3", newTag.TagName)
	})

	t.Run("Find the latest version with a semver constraint on major", func(t *testing.T) {
		tagList := newImageTagList([]string{"0.1", "0.5.1", "0.9", "1.0", "1.0.1", "1.1.2", "2.0.3"})
		img := NewFromIdentifier("jannfis/test:1.0")
		vc := VersionConstraint{Constraint: "^1.0"}
		newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
		require.NoError(t, err)
		require.NotNil(t, newTag)
		assert.Equal(t, "1.1.2", newTag.TagName)
	})

	t.Run("Find the latest version with a semver constraint on patch", func(t *testing.T) {
		tagList := newImageTagList([]string{"0.1", "0.5.1", "0.9", "1.0", "1.0.1", "1.1.2", "2.0.3"})
		img := NewFromIdentifier("jannfis/test:1.0")
		vc := VersionConstraint{Constraint: "~1.0"}
		newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
		require.NoError(t, err)
		require.NotNil(t, newTag)
		assert.Equal(t, "1.0.1", newTag.TagName)
	})

	t.Run("Find the latest version with a semver constraint that has no match", func(t *testing.T) {
		tagList := newImageTagList([]string{"0.1", "0.5.1", "0.9", "2.0.3"})
		img := NewFromIdentifier("jannfis/test:1.0")
		vc := VersionConstraint{Constraint: "~1.0"}
		newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
		require.NoError(t, err)
		require.Nil(t, newTag)
	})

	t.Run("Find the latest version with a semver constraint that is invalid", func(t *testing.T) {
		tagList := newImageTagList([]string{"0.1", "0.5.1", "0.9", "2.0.3"})
		img := NewFromIdentifier("jannfis/test:1.0")
		vc := VersionConstraint{Constraint: "latest"}
		newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
		assert.Error(t, err)
		assert.Nil(t, newTag)
	})

	t.Run("Find the latest version with no tags", func(t *testing.T) {
		tagList := newImageTagList([]string{})
		img := NewFromIdentifier("jannfis/test:1.0")
		vc := VersionConstraint{Constraint: "~1.0"}
		newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
		require.NoError(t, err)
		require.NotNil(t, newTag)
		assert.Equal(t, "1.0", newTag.TagName)
	})

	t.Run("Find the latest version using latest sortmode", func(t *testing.T) {
		tagList := newImageTagListWithDate([]string{"zz", "bb", "yy", "cc", "yy", "aa", "ll"})
		img := NewFromIdentifier("jannfis/test:bb")
		vc := VersionConstraint{Strategy: StrategyNewestBuild}
		newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
		require.NoError(t, err)
		require.NotNil(t, newTag)
		assert.Equal(t, "ll", newTag.TagName)
	})

	t.Run("Find the latest version using latest sortmode, invalid tags", func(t *testing.T) {
		tagList := newImageTagListWithDate([]string{"zz", "bb", "yy", "cc", "yy", "aa", "ll"})
		img := NewFromIdentifier("jannfis/test:bb")
		vc := VersionConstraint{Strategy: StrategySemVer}
		newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
		require.NoError(t, err)
		require.NotNil(t, newTag)
		assert.Equal(t, "bb", newTag.TagName)
	})

	t.Run("Find the latest version using VersionConstraint StrategyAlphabetical", func(t *testing.T) {
		tagList := newImageTagListWithDate([]string{"zz", "bb", "yy", "cc", "yy", "aa", "ll"})
		img := NewFromIdentifier("jannfis/test:bb")
		vc := VersionConstraint{Strategy: StrategyAlphabetical}
		newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
		require.NoError(t, err)
		require.NotNil(t, newTag)
		assert.Equal(t, "zz", newTag.TagName)
	})

	t.Run("Find the latest version using VersionConstraint StrategyDigest", func(t *testing.T) {
		tagList := tag.NewImageTagList()
		newDigest := "latest@sha:abcdefg"
		tagList.Add(tag.NewImageTag("latest", time.Unix(int64(6), 0), newDigest))
		img := NewFromIdentifier("jannfis/test:latest@sha:1234567")
		vc := VersionConstraint{Strategy: StrategyDigest, Constraint: "latest"}
		newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
		require.NoError(t, err)
		assert.Equal(t, "latest", newTag.TagName)
		assert.Equal(t, newDigest, newTag.TagDigest)
	})

}

func Test_UpdateStrategy_String(t *testing.T) {
	tests := []struct {
		name string
		us   UpdateStrategy
		want string
	}{
		{"StrategySemVer", StrategySemVer, "semver"},
		{"StrategyNewestBuild", StrategyNewestBuild, "newest-build"},
		{"StrategyAlphabetical", StrategyAlphabetical, "alphabetical"},
		{"StrategyDigest", StrategyDigest, "digest"},
		{"unknown", UpdateStrategy(-1), "unknown"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.want, tt.us.String())
		})
	}
}

func Test_NewVersionConstraint(t *testing.T) {
	constraint := NewVersionConstraint()
	assert.Equal(t, StrategySemVer, constraint.Strategy)
	assert.Equal(t, options.NewManifestOptions(), constraint.Options)
	assert.False(t, constraint.MatchFunc("", ""))
}

func Test_VersionConstraint_IsTagIgnored(t *testing.T) {
	versionConstraint := VersionConstraint{IgnoreList: []string{"tag1", "tag2"}}
	assert.True(t, versionConstraint.IsTagIgnored("tag1"))
	assert.True(t, versionConstraint.IsTagIgnored("tag2"))
	assert.False(t, versionConstraint.IsTagIgnored("tag3"))
	versionConstraint.IgnoreList = []string{"tag?", "foo"}
	assert.True(t, versionConstraint.IsTagIgnored("tag1"))
	assert.True(t, versionConstraint.IsTagIgnored("foo"))
	assert.False(t, versionConstraint.IsTagIgnored("tag10"))
}

func Test_UpdateStrategy_IsCacheable(t *testing.T) {
	assert.True(t, StrategySemVer.IsCacheable())
	assert.True(t, StrategyNewestBuild.IsCacheable())
	assert.True(t, StrategyAlphabetical.IsCacheable())
	assert.False(t, StrategyDigest.IsCacheable())
}

func Test_UpdateStrategy_NeedsMetadata(t *testing.T) {
	assert.False(t, StrategySemVer.NeedsMetadata())
	assert.True(t, StrategyNewestBuild.NeedsMetadata())
	assert.False(t, StrategyAlphabetical.NeedsMetadata())
	assert.False(t, StrategyDigest.NeedsMetadata())
}

func Test_UpdateStrategy_NeedsVersionConstraint(t *testing.T) {
	assert.False(t, StrategySemVer.NeedsVersionConstraint())
	assert.False(t, StrategyNewestBuild.NeedsVersionConstraint())
	assert.False(t, StrategyAlphabetical.NeedsVersionConstraint())
	assert.True(t, StrategyDigest.NeedsVersionConstraint())
}

func Test_UpdateStrategy_WantsOnlyConstraintTag(t *testing.T) {
	assert.False(t, StrategySemVer.WantsOnlyConstraintTag())
	assert.False(t, StrategyNewestBuild.WantsOnlyConstraintTag())
	assert.False(t, StrategyAlphabetical.WantsOnlyConstraintTag())
	assert.True(t, StrategyDigest.WantsOnlyConstraintTag())
}
