package filters_test

import (
	"fmt"
	"log/slog"
	"sort"
	"strconv"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/prometheus/prometheus/model/labels"

	"github.com/prymitive/karma/internal/filters"
	"github.com/prymitive/karma/internal/log"
	"github.com/prymitive/karma/internal/models"
)

type acTest struct {
	Alerts   []models.Alert
	Expected []string
}

var acTests = []acTest{
	{
		Alerts: []models.Alert{},
		Expected: []string{
			"@age\u003e1h",
			"@age\u003c10m",
			"@age\u003c1h",
			"@age\u003e10m",
			"@inhibited=false",
			"@inhibited=true",
			"@limit=10",
			"@limit=50",
		},
	},
	{
		Alerts: []models.Alert{
			{
				State:    models.AlertStateActive,
				Labels:   labels.FromStrings("foo", "bar", "number", "1"),
				Receiver: "default",
				Alertmanager: []models.AlertmanagerInstance{
					{Cluster: "cluster", Name: "am1"},
					{Cluster: "cluster", Name: "am2"},
				},
			},
			{
				State:    models.AlertStateSuppressed,
				Labels:   labels.FromStrings("foo", "bar baz", "number", "5"),
				Receiver: "not default",
				Alertmanager: []models.AlertmanagerInstance{
					{Cluster: "cluster", Name: "am1"},
					{
						Cluster:     "cluster",
						Name:        "am2",
						SilencedBy:  []string{"1234567890"},
						InhibitedBy: []string{"1234567890"},
						Silences: map[string]*models.Silence{
							"1234567890": {
								ID:        "1234567890",
								CreatedBy: "me@example.com",
								TicketID:  "JIRA-1",
							},
						},
					},
				},
				SilencedBy: []string{"1234567890"},
			},
		},
		Expected: []string{
			"@age\u003c10m",
			"@age\u003c1h",
			"@age\u003e10m",
			"@age\u003e1h",
			"@alertmanager!=am1",
			"@alertmanager!=am2",
			"@alertmanager=am1",
			"@alertmanager=am2",
			"@cluster!=cluster",
			"@cluster=cluster",
			"@inhibited=false",
			"@inhibited=true",
			"@inhibited_by!=1234567890",
			"@inhibited_by=1234567890",
			"@limit=10",
			"@limit=50",
			"@receiver!=default",
			"@receiver!=not default",
			"@receiver!~default",
			"@receiver!~not",
			"@receiver=default",
			"@receiver=not default",
			"@receiver=~default",
			"@receiver=~not",
			"@silence_author!=me@example.com",
			"@silence_author!~me@example.com",
			"@silence_author=me@example.com",
			"@silence_author=~me@example.com",
			"@silenced_by!=1234567890",
			"@silenced_by=1234567890",
			"@silence_ticket!=JIRA-1",
			"@silence_ticket!~JIRA-1",
			"@silence_ticket=JIRA-1",
			"@silence_ticket=~JIRA-1",
			"@state!=active",
			"@state!=suppressed",
			"@state=active",
			"@state=suppressed",
			"foo!=bar",
			"foo!=bar baz",
			"foo!~bar",
			"foo!~baz",
			"foo=bar",
			"foo=bar baz",
			"foo=~bar",
			"foo=~baz",
			"number!=1",
			"number!=5",
			"number\u003c1",
			"number\u003c5",
			"number=1",
			"number=5",
			"number\u003e1",
			"number\u003e5",
		},
	},
}

func TestBuildAutocomplete(t *testing.T) {
	for _, acTest := range acTests {
		labelPairs := make([][]labels.Label, 0, len(acTest.Alerts))
		for _, alert := range acTest.Alerts {
			var pairs []labels.Label
			alert.Labels.Range(func(l labels.Label) {
				pairs = append(pairs, l)
			})
			labelPairs = append(labelPairs, pairs)
		}

		dst := map[string]models.Autocomplete{}
		filters.BuildAutocomplete(acTest.Alerts, dst)
		filters.LabelAutocomplete(labelPairs, dst)

		result := make([]string, 0, len(dst))
		for _, hint := range dst {
			result = append(result, hint.Value)
		}

		sort.Strings(result)
		sort.Strings(acTest.Expected)

		if diff := cmp.Diff(acTest.Expected, result); diff != "" {
			t.Errorf("Wrong autocomplete data returned (-want +got):\n%s", diff)
		}
	}
}

func BenchmarkAutocomplete(b *testing.B) {
	log.SetLevel(slog.LevelError)

	const n = 10000
	alerts := make([]models.Alert, 0, n)
	labelPairs := make([][]labels.Label, 0, n)
	for i := range n {
		l := labels.FromStrings("foo", fmt.Sprintf("xxx%d", i), "number", strconv.Itoa(i))
		alerts = append(alerts, models.Alert{
			State:    models.AlertStateActive,
			Labels:   l,
			Receiver: fmt.Sprintf("receiver-%d", i%1000),
			Alertmanager: []models.AlertmanagerInstance{
				{Cluster: "cluster", Name: "am1"},
				{Cluster: "cluster", Name: "am2"},
			},
		})
		var pairs []labels.Label
		l.Range(func(lbl labels.Label) {
			pairs = append(pairs, lbl)
		})
		labelPairs = append(labelPairs, pairs)
	}
	b.Run("fresh", func(b *testing.B) {
		b.ReportAllocs()
		for i := 0; i < b.N; i++ {
			dst := map[string]models.Autocomplete{}
			filters.BuildAutocomplete(alerts, dst)
			filters.LabelAutocomplete(labelPairs, dst)
		}
	})
	b.Run("reuse", func(b *testing.B) {
		dst := map[string]models.Autocomplete{}
		b.ReportAllocs()
		for i := 0; i < b.N; i++ {
			clear(dst)
			filters.BuildAutocomplete(alerts, dst)
			filters.LabelAutocomplete(labelPairs, dst)
		}
	})
}
