package cache

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"github.com/stretchr/testify/require"

	"github.com/aquasecurity/trivy/pkg/cloud/report"
)

func TestCache(t *testing.T) {

	tests := []struct {
		name     string
		input    report.Report
		services []string
	}{
		{
			name: "no services",
			input: report.Report{
				Provider:        "AWS",
				AccountID:       "1234567890",
				Region:          "us-east-1",
				Results:         make(map[string]report.ResultsAtTime),
				ServicesInScope: nil,
			},
			services: nil,
		},
		{
			name: "all services",
			input: report.Report{
				Provider:  "AWS",
				AccountID: "1234567890",
				Region:    "us-east-1",
				Results: map[string]report.ResultsAtTime{
					"s3": {
						Results:      nil,
						CreationTime: time.Now(),
					},
					"ec2": {
						Results:      nil,
						CreationTime: time.Now(),
					},
				},
				ServicesInScope: []string{"ec2", "s3"},
			},
			services: []string{"ec2", "s3"},
		},
		{
			name: "partial services",
			input: report.Report{
				Provider:  "AWS",
				AccountID: "1234567890",
				Region:    "us-east-1",
				Results: map[string]report.ResultsAtTime{
					"s3": {
						Results:      nil,
						CreationTime: time.Now(),
					},
					"ec2": {
						Results:      nil,
						CreationTime: time.Now(),
					},
				},
				ServicesInScope: []string{"ec2", "s3"},
			},
			services: []string{"ec2"},
		},
	}

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

			baseDir := t.TempDir()

			// ensure saving doesn't error
			cache := New(baseDir, time.Hour, test.input.Provider, test.input.AccountID, test.input.Region)
			require.NoError(t, cache.Save(&test.input))

			// ensure all scoped services were cached
			available := cache.ListAvailableServices(false)
			assert.Equal(t, test.input.ServicesInScope, available)

			// ensure all cached services are really available
			fullReport, err := cache.LoadReport(available...)
			require.NoError(t, err)
			assert.Equal(t, available, fullReport.ServicesInScope)

			// ensure loading restores all (specified) data
			loaded, err := cache.LoadReport(test.services...)
			require.NoError(t, err)

			assert.Equal(t, test.input.Provider, loaded.Provider)
			assert.Equal(t, test.input.AccountID, loaded.AccountID)
			assert.Equal(t, test.input.Region, loaded.Region)
			assert.ElementsMatch(t, test.services, loaded.ServicesInScope)

			var actualServices []string
			for service := range loaded.Results {
				actualServices = append(actualServices, service)
			}
			assert.ElementsMatch(t, test.services, actualServices)

			for _, service := range test.services {
				assert.Equal(t, test.input.Results[service].CreationTime.Format(time.RFC3339), loaded.Results[service].CreationTime.Format(time.RFC3339))
				assert.Equal(t, test.input.Results[service].Results, loaded.Results[service].Results)
			}
		})
	}
}

func TestPartialCacheOverwrite(t *testing.T) {
	baseDir := t.TempDir()

	r1 := report.Report{
		Provider:  "AWS",
		AccountID: "1234567890",
		Region:    "us-east-1",
		Results: map[string]report.ResultsAtTime{
			"a": {
				Results:      nil,
				CreationTime: time.Now(),
			},
			"b": {
				Results:      nil,
				CreationTime: time.Now(),
			},
			"c": {
				Results:      nil,
				CreationTime: time.Now(),
			},
			"d": {
				Results:      nil,
				CreationTime: time.Now(),
			},
		},
		ServicesInScope: []string{"a", "b", "c", "d"},
	}

	// ensure saving doesn't error
	cache := New(baseDir, time.Hour, "AWS", "1234567890", "us-east-1")
	require.NoError(t, cache.Save(&r1))

	r2 := report.Report{
		Provider:  "AWS",
		AccountID: "1234567890",
		Region:    "us-east-1",
		Results: map[string]report.ResultsAtTime{
			"a": {
				Results:      nil,
				CreationTime: time.Now(),
			},
			"b": {
				Results:      nil,
				CreationTime: time.Now(),
			},
		},
		ServicesInScope: []string{"a", "b"},
	}
	require.NoError(t, cache.Save(&r2))

	assert.ElementsMatch(t, []string{"a", "b", "c", "d"}, cache.ListAvailableServices(false))
}
