// Copyright 2016-2018, 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 (
	"context"
	"testing"

	pbempty "github.com/golang/protobuf/ptypes/empty"
	"github.com/pulumi/pulumi/pkg/util/result"
	pulumirpc "github.com/pulumi/pulumi/sdk/proto/go"
	"github.com/stretchr/testify/assert"
)

func TestQuerySource_Trivial_Wait(t *testing.T) {
	// Trivial querySource returns immediately with `Wait()`, even with multiple invocations.

	// Success case.
	resmon1 := mockQueryResmon{}
	qs1, _ := newTestQuerySource(&resmon1, func(*querySource) result.Result {
		return nil
	})

	qs1.forkRun()

	res := qs1.Wait()
	assert.Nil(t, res)
	assert.False(t, resmon1.cancelled)

	res = qs1.Wait()
	assert.Nil(t, res)
	assert.False(t, resmon1.cancelled)

	// Failure case.
	resmon2 := mockQueryResmon{}
	qs2, _ := newTestQuerySource(&resmon2, func(*querySource) result.Result {
		return result.Error("failed")
	})

	qs2.forkRun()

	res = qs2.Wait()
	assert.False(t, res.IsBail())
	assert.NotNil(t, res.Error())
	assert.False(t, resmon2.cancelled)

	res = qs2.Wait()
	assert.False(t, res.IsBail())
	assert.NotNil(t, res.Error())
	assert.False(t, resmon2.cancelled)
}

func TestQuerySource_Async_Wait(t *testing.T) {
	// `Wait()` executes asynchronously.

	// Success case.
	//
	//    test blocks until querySource signals execution has started
	// -> querySource blocks until test acknowledges querySource's signal
	// -> test blocks on `Wait()` until querySource completes.
	qs1Start, qs1StartAck := make(chan interface{}), make(chan interface{})
	resmon1 := mockQueryResmon{}
	qs1, _ := newTestQuerySource(&resmon1, func(*querySource) result.Result {
		qs1Start <- struct{}{}
		<-qs1StartAck
		return nil
	})

	qs1.forkRun()

	// Wait until querySource starts, then acknowledge starting.
	<-qs1Start
	go func() {
		qs1StartAck <- struct{}{}
	}()

	// Wait for querySource to complete.
	res := qs1.Wait()
	assert.Nil(t, res)
	assert.False(t, resmon1.cancelled)

	res = qs1.Wait()
	assert.Nil(t, res)
	assert.False(t, resmon1.cancelled)

	// Cancellation case.
	//
	//    test blocks until querySource signals execution has started
	// -> querySource blocks until test acknowledges querySource's signal
	// -> test blocks on `Wait()` until querySource completes.
	qs2Start, qs2StartAck := make(chan interface{}), make(chan interface{})
	resmon2 := mockQueryResmon{}
	qs2, cancelQs2 := newTestQuerySource(&resmon2, func(*querySource) result.Result {
		qs2Start <- struct{}{}
		// Block forever.
		<-qs2StartAck
		return nil
	})

	qs2.forkRun()

	// Wait until querySource starts, then cancel.
	<-qs2Start
	go func() {
		cancelQs2()
	}()

	// Wait for querySource to complete.
	res = qs2.Wait()
	assert.Nil(t, res)
	assert.True(t, resmon2.cancelled)

	res = qs2.Wait()
	assert.Nil(t, res)
	assert.True(t, resmon2.cancelled)
}

func TestQueryResourceMonitor_UnsupportedOperations(t *testing.T) {
	rm := &queryResmon{}

	_, err := rm.ReadResource(context.TODO(), nil)
	assert.Error(t, err)
	assert.Equal(t, "Query mode does not support reading resources", err.Error())

	_, err = rm.RegisterResource(context.TODO(), nil)
	assert.Error(t, err)
	assert.Equal(t, "Query mode does not support creating, updating, or deleting resources", err.Error())

	_, err = rm.RegisterResourceOutputs(context.TODO(), nil)
	assert.Error(t, err)
	assert.Equal(t, "Query mode does not support registering resource operations", err.Error())
}

//
// Test querySoruce constructor.
//

func newTestQuerySource(mon SourceResourceMonitor,
	runLangPlugin func(*querySource) result.Result) (*querySource, context.CancelFunc) {

	cancel, cancelFunc := context.WithCancel(context.Background())

	return &querySource{
		mon:               mon,
		runLangPlugin:     runLangPlugin,
		langPluginFinChan: make(chan result.Result),
		cancel:            cancel,
	}, cancelFunc
}

//
// Mock resource monitor.
//

type mockQueryResmon struct {
	cancelled bool
}

var _ SourceResourceMonitor = (*mockQueryResmon)(nil)

func (rm *mockQueryResmon) Address() string {
	panic("not implemented")
}
func (rm *mockQueryResmon) Cancel() error {
	rm.cancelled = true
	return nil
}
func (rm *mockQueryResmon) Invoke(ctx context.Context,
	req *pulumirpc.InvokeRequest) (*pulumirpc.InvokeResponse, error) {

	panic("not implemented")
}
func (rm *mockQueryResmon) ReadResource(ctx context.Context,
	req *pulumirpc.ReadResourceRequest) (*pulumirpc.ReadResourceResponse, error) {

	panic("not implemented")
}
func (rm *mockQueryResmon) RegisterResource(ctx context.Context,
	req *pulumirpc.RegisterResourceRequest) (*pulumirpc.RegisterResourceResponse, error) {

	panic("not implemented")
}
func (rm *mockQueryResmon) RegisterResourceOutputs(ctx context.Context,
	req *pulumirpc.RegisterResourceOutputsRequest) (*pbempty.Empty, error) {

	panic("not implemented")
}
