package suricata

import (
	"fmt"
	"log"
	"math/rand"
	"net"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/testutil"
)

var ex2 = `{"timestamp":"2017-03-06T07:43:39.000397+0000","event_type":"stats","stats":` +
	`{"capture":{"kernel_packets":905344474,"kernel_drops":78355440,"kernel_packets_delta":2376742,"kernel_drops_delta":82049}}}`
var ex3 = `{"timestamp":"2017-03-06T07:43:39.000397+0000","event_type":"stats","stats":` +
	`{"threads": { "W#05-wlp4s0": { "capture":{"kernel_packets":905344474,"kernel_drops":78355440}}}}}`

func TestSuricataLarge(t *testing.T) {
	dir := t.TempDir()
	tmpfn := filepath.Join(dir, fmt.Sprintf("t%d", rand.Int63()))

	s := Suricata{
		Source:    tmpfn,
		Delimiter: ".",
		Alerts:    true,
		Log: testutil.Logger{
			Name: "inputs.suricata",
		},
	}
	acc := testutil.Accumulator{}
	require.NoError(t, s.Start(&acc))
	defer s.Stop()

	data, err := os.ReadFile("testdata/test1.json")
	require.NoError(t, err)

	c, err := net.Dial("unix", tmpfn)
	require.NoError(t, err)
	_, err = c.Write(data)
	require.NoError(t, err)
	_, err = c.Write([]byte("\n"))
	require.NoError(t, err)

	// test suricata alerts
	data2, err := os.ReadFile("testdata/test2.json")
	require.NoError(t, err)
	_, err = c.Write(data2)
	require.NoError(t, err)
	_, err = c.Write([]byte("\n"))
	require.NoError(t, err)
	require.NoError(t, c.Close())

	acc.Wait(1)
}

func TestSuricataAlerts(t *testing.T) {
	dir := t.TempDir()
	tmpfn := filepath.Join(dir, fmt.Sprintf("t%d", rand.Int63()))

	s := Suricata{
		Source:    tmpfn,
		Delimiter: ".",
		Alerts:    true,
		Log: testutil.Logger{
			Name: "inputs.suricata",
		},
	}
	acc := testutil.Accumulator{}
	require.NoError(t, s.Start(&acc))
	defer s.Stop()

	data, err := os.ReadFile("testdata/test3.json")
	require.NoError(t, err)

	c, err := net.Dial("unix", tmpfn)
	require.NoError(t, err)
	_, err = c.Write(data)
	require.NoError(t, err)
	_, err = c.Write([]byte("\n"))
	require.NoError(t, err)
	require.NoError(t, c.Close())

	acc.Wait(1)

	expected := []telegraf.Metric{
		testutil.MustMetric(
			"suricata_alert",
			map[string]string{},
			map[string]interface{}{
				"action":       "allowed",
				"category":     "Misc activity",
				"gid":          float64(1),
				"rev":          float64(0),
				"signature":    "Corrupted HTTP body",
				"signature_id": float64(6),
				"severity":     float64(3),
				"source.ip":    "10.0.0.5",
				"target.ip":    "179.60.192.3",
				"source.port":  float64(18715),
				"target.port":  float64(80),
			},
			time.Unix(0, 0),
		),
	}

	testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}

func TestSuricata(t *testing.T) {
	dir := t.TempDir()
	tmpfn := filepath.Join(dir, fmt.Sprintf("t%d", rand.Int63()))

	s := Suricata{
		Source:    tmpfn,
		Delimiter: ".",
		Log: testutil.Logger{
			Name: "inputs.suricata",
		},
	}
	acc := testutil.Accumulator{}
	require.NoError(t, s.Start(&acc))
	defer s.Stop()

	c, err := net.Dial("unix", tmpfn)
	require.NoError(t, err)
	_, err = c.Write([]byte(ex2))
	require.NoError(t, err)
	_, err = c.Write([]byte("\n"))
	require.NoError(t, err)
	require.NoError(t, c.Close())

	acc.Wait(1)

	expected := []telegraf.Metric{
		testutil.MustMetric(
			"suricata",
			map[string]string{
				"thread": "total",
			},
			map[string]interface{}{
				"capture.kernel_packets":       float64(905344474),
				"capture.kernel_drops":         float64(78355440),
				"capture.kernel_packets_delta": float64(2376742),
				"capture.kernel_drops_delta":   float64(82049),
			},
			time.Unix(0, 0),
		),
	}

	testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}

