package structs

import (
	"reflect"
	"strings"
	"testing"

	"github.com/hashicorp/go-multierror"
)

func TestJob_Validate(t *testing.T) {
	j := &Job{}
	err := j.Validate()
	mErr := err.(*multierror.Error)
	if !strings.Contains(mErr.Errors[0].Error(), "job region") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[1].Error(), "job ID") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[2].Error(), "job name") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[3].Error(), "job type") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[4].Error(), "priority") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[5].Error(), "datacenters") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[6].Error(), "task groups") {
		t.Fatalf("err: %s", err)
	}

	j = &Job{
		Region:      "global",
		ID:          GenerateUUID(),
		Name:        "my-job",
		Type:        JobTypeService,
		Priority:    50,
		Datacenters: []string{"dc1"},
		TaskGroups: []*TaskGroup{
			&TaskGroup{
				Name: "web",
			},
			&TaskGroup{
				Name: "web",
			},
			&TaskGroup{},
		},
	}
	err = j.Validate()
	mErr = err.(*multierror.Error)
	if !strings.Contains(mErr.Errors[0].Error(), "2 redefines 'web' from group 1") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[1].Error(), "group 3 missing name") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[2].Error(), "Task group 1 validation failed") {
		t.Fatalf("err: %s", err)
	}
}

func TestTaskGroup_Validate(t *testing.T) {
	tg := &TaskGroup{}
	err := tg.Validate()
	mErr := err.(*multierror.Error)
	if !strings.Contains(mErr.Errors[0].Error(), "group name") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[1].Error(), "count must be positive") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[2].Error(), "Missing tasks") {
		t.Fatalf("err: %s", err)
	}

	tg = &TaskGroup{
		Name:  "web",
		Count: 1,
		Tasks: []*Task{
			&Task{Name: "web"},
			&Task{Name: "web"},
			&Task{},
		},
	}
	err = tg.Validate()
	mErr = err.(*multierror.Error)
	if !strings.Contains(mErr.Errors[0].Error(), "2 redefines 'web' from task 1") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[1].Error(), "Task 3 missing name") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[2].Error(), "Task 1 validation failed") {
		t.Fatalf("err: %s", err)
	}
}

func TestTask_Validate(t *testing.T) {
	task := &Task{}
	err := task.Validate()
	mErr := err.(*multierror.Error)
	if !strings.Contains(mErr.Errors[0].Error(), "task name") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[1].Error(), "task driver") {
		t.Fatalf("err: %s", err)
	}
	if !strings.Contains(mErr.Errors[2].Error(), "task resources") {
		t.Fatalf("err: %s", err)
	}

	task = &Task{
		Name:      "web",
		Driver:    "docker",
		Resources: &Resources{},
	}
	err = task.Validate()
	if err != nil {
		t.Fatalf("err: %s", err)
	}
}

func TestConstraint_Validate(t *testing.T) {
	c := &Constraint{}
	err := c.Validate()
	mErr := err.(*multierror.Error)
	if !strings.Contains(mErr.Errors[0].Error(), "Missing constraint operand") {
		t.Fatalf("err: %s", err)
	}

	c = &Constraint{
		LTarget: "$attr.kernel.name",
		RTarget: "linux",
		Operand: "=",
	}
	err = c.Validate()
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Perform additional regexp validation
	c.Operand = ConstraintRegex
	c.RTarget = "(foo"
	err = c.Validate()
	mErr = err.(*multierror.Error)
	if !strings.Contains(mErr.Errors[0].Error(), "missing closing") {
		t.Fatalf("err: %s", err)
	}

	// Perform version validation
	c.Operand = ConstraintVersion
	c.RTarget = "~> foo"
	err = c.Validate()
	mErr = err.(*multierror.Error)
	if !strings.Contains(mErr.Errors[0].Error(), "Malformed constraint") {
		t.Fatalf("err: %s", err)
	}
}

func TestResource_NetIndex(t *testing.T) {
	r := &Resources{
		Networks: []*NetworkResource{
			&NetworkResource{Device: "eth0"},
			&NetworkResource{Device: "lo0"},
			&NetworkResource{Device: ""},
		},
	}
	if idx := r.NetIndex(&NetworkResource{Device: "eth0"}); idx != 0 {
		t.Fatalf("Bad: %d", idx)
	}
	if idx := r.NetIndex(&NetworkResource{Device: "lo0"}); idx != 1 {
		t.Fatalf("Bad: %d", idx)
	}
	if idx := r.NetIndex(&NetworkResource{Device: "eth1"}); idx != -1 {
		t.Fatalf("Bad: %d", idx)
	}
}

func TestResource_Superset(t *testing.T) {
	r1 := &Resources{
		CPU:      2000,
		MemoryMB: 2048,
		DiskMB:   10000,
		IOPS:     100,
	}
	r2 := &Resources{
		CPU:      2000,
		MemoryMB: 1024,
		DiskMB:   5000,
		IOPS:     50,
	}

	if s, _ := r1.Superset(r1); !s {
		t.Fatalf("bad")
	}
	if s, _ := r1.Superset(r2); !s {
		t.Fatalf("bad")
	}
	if s, _ := r2.Superset(r1); s {
		t.Fatalf("bad")
	}
	if s, _ := r2.Superset(r2); !s {
		t.Fatalf("bad")
	}
}

