package logrus_logstash

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net"
	"reflect"
	"testing"
	"time"

	"github.com/Sirupsen/logrus"
)

func TestLegostashHook(t *testing.T) {
	type Expct struct {
		appName          string
		hookOnlyPrefix   string
		alwaysSentFields logrus.Fields
	}
	tt := []struct {
		expected Expct
		initFunc func() (*Hook, error)
	}{
		{Expct{"bla", "", nil}, func() (*Hook, error) {
			return NewHook("udp", "localhost:9999", "bla")
		}},
		{Expct{"bzz", "", nil}, func() (*Hook, error) {
			udpConn, err := net.Dial("udp", "localhost:9999")
			if err != nil {
				return nil, err
			}
			return NewHookWithConn(udpConn, "bzz")
		}},
		{Expct{"blk", "", logrus.Fields{"id": 1}}, func() (*Hook, error) {
			return NewHookWithFields("udp", "localhost:9999", "blk", logrus.Fields{"id": 1})
		}},
		{Expct{"prefix", "-->", logrus.Fields{"id": 1}}, func() (*Hook, error) {
			return NewHookWithFieldsAndPrefix("udp", "localhost:9999", "prefix", logrus.Fields{"id": 1}, "-->")
		}},
		{Expct{"fieldsconn", "", logrus.Fields{"id": 5}}, func() (*Hook, error) {
			udpConn, err := net.Dial("udp", "localhost:9999")
			if err != nil {
				return nil, err
			}
			return NewHookWithFieldsAndConn(udpConn, "fieldsconn", logrus.Fields{"id": 5})
		}},
		{Expct{"zz", "~~>", logrus.Fields{"id": "bal"}}, func() (*Hook, error) {
			udpConn, err := net.Dial("udp", "localhost:9999")
			if err != nil {
				return nil, err
			}
			return NewHookWithFieldsAndConnAndPrefix(udpConn, "zz", logrus.Fields{"id": "bal"}, "~~>")
		}},
	}

	for _, te := range tt {
		h, err := te.initFunc()
		if err != nil {
			t.Error(err)
		}
		if h == nil {
			t.Error("expected hook to be not nil")
		}

		if h.conn == nil {
			t.Error("expected conn to be not nil")
		}
		if h.appName != te.expected.appName {
			t.Errorf("expected appName to be '%s' but got '%s'", te.expected.appName, h.appName)
		}
		if h.alwaysSentFields == nil {
			t.Error("expected alwaysSentFields to be not nil")
		}
		if te.expected.alwaysSentFields != nil && !reflect.DeepEqual(te.expected.alwaysSentFields, h.alwaysSentFields) {
			t.Errorf("expected alwaysSentFields to be '%v' but got '%v'", te.expected.alwaysSentFields, h.alwaysSentFields)
		}
		if h.hookOnlyPrefix != te.expected.hookOnlyPrefix {
			t.Error("expected hookOnlyPrefix to be an empty string")
		}
	}
}

func TestNewFiltering(t *testing.T) {
	type Expct struct {
		prefix  string
		appName string
	}
	tt := []struct {
		expected Expct
		initFunc func() *Hook
	}{
		{Expct{"", ""}, func() *Hook {
			return NewFilterHook()
		}},
		{Expct{"~~~>", ""}, func() *Hook {
			return NewFilterHookWithPrefix("~~~>")
		}},
	}

	for _, te := range tt {
		h := te.initFunc()
		if h.conn != nil {
			t.Error("expected conn to be nil")
		}
		if h.alwaysSentFields == nil {
			t.Error("expected alwaysSentFields to be not nil")
		}
		if h.hookOnlyPrefix != te.expected.prefix {
			t.Errorf("expected prefix to be '%s' but got '%s'", te.expected.prefix, h.hookOnlyPrefix)
		}
	}
}

func TestSettingAttributes(t *testing.T) {
	tt := []struct {
		setFunc   func(*Hook)
		expctFunc func(*Hook) error
	}{
		{func(h *Hook) {
			h.WithPrefix("mprefix1")
		}, func(h *Hook) error {
			if h.hookOnlyPrefix != "mprefix1" {
				return fmt.Errorf("expected hookOnlyPrefix to be '%s' but got '%s'", "mprefix1", h.hookOnlyPrefix)
			}
			return nil
		}},
		{func(h *Hook) {
			h.WithField("name", "muha")
		}, func(h *Hook) error {
			nField := logrus.Fields{"name": "muha"}
			if !reflect.DeepEqual(h.alwaysSentFields, nField) {
				return fmt.Errorf("expected hookOnlyPrefix to be '%s' but got '%s'", nField, h.hookOnlyPrefix)
			}
			return nil
		}},
		{func(h *Hook) {
			h.WithFields(logrus.Fields{"filename": "app.log", "owner": "mick"})
		}, func(h *Hook) error {
			nField := logrus.Fields{"name": "test-me!", "filename": "app.log", "owner": "mick"}
			if !reflect.DeepEqual(h.alwaysSentFields, nField) {
				return fmt.Errorf("expected hookOnlyPrefix to be '%s' but got '%s'", nField, h.hookOnlyPrefix)
			}
			return nil
		}},
	}

	for _, te := range tt {
		hook := NewFilterHook()
		hook.alwaysSentFields = logrus.Fields{"name": "test-me!"}
		te.setFunc(hook)
		if err := te.expctFunc(hook); err != nil {
			t.Error(err)
		}
	}
}

