package deploytest

import (
	"context"
	"fmt"
	"testing"

	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
	pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/types/known/structpb"
)

// Verifies that ReturnDependencies in a ResourceMonitor.Call
// are propagated back to the caller.
func TestResourceMonitor_Call_deps(t *testing.T) {
	t.Parallel()

	client := stubResourceMonitorClient{
		// ResourceMonitorClient is unset
		// so this will panic if an unexpected method is called.
		CallFunc: func(req *pulumirpc.ResourceCallRequest) (*pulumirpc.CallResponse, error) {
			assert.ElementsMatch(t, req.ArgDependencies["k1"].Urns, []string{"urn1", "urn2"})

			res, err := structpb.NewStruct(map[string]interface{}{
				"k3": "value3",
				"k4": "value4",
			})
			require.NoError(t, err)

			return &pulumirpc.CallResponse{
				Return: res,
				ReturnDependencies: map[string]*pulumirpc.CallResponse_ReturnDependencies{
					"foo": {Urns: []string{"urn1", "urn2"}},
					"bar": {Urns: []string{"urn3", "urn4"}},
				},
			}, nil
		},
	}

	_, deps, _, err := NewResourceMonitor(&client).Call(
		"org/proj/stack:module:member",
		resource.NewPropertyMapFromMap(map[string]interface{}{
			"k1": "value1",
			"k2": "value2",
		}),
		map[resource.PropertyKey][]resource.URN{
			"k1": {"urn1", "urn2"},
		},
		"provider", "1.0", "")
	require.NoError(t, err)

	assert.Equal(t, map[resource.PropertyKey][]resource.URN{
		"foo": {"urn1", "urn2"},
		"bar": {"urn3", "urn4"},
	}, deps)
}

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

	tests := []struct {
		desc string
		give *resource.CustomTimeouts
		want *pulumirpc.RegisterResourceRequest_CustomTimeouts
	}{
		{desc: "nil", give: nil, want: nil},
		{
			desc: "create",
			give: &resource.CustomTimeouts{Create: 1},
			want: &pulumirpc.RegisterResourceRequest_CustomTimeouts{Create: "1s"},
		},
		{
			desc: "update",
			give: &resource.CustomTimeouts{Update: 1},
			want: &pulumirpc.RegisterResourceRequest_CustomTimeouts{Update: "1s"},
		},
		{
			desc: "delete",
			give: &resource.CustomTimeouts{Delete: 1},
			want: &pulumirpc.RegisterResourceRequest_CustomTimeouts{Delete: "1s"},
		},
		{
			desc: "all",
			give: &resource.CustomTimeouts{Create: 1, Update: 2, Delete: 3},
			want: &pulumirpc.RegisterResourceRequest_CustomTimeouts{
				Create: "1s",
				Update: "2s",
				Delete: "3s",
			},
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.desc, func(t *testing.T) {
			t.Parallel()

			client := stubResourceMonitorClient{
				RegisterResourceFunc: func(
					req *pulumirpc.RegisterResourceRequest,
				) (*pulumirpc.RegisterResourceResponse, error) {
					assert.Equal(t, tt.want, req.CustomTimeouts)
					return &pulumirpc.RegisterResourceResponse{}, nil
				},
			}

			_, err := NewResourceMonitor(&client).RegisterResource("pkg:m:typ", "foo", true, ResourceOptions{
				CustomTimeouts: tt.give,
			})
			require.NoError(t, err)
		})
	}
}

// stubResourceMonitorClient is a ResourceMonitorClient
// that can stub out specific functions.
type stubResourceMonitorClient struct {
	pulumirpc.ResourceMonitorClient

	CallFunc             func(req *pulumirpc.ResourceCallRequest) (*pulumirpc.CallResponse, error)
	RegisterResourceFunc func(req *pulumirpc.RegisterResourceRequest) (*pulumirpc.RegisterResourceResponse, error)
}

func (cl *stubResourceMonitorClient) Call(
	ctx context.Context,
	req *pulumirpc.ResourceCallRequest,
	opts ...grpc.CallOption,
) (*pulumirpc.CallResponse, error) {
	if cl.CallFunc != nil {
		return cl.CallFunc(req)
	}
	return cl.ResourceMonitorClient.Call(ctx, req, opts...)
}

func (cl *stubResourceMonitorClient) RegisterResource(
	ctx context.Context,
	req *pulumirpc.RegisterResourceRequest,
	opts ...grpc.CallOption,
) (*pulumirpc.RegisterResourceResponse, error) {
	if cl.RegisterResourceFunc != nil {
		return cl.RegisterResourceFunc(req)
	}
	return cl.ResourceMonitorClient.RegisterResource(ctx, req, opts...)
}

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

	tests := []struct {
		give float64
		want string
	}{
		{0, ""},
		{1, "1s"},
		{1.5, "1.5s"},
		{62, "1m2s"},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(fmt.Sprint(tt.give), func(t *testing.T) {
			t.Parallel()

			assert.Equal(t, tt.want, prepareTestTimeout(tt.give))
		})
	}
}