package main

import (
	"fmt"
	"testing"
	"time"
)

func TestCheckOldestEntry(t *testing.T) {
	for name, tc := range map[string]struct {
		logs           []string
		oldest         string
		expectedLogs   []string
		expectedOldest string
		expectedAllNew bool
	}{
		"nil logs": {
			logs:           nil,
			oldest:         "oldest",
			expectedLogs:   nil,
			expectedOldest: "oldest",
			expectedAllNew: false,
		},
		"empty logs": {
			logs:           []string{},
			oldest:         "oldest",
			expectedLogs:   nil,
			expectedOldest: "oldest",
			expectedAllNew: false,
		},
		"no new entries, one old entry": {
			logs:           []string{"old"},
			oldest:         "old",
			expectedLogs:   []string{},
			expectedOldest: "old",
			expectedAllNew: false,
		},
		"no new entries, multipile old entries": {
			logs:           []string{"old1", "old2", "old3"},
			oldest:         "old3",
			expectedLogs:   []string{},
			expectedOldest: "old3",
			expectedAllNew: false,
		},
		"one new entry, no old entry": {
			logs:           []string{"new"},
			oldest:         "old",
			expectedLogs:   []string{"new"},
			expectedOldest: "new",
			expectedAllNew: true,
		},
		"multipile new entries, no old entry": {
			logs:           []string{"new1", "new2", "new3"},
			oldest:         "old",
			expectedLogs:   []string{"new1", "new2", "new3"},
			expectedOldest: "new3",
			expectedAllNew: true,
		},
		"one new entry, one old entry": {
			logs:           []string{"old", "new"},
			oldest:         "old",
			expectedLogs:   []string{"new"},
			expectedOldest: "new",
			expectedAllNew: false,
		},
		"one new entry, multipile old entries": {
			logs:           []string{"old1", "old2", "old3", "new"},
			oldest:         "old3",
			expectedLogs:   []string{"new"},
			expectedOldest: "new",
			expectedAllNew: false,
		},
		"multipile new entries, ultipile old entries": {
			logs:           []string{"old1", "old2", "old3", "new1", "new2", "new3"},
			oldest:         "old3",
			expectedLogs:   []string{"new1", "new2", "new3"},
			expectedOldest: "new3",
			expectedAllNew: false,
		},
	} {
		t.Run(name, func(t *testing.T) {
			logs, oldest, allNew := checkOldestEntry(tc.logs, tc.oldest)

			if allNew != tc.expectedAllNew {
				t.Logf("expected allNew: %v, got %v", tc.expectedAllNew, allNew)
				t.Fail()
			}
			if oldest != tc.expectedOldest {
				t.Logf("expected oldest: %v, got %v", tc.expectedOldest, oldest)
				t.Fail()
			}

			compareSlice(t, tc.expectedLogs, logs)
		})
	}
}

func TestFilterLogEntries(t *testing.T) {
	now := time.Now()

	for name, tc := range map[string]struct {
		logs         []string
		pluginID     string
		since        time.Time
		expectedLogs []string
		expectedErr  bool
	}{
		"nil slice": {
			logs:         nil,
			expectedLogs: nil,
			expectedErr:  false,
		},
		"empty slice": {
			logs:         []string{},
			expectedLogs: nil,
			expectedErr:  false,
		},
		"no JSON": {
			logs: []string{
				`{"foo"`,
			},
			expectedLogs: nil,
			expectedErr:  true,
		},
		"unknown time format": {
			logs: []string{
				`{"message":"foo", "plugin_id": "some.plugin.id", "timestamp": "2023-12-18 10:58:53"}`,
			},
			pluginID:     "some.plugin.id",
			expectedLogs: nil,
			expectedErr:  true,
		},
		"one matching entry": {
			logs: []string{
				`{"message":"foo", "plugin_id": "some.plugin.id", "timestamp": "2023-12-18 10:58:53.091 +01:00"}`,
			},
			pluginID: "some.plugin.id",
			expectedLogs: []string{
				`{"message":"foo", "plugin_id": "some.plugin.id", "timestamp": "2023-12-18 10:58:53.091 +01:00"}`,
			},
			expectedErr: false,
		},
		"filter out non plugin entries": {
			logs: []string{
				`{"message":"bar1", "timestamp": "2023-12-18 10:58:52.091 +01:00"}`,
				`{"message":"foo", "plugin_id": "some.plugin.id", "timestamp": "2023-12-18 10:58:53.091 +01:00"}`,
				`{"message":"bar2", "timestamp": "2023-12-18 10:58:54.091 +01:00"}`,
			},
			pluginID: "some.plugin.id",
			expectedLogs: []string{
				`{"message":"foo", "plugin_id": "some.plugin.id", "timestamp": "2023-12-18 10:58:53.091 +01:00"}`,
			},
			expectedErr: false,
		},
		"filter out old entries": {
			logs: []string{
				fmt.Sprintf(`{"message":"old2", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Add(-2*time.Second).Format(timeStampFormat)),
				fmt.Sprintf(`{"message":"old1", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Add(-1*time.Second).Format(timeStampFormat)),
				fmt.Sprintf(`{"message":"now", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Format(timeStampFormat)),
				fmt.Sprintf(`{"message":"new1", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Add(1*time.Second).Format(timeStampFormat)),
				fmt.Sprintf(`{"message":"new2", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Add(2*time.Second).Format(timeStampFormat)),
			},
			pluginID: "some.plugin.id",
			since:    now,
			expectedLogs: []string{
				fmt.Sprintf(`{"message":"new1", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Add(1*time.Second).Format(timeStampFormat)),
				fmt.Sprintf(`{"message":"new2", "plugin_id": "some.plugin.id", "timestamp": "%s"}`, now.Add(2*time.Second).Format(timeStampFormat)),
			},
			expectedErr: false,
		},
	} {
		t.Run(name, func(t *testing.T) {
			logs, err := filterLogEntries(tc.logs, tc.pluginID, tc.since)
			if tc.expectedErr {
				if err == nil {
					t.Logf("expected error, got nil")
					t.Fail()
				}
			} else {
				if err != nil {
					t.Logf("expected no error, got %v", err)
					t.Fail()
				}
			}
			compareSlice(t, tc.expectedLogs, logs)
		})
	}
}

func compareSlice[S ~[]E, E comparable](t *testing.T, expected, got S) {
	if len(expected) != len(got) {
		t.Logf("expected len: %v, got %v", len(expected), len(got))
		t.FailNow()
	}

	for i := 0; i < len(expected); i++ {
		if expected[i] != got[i] {
			t.Logf("expected [%d]: %v, got %v", i, expected[i], got[i])
			t.Fail()
		}
	}
}