func TestFilterHookOnly(t *testing.T) {
	tt := []struct {
		entry    *logrus.Entry
		prefix   string
		expected logrus.Fields
	}{
		{&logrus.Entry{Data: logrus.Fields{"name": "slimshady"}}, "", logrus.Fields{"name": "slimshady"}},
		{&logrus.Entry{Data: logrus.Fields{"_name": "slimshady", "nick": "blabla"}}, "_", logrus.Fields{"nick": "blabla"}},
	}

	for _, te := range tt {
		hook := NewFilterHookWithPrefix(te.prefix)
		hook.filterHookOnly(te.entry)
		if !reflect.DeepEqual(te.entry.Data, te.expected) {
			t.Errorf("expected entry data to be '%v' but got '%v'", te.expected, te.entry.Data)
		}
	}
}

type AddrMock struct {
}

func (a AddrMock) Network() string {
	return ""
}

func (a AddrMock) String() string {
	return ""
}

type ConnMock struct {
	buff *bytes.Buffer
}

func (c ConnMock) Read(b []byte) (int, error) {
	return c.buff.Read(b)
}

func (c ConnMock) Write(b []byte) (int, error) {
	return c.buff.Write(b)
}

func (c ConnMock) Close() error {
	return nil
}

func (c ConnMock) LocalAddr() net.Addr {
	return AddrMock{}
}

func (c ConnMock) RemoteAddr() net.Addr {
	return AddrMock{}
}

func (c ConnMock) SetDeadline(t time.Time) error {
	return nil
}

func (c ConnMock) SetReadDeadline(t time.Time) error {
	return nil
}

func (c ConnMock) SetWriteDeadline(t time.Time) error {
	return nil
}

func TestFire(t *testing.T) {
	conn := ConnMock{buff: bytes.NewBufferString("")}
	hook := &Hook{
		conn:             conn,
		appName:          "fire_test",
		alwaysSentFields: logrus.Fields{"test-name": "fire-test", "->ignore": "haaa", "override": "no"},
		hookOnlyPrefix:   "->",
	}
	entry := &logrus.Entry{
		Message: "hello world!",
		Data:    logrus.Fields{"override": "yes"},
		Level:   logrus.DebugLevel,
	}
	if err := hook.Fire(entry); err != nil {
		t.Error(err)
	}
	var res map[string]string
	if err := json.NewDecoder(conn.buff).Decode(&res); err != nil {
		t.Error(err)
	}
	expected := map[string]string{
		"@timestamp": "0001-01-01 00:00:00.000",
		"@version":   "1",
		"ignore":     "haaa",
		"level":      "debug",
		"message":    "hello world!",
		"override":   "yes",
		"test-name":  "fire-test",
		"type":       "fire_test",
	}
	if !reflect.DeepEqual(expected, res) {
		t.Errorf("expected message to be '%v' but got '%v'", expected, res)
	}
}

func TestFireFilterHook(t *testing.T) {
	hook := &Hook{
		appName:          "fire_hook_test",
		alwaysSentFields: logrus.Fields{"test-name": "fire-test-hook", "_ignore": "haaa", "override": "no"},
		hookOnlyPrefix:   "_",
	}
	entry := &logrus.Entry{
		Message: "hello world!",
		Data:    logrus.Fields{"override": "yes"},
		Level:   logrus.DebugLevel,
	}
	if err := hook.Fire(entry); err != nil {
		t.Error(err)
	}
	expected := &logrus.Entry{
		Message: "hello world!",
		Data:    logrus.Fields{"test-name": "fire-test-hook", "override": "yes"},
		Level:   logrus.DebugLevel,
	}

	if !reflect.DeepEqual(expected, entry) {
		t.Errorf("expected message to be '%v' but got '%v'", expected, entry)
	}
}

func TestLevels(t *testing.T) {
	hook := &Hook{}
	expected := []logrus.Level{
		logrus.PanicLevel,
		logrus.FatalLevel,
		logrus.ErrorLevel,
		logrus.WarnLevel,
		logrus.InfoLevel,
		logrus.DebugLevel,
	}
	res := hook.Levels()
	if !reflect.DeepEqual(expected, res) {
		t.Errorf("expected levels to be '%v' but got '%v'", expected, res)
	}

}