func TestResource_Add(t *testing.T) {
	r1 := &Resources{
		CPU:      2000,
		MemoryMB: 2048,
		DiskMB:   10000,
		IOPS:     100,
		Networks: []*NetworkResource{
			&NetworkResource{
				CIDR:          "10.0.0.0/8",
				MBits:         100,
				ReservedPorts: []int{22},
			},
		},
	}
	r2 := &Resources{
		CPU:      2000,
		MemoryMB: 1024,
		DiskMB:   5000,
		IOPS:     50,
		Networks: []*NetworkResource{
			&NetworkResource{
				IP:            "10.0.0.1",
				MBits:         50,
				ReservedPorts: []int{80},
			},
		},
	}

	err := r1.Add(r2)
	if err != nil {
		t.Fatalf("Err: %v", err)
	}

	expect := &Resources{
		CPU:      3000,
		MemoryMB: 3072,
		DiskMB:   15000,
		IOPS:     150,
		Networks: []*NetworkResource{
			&NetworkResource{
				CIDR:          "10.0.0.0/8",
				MBits:         150,
				ReservedPorts: []int{22, 80},
			},
		},
	}

	if !reflect.DeepEqual(expect.Networks, r1.Networks) {
		t.Fatalf("bad: %#v %#v", expect, r1)
	}
}

func TestResource_Add_Network(t *testing.T) {
	r1 := &Resources{}
	r2 := &Resources{
		Networks: []*NetworkResource{
			&NetworkResource{
				MBits:        50,
				DynamicPorts: []string{"http", "https"},
			},
		},
	}
	r3 := &Resources{
		Networks: []*NetworkResource{
			&NetworkResource{
				MBits:        25,
				DynamicPorts: []string{"admin"},
			},
		},
	}

	err := r1.Add(r2)
	if err != nil {
		t.Fatalf("Err: %v", err)
	}
	err = r1.Add(r3)
	if err != nil {
		t.Fatalf("Err: %v", err)
	}

	expect := &Resources{
		Networks: []*NetworkResource{
			&NetworkResource{
				MBits:        75,
				DynamicPorts: []string{"http", "https", "admin"},
			},
		},
	}

	if !reflect.DeepEqual(expect.Networks, r1.Networks) {
		t.Fatalf("bad: %#v %#v", expect.Networks[0], r1.Networks[0])
	}
}

func TestMapDynamicPorts(t *testing.T) {
	resources := &NetworkResource{
		ReservedPorts: []int{80, 443, 3306, 8080},
		DynamicPorts:  []string{"mysql", "admin"},
	}

	expected := map[string]int{
		"mysql": 3306,
		"admin": 8080,
	}
	actual := resources.MapDynamicPorts()

	if !reflect.DeepEqual(expected, actual) {
		t.Fatalf("Expected %#v; found %#v", expected, actual)
	}
}

func TestMapDynamicPortsEmpty(t *testing.T) {
	resources := &NetworkResource{
		ReservedPorts: []int{},
		DynamicPorts:  []string{},
	}

	expected := map[string]int{}
	actual := resources.MapDynamicPorts()

	if !reflect.DeepEqual(expected, actual) {
		t.Fatalf("Expected %#v; found %#v", expected, actual)
	}
}

func TestMapDynamicPortsStaticOnly(t *testing.T) {
	resources := &NetworkResource{
		ReservedPorts: []int{80, 443},
		DynamicPorts:  []string{},
	}

	expected := map[string]int{}
	actual := resources.MapDynamicPorts()

	if !reflect.DeepEqual(expected, actual) {
		t.Fatalf("Expected %#v; found %#v", expected, actual)
	}
}

func TestMapDynamicPortsOnly(t *testing.T) {
	resources := &NetworkResource{
		ReservedPorts: []int{3306, 8080},
		DynamicPorts:  []string{"mysql", "admin"},
	}

	expected := map[string]int{
		"mysql": 3306,
		"admin": 8080,
	}
	actual := resources.MapDynamicPorts()

	if !reflect.DeepEqual(expected, actual) {
		t.Fatalf("Expected %#v; found %#v", expected, actual)
	}
}

func TestListStaticPorts(t *testing.T) {
	resources := &NetworkResource{
		ReservedPorts: []int{80, 443, 3306, 8080},
		DynamicPorts:  []string{"mysql", "admin"},
	}

	expected := []int{80, 443}
	actual := resources.ListStaticPorts()

	if !reflect.DeepEqual(expected, actual) {
		t.Fatalf("Expected %#v; found %#v", expected, actual)
	}
}

func TestListStaticPortsEmpty(t *testing.T) {
	resources := &NetworkResource{
		ReservedPorts: []int{},
		DynamicPorts:  []string{},
	}

	expected := []int{}
	actual := resources.ListStaticPorts()

	if !reflect.DeepEqual(expected, actual) {
		t.Fatalf("Expected %#v; found %#v", expected, actual)
	}
}

func TestListStaticPortsOnly(t *testing.T) {
	resources := &NetworkResource{
		ReservedPorts: []int{80, 443},
		DynamicPorts:  []string{},
	}

	expected := []int{80, 443}
	actual := resources.ListStaticPorts()

	if !reflect.DeepEqual(expected, actual) {
		t.Fatalf("Expected %#v; found %#v", expected, actual)
	}
}

func TestListStaticPortsDynamicOnly(t *testing.T) {
	resources := &NetworkResource{
		ReservedPorts: []int{3306, 8080},
		DynamicPorts:  []string{"mysql", "admin"},
	}

	expected := []int{}
	actual := resources.ListStaticPorts()

	if !reflect.DeepEqual(expected, actual) {
		t.Fatalf("Expected %#v; found %#v", expected, actual)
	}
}

func TestEncodeDecode(t *testing.T) {
	type FooRequest struct {
		Foo string
		Bar int
		Baz bool
	}
	arg := &FooRequest{
		Foo: "test",
		Bar: 42,
		Baz: true,
	}
	buf, err := Encode(1, arg)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	var out FooRequest
	err = Decode(buf[1:], &out)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	if !reflect.DeepEqual(arg, &out) {
		t.Fatalf("bad: %#v %#v", arg, out)
	}
}
