// Package testutil can be .-imported to gain access to useful test functions.
package testutil

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	simplejson "github.com/bitly/go-simplejson"
)

// TestAPIKey is a fake API key that can be used for testing
const TestAPIKey = "166f5ad3590596f9aa8d601ea89af845"

// Setup sets up and returns a test event server for receiving the event payloads.
// report payloads published to the returned server's URL will be put on the returned channel
func Setup() (*httptest.Server, chan []byte) {
	reports := make(chan []byte, 10)
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if strings.Contains(r.URL.Path, "sessions") {
			return
		}
		body, _ := ioutil.ReadAll(r.Body)
		reports <- body
	})), reports
}

// Get travels through a JSON object and returns the specified node
func Get(j *simplejson.Json, path string) *simplejson.Json {
	return j.GetPath(strings.Split(path, ".")...)
}

// GetIndex returns the n-th element of the specified path inside the given JSON object
func GetIndex(j *simplejson.Json, path string, n int) *simplejson.Json {
	return Get(j, path).GetIndex(n)
}

func getBool(j *simplejson.Json, path string) bool {
	return Get(j, path).MustBool()
}
func getInt(j *simplejson.Json, path string) int {
	return Get(j, path).MustInt()
}
func getString(j *simplejson.Json, path string) string {
	return Get(j, path).MustString()
}

// AssertPayload compares the payload that was received by the event-server to
// the expected report JSON payload
func AssertPayload(t *testing.T, report *simplejson.Json, expPretty string) {
	expReport, err := simplejson.NewJson([]byte(expPretty))
	if err != nil {
		t.Fatal(err)
	}
	expEvent := GetIndex(expReport, "events", 0)
	expException := GetIndex(expEvent, "exceptions", 0)

	event := GetIndex(report, "events", 0)
	exception := GetIndex(event, "exceptions", 0)

	if exp, got := getBool(expEvent, "unhandled"), getBool(event, "unhandled"); got != exp {
		t.Errorf("expected 'unhandled' to be '%v' but got '%v'", exp, got)
	}
	for _, tc := range []struct {
		prop     string
		got, exp *simplejson.Json
	}{
		{got: report, exp: expReport, prop: "apiKey"},
		{got: report, exp: expReport, prop: "notifier.name"},
		{got: report, exp: expReport, prop: "notifier.version"},
		{got: report, exp: expReport, prop: "notifier.url"},
		{got: exception, exp: expException, prop: "message"},
		{got: exception, exp: expException, prop: "errorClass"},
		{got: event, exp: expEvent, prop: "user.id"},
		{got: event, exp: expEvent, prop: "severity"},
		{got: event, exp: expEvent, prop: "severityReason.type"},
		{got: event, exp: expEvent, prop: "metaData.request.httpMethod"},
		{got: event, exp: expEvent, prop: "metaData.request.url"},
		{got: event, exp: expEvent, prop: "request.httpMethod"},
		{got: event, exp: expEvent, prop: "request.url"},
		{got: event, exp: expEvent, prop: "request.referer"},
		{got: event, exp: expEvent, prop: "request.headers.Accept-Encoding"},
	} {
		if got, exp := getString(tc.got, tc.prop), getString(tc.exp, tc.prop); got != exp {
			t.Errorf("expected '%s' to be '%s' but was '%s'", tc.prop, exp, got)
		}
	}
	assertValidSession(t, event, getBool(expEvent, "unhandled"))
}

func assertValidSession(t *testing.T, event *simplejson.Json, unhandled bool) {
	if sessionID := getString(event, "session.id"); len(sessionID) != 36 {
		t.Errorf("Expected a valid session ID to be set but was '%s'", sessionID)
	}
	if _, e := time.Parse(time.RFC3339, getString(event, "session.startedAt")); e != nil {
		t.Error(e)
	}
	expHandled, expUnhandled := 1, 0
	if unhandled {
		expHandled, expUnhandled = expUnhandled, expHandled
	}
	if got := getInt(event, "session.events.unhandled"); got != expUnhandled {
		t.Errorf("Expected %d unhandled events in session but was %d", expUnhandled, got)
	}
	if got := getInt(event, "session.events.handled"); got != expHandled {
		t.Errorf("Expected %d handled events in session but was %d", expHandled, got)
	}
}
