// Copyright 2016-2022, Pulumi Corporation.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package deploy

import (
	"testing"

	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
	"github.com/pulumi/pulumi/sdk/v3/go/common/slice"
	"github.com/stretchr/testify/assert"
)

// Note: the only valid way to add a resource to the node list is via the `add` method.
// This ensures that the `sorted` method works correctly.
type nodeList []*resource.State

func (nl *nodeList) Add(urn string, delete bool, children ...*resource.State) *resource.State {
	n := &resource.State{URN: resource.URN(urn), Delete: delete}
	for _, child := range children {
		child.Parent = n.URN
	}
	*nl = append(*nl, n)
	return n
}

func (nl *nodeList) AsRefreshSteps() map[*resource.State]Step {
	m := make(map[*resource.State]Step, len(*nl))
	for _, r := range *nl {
		m[r] = &RefreshStep{
			old: r,
			new: r,
		}
	}
	return m
}

func (nl *nodeList) sorted() []*resource.State {
	// Since we add elements before we add their parents, we are guaranteed a reverse
	// topological sort. We can retrieve a topological sort by reversing the list.
	l := slice.Prealloc[*resource.State](len(*nl))
	for i := len(*nl) - 1; i >= 0; i-- {
		l = append(l, (*nl)[i])
	}
	return l
}

func (nl *nodeList) Executor() *deploymentExecutor {
	return &deploymentExecutor{
		deployment: &Deployment{
			prev: &Snapshot{
				Resources: nl.sorted(),
			},
		},
	}
}

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

	t.Run("simple-deps", func(t *testing.T) {
		t.Parallel()
		nl := &nodeList{}
		nl.Add("A", true, nl.Add("B", false))

		ex := nl.Executor()

		ex.rebuildBaseState(nl.AsRefreshSteps(), true)

		assert.EqualValues(t, map[resource.URN]*resource.State{
			"B": {URN: "B"},
		}, ex.deployment.olds)
	})

	t.Run("tree", func(t *testing.T) {
		t.Parallel()
		nl := &nodeList{}
		nl.Add("A", false,
			nl.Add("C", true,
				nl.Add("F", false)),
			nl.Add("D", false,
				nl.Add("G", false),
				nl.Add("H", true)))
		nl.Add("B", true,
			nl.Add("E", true,
				nl.Add("I", false)))

		ex := nl.Executor()

		ex.rebuildBaseState(nl.AsRefreshSteps(), true)

		assert.EqualValues(t, map[resource.URN]*resource.State{
			"A": {URN: "A"},
			"I": {URN: "I"},
			"F": {URN: "F", Parent: "A"},
			"G": {URN: "G", Parent: "D"},
			"D": {URN: "D", Parent: "A"},
		}, ex.deployment.olds)
	})
}