package cmd

import (
	"context"
	"strings"
	"testing"

	fn "knative.dev/func/pkg/functions"
	"knative.dev/func/pkg/mock"
	. "knative.dev/func/pkg/testing"
)

// TestDescribe_Default ensures that running describe when there is no
// function in the given directory fails correctly.
func TestDescribe_Default(t *testing.T) {
	_ = FromTempDirectory(t)
	describer := mock.NewDescriber()

	cmd := NewDescribeCmd(NewTestClient(fn.WithDescriber(describer)))
	cmd.SetArgs([]string{})
	err := cmd.Execute()

	if err == nil {
		t.Fatal("describing a nonexistent function should error")
	}
	if !strings.Contains(err.Error(), "function not found at this path and no name provided") {
		t.Fatalf("Unexpected error text returned: %v", err)
	}
	if describer.DescribeInvoked {
		t.Fatal("Describer incorrectly invoked")
	}
}

// TestDescribe_Undeployed ensures that describing a function which exists,
// but has not been deployed, does not error but rather delegates to the
// deployer which will presumably describe it as being !deployed (See deployer
// test suite)
func TestDescribe_Undeployed(t *testing.T) {
	root := FromTempDirectory(t)

	client := fn.New()
	_, err := client.Init(fn.Function{
		Name:     "testfunc",
		Runtime:  "go",
		Registry: TestRegistry,
		Root:     root,
	})
	if err != nil {
		t.Fatal(err)
	}

	describer := mock.NewDescriber()

	cmd := NewDescribeCmd(NewTestClient(fn.WithDescriber(describer)))
	cmd.SetArgs([]string{})
	if err := cmd.Execute(); err != nil {
		t.Fatal(err)
	}

	if !describer.DescribeInvoked {
		t.Fatal("Describer should have been invoked for any initialized function")
	}
}

// TestDescribe_ByName ensures that describing a function by name invokes
// the describer appropriately.
func TestDescribe_ByName(t *testing.T) {
	var (
		testname  = "testname"
		describer = mock.NewDescriber()
	)

	describer.DescribeFn = func(_ context.Context, name, namespace string) (fn.Instance, error) {
		if name != testname {
			t.Fatalf("expected describe name '%v', got '%v'", testname, name)
		}
		return fn.Instance{}, nil
	}

	cmd := NewDescribeCmd(NewTestClient(fn.WithDescriber(describer)))
	cmd.SetArgs([]string{testname})
	if err := cmd.Execute(); err != nil {
		t.Fatal(err)
	}

	if !describer.DescribeInvoked {
		t.Fatal("Describer not invoked")
	}
}

// TestDescribe_ByProject ensures that describing the currently active project
// (func created in the current working directory) invokes the describer with
// its name correctly.
func TestDescribe_ByProject(t *testing.T) {
	root := FromTempDirectory(t)
	expected := "testname"

	_, err := fn.New().Init(fn.Function{
		Name:     expected,
		Runtime:  "go",
		Registry: TestRegistry,
		Root:     root,
	})
	if err != nil {
		t.Fatal(err)
	}

	describer := mock.NewDescriber()
	describer.DescribeFn = func(_ context.Context, name, namespace string) (i fn.Instance, err error) {
		if name != expected {
			t.Fatalf("expected describer to receive name %q, got %q", expected, name)
		}
		return
	}
	cmd := NewDescribeCmd(NewTestClient(fn.WithDescriber(describer)))
	cmd.SetArgs([]string{})
	if err := cmd.Execute(); err != nil {
		t.Fatal(err)
	}
}

// TestDescribe_NameAndPathExclusivity ensures that providing both a name
// and a path will generate an error.
func TestDescribe_NameAndPathExclusivity(t *testing.T) {
	d := mock.NewDescriber()
	cmd := NewDescribeCmd(NewTestClient(fn.WithDescriber(d)))
	cmd.SetArgs([]string{"-p", "./testpath", "testname"})
	if err := cmd.Execute(); err == nil {
		// TODO(lkingland): use a typed error
		t.Fatalf("expected error on conflicting flags not received")
	}
	if d.DescribeInvoked {
		t.Fatal("describer was invoked when conflicting flags were provided")
	}
}
