package analytics

import (
	"context"
	"sync"
	"testing"
	"time"

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

type dummySink struct {
	events []*Events
	err    error
	mu     sync.Mutex
	ch     chan struct{}
}

type evt struct {
	I int
}

func newDummySink() *dummySink {
	return &dummySink{
		events: []*Events{},
		ch:     make(chan struct{}, 1),
	}
}

func (d *dummySink) RecordAnalyticsEvents(_ context.Context, events Events) error {
	d.mu.Lock()
	defer d.mu.Unlock()
	// Make a copy in case a test is holding a copy too
	eventsCopy := make([]*Events, len(d.events))
	copy(eventsCopy, d.events)
	d.events = append(eventsCopy, &events)
	d.ch <- struct{}{}
	return d.err
}

func (d *dummySink) Events() []*Events {
	d.mu.Lock()
	defer d.mu.Unlock()
	return d.events
}

func (d *dummySink) ExpectImmediateMessage(t *testing.T) {
	select {
	case <-time.After(150 * time.Millisecond):
		t.Errorf("expected to not wait out the flush timeout")
	case <-d.ch:
	}
}

func (d *dummySink) ExpectTimeoutThenMessage(t *testing.T) {
	select {
	case <-d.ch:
		t.Errorf("Expected to wait out the flush timeout")
	case <-time.After(150 * time.Millisecond):
	}
	<-d.ch
}

func Test_batching(t *testing.T) {
	d := newDummySink()
	ctx := context.Background()
	c := NewClient(ctx, d, hclog.Default())
	for i := 0; i < 2; i++ {
		c.LogEvent(&evt{i})
	}
	found := d.Events()
	if len(found) != 0 {
		t.Errorf("got %v events, want 0 due to batching", len(found))
	}
	// Should timeout
	d.ExpectTimeoutThenMessage(t)
	found = d.Events()
	if len(found) != 1 {
		t.Errorf("got %v, want 1 batch to have been flushed", len(found))
	}
	payloads := *found[0]
	if len(payloads) != 2 {
		t.Errorf("got %v, want 2 payloads to have been flushed", len(payloads))
	}
}

func Test_batchingAcrossTwoBatches(t *testing.T) {
	d := newDummySink()
	ctx := context.Background()
	c := NewClient(ctx, d, hclog.Default())
	for i := 0; i < 12; i++ {
		c.LogEvent(&evt{i})
	}
	// We sent more than the batch size, expect a message immediately
	d.ExpectImmediateMessage(t)
	found := d.Events()
	if len(found) != 1 {
		t.Errorf("got %v, want 1 batch to have been flushed", len(found))
	}
	payloads := *found[0]
	if len(payloads) != 10 {
		t.Errorf("got %v, want 10 payloads to have been flushed", len(payloads))
	}
	// Should timeout second batch
	d.ExpectTimeoutThenMessage(t)
	found = d.Events()
	if len(found) != 2 {
		t.Errorf("got %v, want 2 batches to have been flushed", len(found))
	}
	payloads = *found[1]
	if len(payloads) != 2 {
		t.Errorf("got %v, want 2 payloads to have been flushed", len(payloads))
	}
}

func Test_closing(t *testing.T) {
	d := newDummySink()
	ctx := context.Background()
	c := NewClient(ctx, d, hclog.Default())
	for i := 0; i < 2; i++ {
		c.LogEvent(&evt{i})
	}
	found := d.Events()
	if len(found) != 0 {
		t.Errorf("got %v events, want 0 due to batching", len(found))
	}
	c.Close()
	found = d.Events()
	if len(found) != 1 {
		t.Errorf("got %v, want 1 batch to have been flushed", len(found))
	}
	payloads := *found[0]
	if len(payloads) != 2 {
		t.Errorf("got %v, want 2 payloads to have been flushed", len(payloads))
	}
}

func Test_closingByContext(t *testing.T) {
	d := newDummySink()
	ctx, cancel := context.WithCancel(context.Background())
	c := NewClient(ctx, d, hclog.Default())
	for i := 0; i < 2; i++ {
		c.LogEvent(&evt{i})
	}
	found := d.Events()
	if len(found) != 0 {
		t.Errorf("got %v events, want 0 due to batching", len(found))
	}
	cancel()
	d.ExpectImmediateMessage(t)
	found = d.Events()
	if len(found) != 1 {
		t.Errorf("got %v, want 1 batch to have been flushed", len(found))
	}
	payloads := *found[0]
	if len(payloads) != 2 {
		t.Errorf("got %v, want 2 payloads to have been flushed", len(payloads))
	}
}

func Test_addSessionId(t *testing.T) {
	events := []struct {
		Foo string `mapstructure:"foo"`
	}{
		{
			Foo: "foo1",
		},
		{
			Foo: "foo2",
		},
	}
	arr := make([]interface{}, len(events))
	for i, event := range events {
		arr[i] = event
	}
	sessionID := "my-uuid"
	output, err := addSessionID(sessionID, arr)
	if err != nil {
		t.Errorf("failed to encode analytics events: %v", err)
	}
	if len(output) != 2 {
		t.Errorf("len output got %v, want 2", len(output))
	}
	if output[0]["foo"] != "foo1" {
		t.Errorf("first event foo got %v, want foo1", output[0]["foo"])
	}
	for i, event := range output {
		if event["sessionId"] != "my-uuid" {
			t.Errorf("event %v sessionId got %v, want %v", i, event["sessionId"], sessionID)
		}
	}
}