func TestThreadStats(t *testing.T) {
	dir := t.TempDir()
	tmpfn := filepath.Join(dir, fmt.Sprintf("t%d", rand.Int63()))

	s := Suricata{
		Source:    tmpfn,
		Delimiter: ".",
		Log: testutil.Logger{
			Name: "inputs.suricata",
		},
	}

	acc := testutil.Accumulator{}
	require.NoError(t, s.Start(&acc))
	defer s.Stop()

	c, err := net.Dial("unix", tmpfn)
	require.NoError(t, err)
	_, err = c.Write([]byte(""))
	require.NoError(t, err)
	_, err = c.Write([]byte("\n"))
	require.NoError(t, err)
	_, err = c.Write([]byte("foobard}\n"))
	require.NoError(t, err)
	_, err = c.Write([]byte(ex3))
	require.NoError(t, err)
	_, err = c.Write([]byte("\n"))
	require.NoError(t, err)
	require.NoError(t, c.Close())
	acc.Wait(2)

	expected := []telegraf.Metric{
		testutil.MustMetric(
			"suricata",
			map[string]string{
				"thread": "W#05-wlp4s0",
			},
			map[string]interface{}{
				"capture.kernel_packets": float64(905344474),
				"capture.kernel_drops":   float64(78355440),
			},
			time.Unix(0, 0),
		),
	}

	testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}

func TestSuricataInvalid(t *testing.T) {
	dir := t.TempDir()
	tmpfn := filepath.Join(dir, fmt.Sprintf("t%d", rand.Int63()))

	s := Suricata{
		Source: tmpfn,
		Log: testutil.Logger{
			Name: "inputs.suricata",
		},
	}
	acc := testutil.Accumulator{}
	acc.SetDebug(true)

	require.NoError(t, s.Start(&acc))
	defer s.Stop()

	c, err := net.Dial("unix", tmpfn)
	require.NoError(t, err)
	_, err = c.Write([]byte("sfjiowef"))
	require.NoError(t, err)
	_, err = c.Write([]byte("\n"))
	require.NoError(t, err)
	require.NoError(t, c.Close())

	acc.WaitError(1)
}

func TestSuricataInvalidPath(t *testing.T) {
	tmpfn := fmt.Sprintf("/t%d/X", rand.Int63())
	s := Suricata{
		Source: tmpfn,
		Log: testutil.Logger{
			Name: "inputs.suricata",
		},
	}

	acc := testutil.Accumulator{}
	require.Error(t, s.Start(&acc))
}

func TestSuricataTooLongLine(t *testing.T) {
	tmpfn := testutil.TempSocket(t)

	s := Suricata{
		Source: tmpfn,
		Log: testutil.Logger{
			Name: "inputs.suricata",
		},
	}
	acc := testutil.Accumulator{}

	require.NoError(t, s.Start(&acc))
	defer s.Stop()

	c, err := net.Dial("unix", tmpfn)
	require.NoError(t, err)
	_, err = c.Write([]byte(strings.Repeat("X", 20000000)))
	require.NoError(t, err)
	_, err = c.Write([]byte("\n"))
	require.NoError(t, err)
	require.NoError(t, c.Close())

	acc.WaitError(1)
}

func TestSuricataEmptyJSON(t *testing.T) {
	tmpfn := testutil.TempSocket(t)

	s := Suricata{
		Source: tmpfn,
		Log: testutil.Logger{
			Name: "inputs.suricata",
		},
	}
	acc := testutil.Accumulator{}
	require.NoError(t, s.Start(&acc))
	defer s.Stop()

	c, err := net.Dial("unix", tmpfn)
	if err != nil {
		log.Println(err)
	}
	_, err = c.Write([]byte("\n"))
	require.NoError(t, err)
	require.NoError(t, c.Close())

	acc.WaitError(1)
}

