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

package serf

import (
	"reflect"
	"sort"
	"testing"
	"time"
)

func TestMemberEventCoalesce_Basic(t *testing.T) {
	outCh := make(chan Event, 64)
	shutdownCh := make(chan struct{})
	defer close(shutdownCh)

	c := &memberEventCoalescer{
		lastEvents:   make(map[string]EventType),
		latestEvents: make(map[string]coalesceEvent),
	}

	inCh := coalescedEventCh(outCh, shutdownCh,
		5*time.Millisecond, 5*time.Millisecond, c)

	send := []Event{
		MemberEvent{
			Type:    EventMemberJoin,
			Members: []Member{Member{Name: "foo"}},
		},
		MemberEvent{
			Type:    EventMemberLeave,
			Members: []Member{Member{Name: "foo"}},
		},
		MemberEvent{
			Type:    EventMemberLeave,
			Members: []Member{Member{Name: "bar"}},
		},
		MemberEvent{
			Type:    EventMemberUpdate,
			Members: []Member{Member{Name: "zip", Tags: map[string]string{"role": "foo"}}},
		},
		MemberEvent{
			Type:    EventMemberUpdate,
			Members: []Member{Member{Name: "zip", Tags: map[string]string{"role": "bar"}}},
		},
		MemberEvent{
			Type:    EventMemberReap,
			Members: []Member{Member{Name: "dead"}},
		},
	}

	for _, e := range send {
		inCh <- e
	}

	events := make(map[EventType]Event)
	timeout := time.After(10 * time.Millisecond)

MEMBEREVENTFORLOOP:
	for {
		select {
		case e := <-outCh:
			events[e.EventType()] = e
		case <-timeout:
			break MEMBEREVENTFORLOOP
		}
	}

	if len(events) != 3 {
		t.Fatalf("bad: %#v", events)
	}

	if e, ok := events[EventMemberLeave]; !ok {
		t.Fatalf("bad: %#v", events)
	} else {
		me := e.(MemberEvent)

		if len(me.Members) != 2 {
			t.Fatalf("bad: %#v", me)
		}

		expected := []string{"bar", "foo"}
		names := []string{me.Members[0].Name, me.Members[1].Name}
		sort.Strings(names)

		if !reflect.DeepEqual(expected, names) {
			t.Fatalf("bad: %#v", names)
		}
	}

	if e, ok := events[EventMemberUpdate]; !ok {
		t.Fatalf("bad: %#v", events)
	} else {
		me := e.(MemberEvent)
		if len(me.Members) != 1 {
			t.Fatalf("bad: %#v", me)
		}

		if me.Members[0].Name != "zip" {
			t.Fatalf("bad: %#v", me.Members)
		}
		if me.Members[0].Tags["role"] != "bar" {
			t.Fatalf("bad: %#v", me.Members[0].Tags)
		}
	}

	if e, ok := events[EventMemberReap]; !ok {
		t.Fatalf("bad: %#v", events)
	} else {
		me := e.(MemberEvent)
		if len(me.Members) != 1 {
			t.Fatalf("bad: %#v", me)
		}

		if me.Members[0].Name != "dead" {
			t.Fatalf("bad: %#v", me.Members)
		}
	}
}

func TestMemberEventCoalesce_TagUpdate(t *testing.T) {
	outCh := make(chan Event, 64)
	shutdownCh := make(chan struct{})
	defer close(shutdownCh)

	c := &memberEventCoalescer{
		lastEvents:   make(map[string]EventType),
		latestEvents: make(map[string]coalesceEvent),
	}

	inCh := coalescedEventCh(outCh, shutdownCh,
		5*time.Millisecond, 5*time.Millisecond, c)

	inCh <- MemberEvent{
		Type:    EventMemberUpdate,
		Members: []Member{Member{Name: "foo", Tags: map[string]string{"role": "foo"}}},
	}

	time.Sleep(30 * time.Millisecond)

	select {
	case e := <-outCh:
		if e.EventType() != EventMemberUpdate {
			t.Fatalf("expected update")
		}
	default:
		t.Fatalf("expected update")
	}

	// Second update should not be suppressed even though
	// last event was an update
	inCh <- MemberEvent{
		Type:    EventMemberUpdate,
		Members: []Member{Member{Name: "foo", Tags: map[string]string{"role": "bar"}}},
	}
	time.Sleep(10 * time.Millisecond)

	select {
	case e := <-outCh:
		if e.EventType() != EventMemberUpdate {
			t.Fatalf("expected update")
		}
	default:
		t.Fatalf("expected update")
	}
}

func TestMemberEventCoalesce_passThrough(t *testing.T) {
	cases := []struct {
		e      Event
		handle bool
	}{
		{UserEvent{}, false},
		{MemberEvent{Type: EventMemberJoin}, true},
		{MemberEvent{Type: EventMemberLeave}, true},
		{MemberEvent{Type: EventMemberFailed}, true},
		{MemberEvent{Type: EventMemberUpdate}, true},
		{MemberEvent{Type: EventMemberReap}, true},
	}

	for _, tc := range cases {
		c := &memberEventCoalescer{}
		if tc.handle != c.Handle(tc.e) {
			t.Fatalf("bad: %#v", tc.e)
		}
	}
}
