package cmd

import (
	"testing"

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

// 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(n string) (fn.Instance, error) {
		if n != testname {
			t.Fatalf("expected describe name '%v', got '%v'", testname, n)
		}
		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)

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

	describer := mock.NewDescriber()
	describer.DescribeFn = func(n string) (i fn.Instance, err error) {
		if n != "testname" {
			t.Fatalf("expected describer to receive name 'testname', got '%v'", n)
		}
		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")
	}
}

// TestDescribe_Namespace ensures that the namespace provided to the client
// for use when describing a function is set
//  1. Blank when not provided nor available (delegate to the describer impl to
//     choose current kube context)
//  2. The namespace of the contextually active function
//  3. The flag /env variable if provided
func TestDescribe_Namespace(t *testing.T) {
	root := fromTempDirectory(t)

	client := fn.New(fn.WithDescriber(mock.NewDescriber()))

	// Ensure that the default is "", indicating the describer should use
	// config.DefaultNamespace
	cmd := NewDescribeCmd(func(cc ClientConfig, _ ...fn.Option) (*fn.Client, func()) {
		if cc.Namespace != "" {
			t.Fatalf("expected '', got '%v'", cc.Namespace)
		}
		return client, func() {}
	})
	cmd.SetArgs([]string{"somefunc"}) // by name such that no f need be created
	if err := cmd.Execute(); err != nil {
		t.Fatal(err)
	}

	// Ensure the extant function's namespace is used
	f := fn.Function{
		Root:    root,
		Runtime: "go",
		Deploy: fn.DeploySpec{
			Namespace: "deployed",
		},
	}
	if _, err := client.Init(f); err != nil {
		t.Fatal(err)
	}
	cmd = NewDescribeCmd(func(cc ClientConfig, _ ...fn.Option) (*fn.Client, func()) {
		if cc.Namespace != "deployed" {
			t.Fatalf("expected 'deployed', got '%v'", cc.Namespace)
		}
		return client, func() {}
	})
	cmd.SetArgs([]string{})
	if err := cmd.Execute(); err != nil {
		t.Fatal(err)
	}

	// Ensure an explicit namespace is plumbed through
	cmd = NewDescribeCmd(func(cc ClientConfig, _ ...fn.Option) (*fn.Client, func()) {
		if cc.Namespace != "ns" {
			t.Fatalf("expected 'ns', got '%v'", cc.Namespace)
		}
		return client, func() {}
	})
	cmd.SetArgs([]string{"--namespace", "ns"})
	if err := cmd.Execute(); err != nil {
		t.Fatal(err)
	}

}