func TestSuricataDisconnectSocket(t *testing.T) {
	tmpfn := testutil.TempSocket(t)

	s := Suricata{
		Source: tmpfn,
		Log: testutil.Logger{
			Name: "inputs.suricata",
		},
	}
	acc := testutil.Accumulator{}

	require.NoError(t, s.Start(&acc))
	defer s.Stop()

	c, err := net.Dial("unix", tmpfn)
	require.NoError(t, err)
	_, err = c.Write([]byte(ex2))
	require.NoError(t, err)
	_, err = c.Write([]byte("\n"))
	require.NoError(t, err)
	require.NoError(t, c.Close())

	c, err = net.Dial("unix", tmpfn)
	require.NoError(t, err)
	_, err = c.Write([]byte(ex3))
	require.NoError(t, err)
	_, err = c.Write([]byte("\n"))
	require.NoError(t, err)
	require.NoError(t, c.Close())

	acc.Wait(2)
}

func TestSuricataStartStop(t *testing.T) {
	tmpfn := testutil.TempSocket(t)

	s := Suricata{
		Source: tmpfn,
		Log: testutil.Logger{
			Name: "inputs.suricata",
		},
	}
	acc := testutil.Accumulator{}
	require.NoError(t, s.Start(&acc))
	s.Stop()
}

