// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

//go:build !consulent

package consul

import (
	"sort"
	"testing"
	"time"

	"github.com/hashicorp/consul/agent/structs"
	"github.com/stretchr/testify/require"
)

func testLeader_LegacyIntentionMigrationHookEnterprise(_ *testing.T, _ *Server, _ bool) {
}

func appendLegacyIntentionsForMigrationTestEnterprise(_ *testing.T, _ *Server, ixns []*structs.Intention) []*structs.Intention {
	return ixns
}

func TestMigrateIntentionsToConfigEntries(t *testing.T) {
	compare := func(t *testing.T, got structs.Intentions, expect [][]string) {
		t.Helper()

		var actual [][]string
		for _, ixn := range got {
			actual = append(actual, []string{
				ixn.SourceNS,
				ixn.SourceName,
				ixn.DestinationNS,
				ixn.DestinationName,
			})
		}
		require.ElementsMatch(t, expect, actual)
	}

	type testCase struct {
		insert [][]string
		expect [][]string
	}

	cases := map[string]testCase{
		"no change": {
			insert: [][]string{
				{"default", "foo", "default", "bar"},
				{"default", "*", "default", "*"},
			},
			expect: [][]string{
				{"default", "foo", "default", "bar"},
				{"default", "*", "default", "*"},
			},
		},
		"non-wildcard deletions": {
			insert: [][]string{
				{"default", "foo", "default", "bar"},
				{"alpha", "*", "default", "bar"},
				{"default", "foo", "beta", "*"},
				{"alpha", "zoo", "beta", "bar"},
			},
			expect: [][]string{
				{"default", "foo", "default", "bar"},
			},
		},
		"updates with no deletions and no collisions": {
			insert: [][]string{
				{"default", "foo", "default", "bar"},
				{"default", "foo", "*", "*"},
				{"*", "*", "default", "bar"},
				{"*", "*", "*", "*"},
			},
			expect: [][]string{
				{"default", "foo", "default", "bar"},
				{"default", "foo", "default", "*"},
				{"default", "*", "default", "bar"},
				{"default", "*", "default", "*"},
			},
		},
		"updates with only collision deletions": {
			insert: [][]string{
				{"default", "foo", "default", "bar"},
				{"default", "foo", "default", "*"},
				{"default", "foo", "*", "*"},
				{"default", "*", "default", "bar"},
				{"*", "*", "default", "bar"},
				{"default", "*", "default", "*"},
				{"*", "*", "*", "*"},
			},
			expect: [][]string{
				{"default", "foo", "default", "bar"},
				{"default", "foo", "default", "*"},
				{"default", "*", "default", "bar"},
				{"default", "*", "default", "*"},
			},
		},
		"a bit of everything": {
			insert: [][]string{
				{"default", "foo", "default", "bar"}, // retained
				{"default", "foo", "*", "*"},         // upgrade
				{"default", "*", "default", "bar"},   // retained in collision
				{"*", "*", "default", "bar"},         // deleted in collision
				{"default", "*", "default", "*"},     // retained in collision
				{"*", "*", "*", "*"},                 // deleted in collision
				{"alpha", "*", "default", "bar"},     // deleted
				{"default", "foo", "beta", "*"},      // deleted
				{"alpha", "zoo", "beta", "bar"},      // deleted
			},
			expect: [][]string{
				{"default", "foo", "default", "bar"},
				{"default", "foo", "default", "*"},
				{"default", "*", "default", "bar"},
				{"default", "*", "default", "*"},
			},
		},
	}
	for name, tc := range cases {
		tc := tc
		t.Run(name, func(t *testing.T) {

			// Do something super evil and directly reach into the FSM to seed it with "bad" data.
			var ixns structs.Intentions
			for _, elem := range tc.insert {
				require.Len(t, elem, 4)
				ixn := structs.TestIntention(t)
				ixn.ID = generateUUID()
				ixn.SourceNS = elem[0]
				ixn.SourceName = elem[1]
				ixn.DestinationNS = elem[2]
				ixn.DestinationName = elem[3]
				ixn.CreatedAt = time.Now().UTC()
				ixn.UpdatedAt = ixn.CreatedAt

				ixns = append(ixns, ixn)
			}

			// Sleep a bit so that the UpdatedAt field will definitely be different
			time.Sleep(1 * time.Millisecond)

			got := migrateIntentionsToConfigEntries(ixns)

			// Convert them back to the line-item version.
			var gotIxns structs.Intentions
			for _, entry := range got {
				gotIxns = append(gotIxns, entry.ToIntentions()...)
			}
			sort.Sort(structs.IntentionPrecedenceSorter(gotIxns))

			compare(t, gotIxns, tc.expect)
		})
	}
}
