package models

import (
	"testing"
	"time"

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

	"github.com/anchore/grype/grype/version"
	"github.com/anchore/grype/grype/vulnerability"
)

func Test_sortVersions(t *testing.T) {
	tests := []struct {
		name     string
		versions []string
		expected []string
	}{
		{
			name:     "empty slice",
			versions: []string{},
			expected: []string{},
		},
		{
			name:     "single version",
			versions: []string{"1.0.0"},
			expected: []string{"1.0.0"},
		},
		{
			name:     "already sorted versions",
			versions: []string{"1.0.0", "1.1.0", "2.0.0"},
			expected: []string{"1.0.0", "1.1.0", "2.0.0"},
		},
		{
			name:     "unsorted versions",
			versions: []string{"2.0.0", "1.0.0", "1.1.0"},
			expected: []string{"1.0.0", "1.1.0", "2.0.0"},
		},
		{
			name:     "patch versions",
			versions: []string{"1.0.2", "1.0.1", "1.0.0"},
			expected: []string{"1.0.0", "1.0.1", "1.0.2"},
		},
		{
			name:     "versions with pre-release",
			versions: []string{"1.0.0", "1.0.0-alpha", "1.0.0-beta"},
			expected: []string{"1.0.0-alpha", "1.0.0-beta", "1.0.0"},
		},
		{
			name:     "mixed pre-release and regular",
			versions: []string{"2.0.0", "1.0.0-alpha", "1.0.0", "1.0.0-beta"},
			expected: []string{"1.0.0-alpha", "1.0.0-beta", "1.0.0", "2.0.0"},
		},
		{
			name:     "versions with build metadata",
			versions: []string{"1.0.0+build.2", "1.0.0+build.1", "1.0.0"},
			expected: []string{"1.0.0+build.2", "1.0.0+build.1", "1.0.0"},
		},
		{
			name:     "complex semantic versions",
			versions: []string{"1.0.0-alpha.1", "1.0.0-alpha", "1.0.0-beta.2", "1.0.0-beta.11", "1.0.0-rc.1"},
			expected: []string{"1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-beta.2", "1.0.0-beta.11", "1.0.0-rc.1"},
		},
		{
			name:     "invalid versions are appended to the end (in the order they were found in)",
			versions: []string{"invalid", "2.0.0", "also-invalid", "1.0.0"},
			expected: []string{"1.0.0", "2.0.0", "invalid", "also-invalid"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := sortVersions(tt.versions, version.SemanticFormat)

			if d := cmp.Diff(tt.expected, result); d != "" {
				t.Errorf("sortVersions() mismatch (-want +got):\n%s", d)
			}
		})
	}
}

func Test_getFixAvailable(t *testing.T) {
	validDate1 := time.Date(2023, 1, 15, 0, 0, 0, 0, time.UTC)
	validDate2 := time.Date(2023, 3, 10, 0, 0, 0, 0, time.UTC)
	zeroDate := time.Time{}

	tests := []struct {
		name     string
		input    []vulnerability.FixAvailable
		expected []FixAvailable
	}{
		{
			name:     "empty input returns nil",
			input:    []vulnerability.FixAvailable{},
			expected: nil,
		},
		{
			name: "single fix with valid date",
			input: []vulnerability.FixAvailable{
				{Version: "1.2.3", Date: validDate1, Kind: "first-observed"},
			},
			expected: []FixAvailable{
				{Version: "1.2.3", Date: "2023-01-15", Kind: "first-observed"},
			},
		},
		{
			name: "multiple fixes with valid dates",
			input: []vulnerability.FixAvailable{
				{Version: "1.2.3", Date: validDate1, Kind: "first-observed"},
				{Version: "2.0.0", Date: validDate2, Kind: "first-observed"},
			},
			expected: []FixAvailable{
				{Version: "1.2.3", Date: "2023-01-15", Kind: "first-observed"},
				{Version: "2.0.0", Date: "2023-03-10", Kind: "first-observed"},
			},
		},
		{
			name: "filters out fixes with zero dates",
			input: []vulnerability.FixAvailable{
				{Version: "1.2.3", Date: validDate1, Kind: "first-observed"},
				{Version: "1.2.4", Date: zeroDate, Kind: "first-observed"},
				{Version: "2.0.0", Date: validDate2, Kind: "first-observed"},
			},
			expected: []FixAvailable{
				{Version: "1.2.3", Date: "2023-01-15", Kind: "first-observed"},
				{Version: "2.0.0", Date: "2023-03-10", Kind: "first-observed"},
			},
		},
		{
			name: "all fixes with zero dates returns nil",
			input: []vulnerability.FixAvailable{
				{Version: "1.2.3", Date: zeroDate, Kind: "first-observed"},
				{Version: "1.2.4", Date: zeroDate, Kind: "first-observed"},
			},
			expected: nil,
		},
		{
			name: "empty kind is preserved",
			input: []vulnerability.FixAvailable{
				{Version: "1.0.0", Date: validDate1, Kind: ""},
			},
			expected: []FixAvailable{
				{Version: "1.0.0", Date: "2023-01-15", Kind: ""},
			},
		},
	}

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

			if d := cmp.Diff(tt.expected, result); d != "" {
				t.Errorf("getFixAvailable() mismatch (-want +got):\n%s", d)
			}
		})
	}
}