func TestSuricataParse(t *testing.T) {
	tests := []struct {
		filename string
		expected []telegraf.Metric
	}{{
		filename: "test2.json",
		expected: []telegraf.Metric{
			testutil.MustMetric(
				"suricata",
				map[string]string{
					"thread": "W#01-ens2f1",
				},
				map[string]interface{}{
					"detect_alert":                float64(0),
					"detect_engines_id":           float64(0),
					"detect_engines_last_reload":  "2021-06-08T06:33:05.084872+0000",
					"detect_engines_rules_failed": float64(0),
					"detect_engines_rules_loaded": float64(22712),
				},
				time.Unix(0, 0),
			),
		},
	},
	}

	for _, tc := range tests {
		data, err := os.ReadFile("testdata/" + tc.filename)
		require.NoError(t, err)

		s := Suricata{
			Delimiter: "_",
		}
		acc := testutil.Accumulator{}
		err = s.parse(&acc, data)
		require.NoError(t, err)

		testutil.RequireMetricsEqual(t, tc.expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
	}
}

func TestSuricataParseVersion2(t *testing.T) {
	tests := []struct {
		filename string
		expected []telegraf.Metric
	}{
		{
			filename: "v2/alert.json",
			expected: []telegraf.Metric{
				testutil.MustMetric(
					"suricata",
					map[string]string{
						"event_type": "alert",
						"in_iface":   "s1-suricata",
						"proto":      "TCP",
					},
					map[string]interface{}{
						"action":       "allowed",
						"category":     "Misc activity",
						"dest_ip":      "179.60.192.3",
						"dest_port":    int64(80),
						"gid":          float64(1),
						"rev":          float64(0),
						"severity":     float64(3),
						"signature":    "Corrupted HTTP body",
						"signature_id": float64(6),
						"sourceip":     "10.0.0.5",
						"sourceport":   float64(18715),
						"src_ip":       "10.0.0.5",
						"src_port":     int64(18715),
						"targetip":     "179.60.192.3",
						"targetport":   float64(80),
					},
					time.Unix(0, 0),
				),
			},
		},
		{
			filename: "v2/dns.json",
			expected: []telegraf.Metric{
				testutil.MustMetric(
					"suricata",
					map[string]string{
						"event_type": "dns",
						"in_iface":   "eth1",
						"proto":      "UDP",
					},
					map[string]interface{}{
						"dest_ip":   "192.168.0.1",
						"dest_port": int64(53),
						"id":        float64(7145),
						"rrname":    "reddit.com",
						"rrtype":    "A",
						"src_ip":    "192.168.0.100",
						"type":      "query",
						"src_port":  int64(39262),
						"tx_id":     float64(10),
					},
					time.Unix(0, 0),
				),
			},
		},
		{
			filename: "v2/drop.json",
			expected: []telegraf.Metric{
				testutil.MustMetric(
					"suricata",
					map[string]string{
						"event_type": "drop",
						"in_iface":   "eth1",
						"proto":      "TCP",
					},
					map[string]interface{}{
						"dest_ip":   "54.192.18.125",
						"dest_port": int64(443),
						"ipid":      float64(62316),
						"len":       float64(76),
						"reason":    "stream error",
						"src_ip":    "192.168.0.110",
						"src_port":  int64(46016),
						"tcpack":    float64(2339873683),
						"tcpres":    float64(0),
						"tcpseq":    float64(3900248957),
						"tcpurgp":   float64(0),
						"tcpwin":    float64(501),
						"tos":       float64(0),
						"ttl":       float64(64),
					},
					time.Unix(0, 0),
				),
			},
		},
		{
			filename: "v2/flow.json",
			expected: []telegraf.Metric{
				testutil.MustMetric(
					"suricata",
					map[string]string{
						"event_type": "flow",
						"in_iface":   "eth1",
						"proto":      "TCP",
					},
					map[string]interface{}{
						"age":       float64(0),
						"dest_ip":   "142.251.130.3",
						"dest_port": int64(443),
						"src_ip":    "192.168.0.121",
						"src_port":  int64(50212),
						"state":     "new",
					},
					time.Unix(0, 0),
				),
			},
		},
		{
			filename: "v2/http.json",
			expected: []telegraf.Metric{
				testutil.MustMetric(
					"suricata",
					map[string]string{
						"event_type": "http",
						"in_iface":   "eth2",
						"proto":      "TCP",
					},
					map[string]interface{}{
						"dest_ip":           "203.205.239.179",
						"dest_port":         int64(80),
						"hostname":          "hkminorshort.weixin.qq.com",
						"http_content_type": "application/octet-stream",
						"http_method":       "POST",
						"http_user_agent":   "MicroMessenger Client",
						"length":            float64(245),
						"protocol":          "HTTP/1.1",
						"src_ip":            "192.168.0.120",
						"src_port":          int64(33950),
						"status":            float64(200),
						"url":               "/mmtls/2d6d45f1",
					},
					time.Unix(0, 0),
				),
			},
		},
		{
			filename: "v2/status.json",
			expected: []telegraf.Metric{
				testutil.MustMetric(
					"suricata",
					map[string]string{
						"event_type": "stats",
					},
					map[string]interface{}{
						"captureerrors":          float64(0),
						"capturekernel_drops":    float64(0),
						"capturekernel_packets":  float64(522),
						"flowemerg_mode_entered": float64(0),
						"flowemerg_mode_over":    float64(0),
						"flowmemcap":             float64(0),
						"flowmemuse":             float64(9965056),
						"flowmgrclosed_pruned":   float64(0),
						"flowmgrfull_hash_pass":  float64(1),
						"flowmgrnew_pruned":      float64(0),
						"flowspare":              float64(10100),
						"flowtcp":                float64(15),
						"flowudp":                float64(13),
						"flowwrkspare_sync":      float64(11),
						"flowwrkspare_sync_avg":  float64(100),
						"uptime":                 float64(160),
					},
					time.Unix(0, 0),
				),
			},
		},
	}

	for _, tc := range tests {
		data, err := os.ReadFile("testdata/" + tc.filename)
		require.NoError(t, err)

		s := Suricata{
			Version: "2",
			Log:     testutil.Logger{},
		}
		acc := testutil.Accumulator{}
		require.NoError(t, s.parse(&acc, data))

		testutil.RequireMetricsEqual(t, tc.expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
	}
}
