// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package api

import (
	"reflect"
	"testing"

	"github.com/hashicorp/consul/sdk/testutil/retry"
)

func TestAPI_PreparedQuery(t *testing.T) {
	t.Parallel()
	c, s := makeClient(t)
	defer s.Stop()

	// Set up a node and a service.
	reg := &CatalogRegistration{
		Datacenter: "dc1",
		Node:       "foobar",
		Address:    "192.168.10.10",
		TaggedAddresses: map[string]string{
			"wan": "127.0.0.1",
		},
		NodeMeta: map[string]string{"somekey": "somevalue"},
		Service: &AgentService{
			ID:      "redis1",
			Service: "redis",
			Tags:    []string{"primary", "v1"},
			Meta:    map[string]string{"redis-version": "4.0"},
			Port:    8000,
		},
	}

	catalog := c.Catalog()
	retry.Run(t, func(r *retry.R) {
		if _, err := catalog.Register(reg, nil); err != nil {
			r.Fatal(err)
		}
		if _, _, err := catalog.Node("foobar", nil); err != nil {
			r.Fatal(err)
		}
	})

	// Create a simple prepared query.
	def := &PreparedQueryDefinition{
		Name: "test",
		Service: ServiceQuery{
			Service:     "redis",
			NodeMeta:    map[string]string{"somekey": "somevalue"},
			ServiceMeta: map[string]string{"redis-version": "4.0"},
		},
	}

	query := c.PreparedQuery()
	var err error
	def.ID, _, err = query.Create(def, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Read it back.
	defs, _, err := query.Get(def.ID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if len(defs) != 1 || !reflect.DeepEqual(defs[0], def) {
		t.Fatalf("bad: %v", defs)
	}

	// List them all.
	defs, _, err = query.List(nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if len(defs) != 1 || !reflect.DeepEqual(defs[0], def) {
		t.Fatalf("bad: %v", defs)
	}

	// Make an update.
	def.Name = "my-query"
	_, err = query.Update(def, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Read it back again to verify the update worked.
	defs, _, err = query.Get(def.ID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if len(defs) != 1 || !reflect.DeepEqual(defs[0], def) {
		t.Fatalf("bad: %v", defs)
	}

	// Execute by ID.
	results, _, err := query.Execute(def.ID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if len(results.Nodes) != 1 || results.Nodes[0].Node.Node != "foobar" {
		t.Fatalf("bad: %v", results)
	}
	if wan, ok := results.Nodes[0].Node.TaggedAddresses["wan"]; !ok || wan != "127.0.0.1" {
		t.Fatalf("bad: %v", results)
	}

	// Execute by name.
	results, _, err = query.Execute("my-query", nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if len(results.Nodes) != 1 || results.Nodes[0].Node.Node != "foobar" {
		t.Fatalf("bad: %v", results)
	}
	if wan, ok := results.Nodes[0].Node.TaggedAddresses["wan"]; !ok || wan != "127.0.0.1" {
		t.Fatalf("bad: %v", results)
	}
	if results.Nodes[0].Node.Datacenter != "dc1" {
		t.Fatalf("bad datacenter: %v", results)
	}

	// Add new node with failing health check.
	reg2 := reg
	reg2.Node = "failingnode"
	reg2.Check = &AgentCheck{
		Node:        "failingnode",
		ServiceID:   "redis1",
		ServiceName: "redis",
		Name:        "failingcheck",
		Status:      "critical",
	}
	retry.Run(t, func(r *retry.R) {
		if _, err := catalog.Register(reg2, nil); err != nil {
			r.Fatal(err)
		}
		if _, _, err := catalog.Node("failingnode", nil); err != nil {
			r.Fatal(err)
		}
	})

	// Execute by ID. Should return only healthy node.
	results, _, err = query.Execute(def.ID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if len(results.Nodes) != 1 || results.Nodes[0].Node.Node != "foobar" {
		t.Fatalf("bad: %v", results)
	}
	if wan, ok := results.Nodes[0].Node.TaggedAddresses["wan"]; !ok || wan != "127.0.0.1" {
		t.Fatalf("bad: %v", results)
	}

	// Update PQ with ignore rule for the failing check
	def.Service.IgnoreCheckIDs = []string{"failingcheck"}
	_, err = query.Update(def, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Execute by ID. Should return BOTH nodes ignoring the failing check.
	results, _, err = query.Execute(def.ID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if len(results.Nodes) != 2 {
		t.Fatalf("got %d nodes, want 2", len(results.Nodes))
	}

	// Delete it.
	_, err = query.Delete(def.ID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Make sure there are no longer any queries.
	defs, _, err = query.List(nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if len(defs) != 0 {
		t.Fatalf("bad: %v", defs)
	}
}

func TestAPI_PreparedQueryRemoveEmptyTags(t *testing.T) {
	t.Parallel()
	c, s := makeClient(t)
	defer s.Stop()

	def := &PreparedQueryDefinition{
		Name: "test",
		Service: ServiceQuery{
			Service: "redis",
		},
		Template: QueryTemplate{
			RemoveEmptyTags: false,
		},
	}

	query := c.PreparedQuery()
	var err error
	def.ID, _, err = query.Create(def, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	queries, _, err := query.Get(def.ID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if len(queries) != 1 {
		t.Fatalf("wrong length: %#v", queries)
	}
	if queries[0].Template.RemoveEmptyTags {
		t.Fatalf("wrong value: %v", queries[0].Template.RemoveEmptyTags)
	}

	def.Template.RemoveEmptyTags = true
	_, err = query.Update(def, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	queries, _, err = query.Get(def.ID, nil)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if len(queries) != 1 {
		t.Fatalf("wrong length: %#v", queries)
	}
	if !queries[0].Template.RemoveEmptyTags {
		t.Fatalf("wrong value: %v", queries[0].Template.RemoveEmptyTags)
	}

}
