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

package structs

import (
	"reflect"
	"testing"
	"time"

	fuzz "github.com/google/gofuzz"
	"github.com/hashicorp/consul/api"
	"github.com/mitchellh/reflectwalk"
	"github.com/stretchr/testify/require"
)

func TestCheckDefinition_Defaults(t *testing.T) {
	def := CheckDefinition{}
	check := def.HealthCheck("node1")

	// Health checks default to critical state
	if check.Status != api.HealthCritical {
		t.Fatalf("bad: %v", check.Status)
	}
}

type walker struct {
	fields map[string]reflect.Value
}

func (w *walker) Struct(reflect.Value) error {
	return nil
}

func (w *walker) StructField(f reflect.StructField, v reflect.Value) error {
	if !f.Anonymous {
		w.fields[f.Name] = v
		return nil
	}
	return reflectwalk.SkipEntry
}

func mapFields(t *testing.T, obj interface{}) map[string]reflect.Value {
	w := &walker{make(map[string]reflect.Value)}
	if err := reflectwalk.Walk(obj, w); err != nil {
		t.Fatalf("failed to generate map fields for %+v - %v", obj, err)
	}
	return w.fields
}

func TestCheckDefinition_CheckType(t *testing.T) {

	// Fuzz a definition to fill all its fields with data.
	var def CheckDefinition
	fuzz.New().Fuzz(&def)
	orig := mapFields(t, def)

	// Remap the ID field which changes name, and redact fields we don't
	// expect in the copy.
	orig["CheckID"] = orig["ID"]
	delete(orig, "ID")
	delete(orig, "ServiceID")
	delete(orig, "Token")

	// Now convert to a check type and ensure that all fields left match.
	chk := def.CheckType()
	copy := mapFields(t, chk)
	for f, vo := range orig {
		vc, ok := copy[f]
		if !ok {
			t.Fatalf("struct is missing field %q", f)
		}

		if !reflect.DeepEqual(vo.Interface(), vc.Interface()) {
			t.Fatalf("copy skipped field %q", f)
		}
	}
}

func TestCheckDefinitionToCheckType(t *testing.T) {
	got := &CheckDefinition{
		ID:     "id",
		Name:   "name",
		Status: "green",
		Notes:  "notes",

		ServiceID:                      "svcid",
		Token:                          "tok",
		ScriptArgs:                     []string{"/bin/foo"},
		HTTP:                           "someurl",
		H2PING:                         "somehttp2url",
		TCP:                            "host:port",
		Interval:                       1 * time.Second,
		DockerContainerID:              "abc123",
		Shell:                          "/bin/ksh",
		OSService:                      "myco-svctype-svcname-001",
		TLSSkipVerify:                  true,
		Timeout:                        2 * time.Second,
		TTL:                            3 * time.Second,
		DeregisterCriticalServiceAfter: 4 * time.Second,
	}
	want := &CheckType{
		CheckID: "id",
		Name:    "name",
		Status:  "green",
		Notes:   "notes",

		ScriptArgs:                     []string{"/bin/foo"},
		HTTP:                           "someurl",
		H2PING:                         "somehttp2url",
		TCP:                            "host:port",
		Interval:                       1 * time.Second,
		DockerContainerID:              "abc123",
		Shell:                          "/bin/ksh",
		OSService:                      "myco-svctype-svcname-001",
		TLSSkipVerify:                  true,
		Timeout:                        2 * time.Second,
		TTL:                            3 * time.Second,
		DeregisterCriticalServiceAfter: 4 * time.Second,
	}
	require.Equal(t, want, got.CheckType())
}
