package models_test

import (
	"encoding/json"
	"reflect"
	"testing"
	"time"

	jsonv2 "github.com/go-json-experiment/json"

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

// TestMarshalJSONTo_MatchesV1 verifies that our custom MarshalJSONTo methods
// produce JSON that is semantically identical to encoding/json v1's
// reflection-based Marshal for the same struct values.
func TestMarshalJSONTo_MatchesV1(t *testing.T) {
	ts := time.Date(2025, 3, 10, 12, 0, 0, 0, time.UTC)

	tests := []struct {
		// scenario describes the behaviour being tested
		name string
		val  any
	}{
		{
			// empty ordered labels slice should marshal to []
			name: "OrderedLabels/empty",
			val:  models.OrderedLabels{},
		},
		{
			// ordered labels with multiple entries
			name: "OrderedLabels/populated",
			val: models.OrderedLabels{
				{Name: "alertname", Value: "TestAlert"},
				{Name: "severity", Value: "critical"},
			},
		},
		{
			// single annotation with all boolean fields set
			name: "Annotation/allFields",
			val: models.Annotation{
				Name:     "summary",
				Value:    "Something broke",
				Visible:  true,
				IsLink:   false,
				IsAction: true,
			},
		},
		{
			// empty annotations slice should marshal to []
			name: "Annotations/empty",
			val:  models.Annotations{},
		},
		{
			// annotations with mixed visibility and link flags
			name: "Annotations/populated",
			val: models.Annotations{
				{Name: "summary", Value: "test", Visible: true, IsLink: false, IsAction: false},
				{Name: "url", Value: "https://example.com", Visible: false, IsLink: true, IsAction: false},
			},
		},
		{
			// alert with all nested types populated
			name: "APIAlert/full",
			val: models.APIAlert{
				StartsAt: ts,
				State:    "active",
				Receiver: "default",
				LabelsFP: "abc123",
				Annotations: models.Annotations{
					{Name: "summary", Value: "test alert", Visible: true},
				},
				Labels: models.OrderedLabels{
					{Name: "alertname", Value: "TestAlert"},
				},
				Alertmanager: []models.AlertmanagerInstance{
					{
						StartsAt:    ts,
						Fingerprint: "fp1",
						Name:        "am1",
						Cluster:     "cluster1",
						Source:      "http://am1:9093",
						SilencedBy:  []string{"silence1"},
						InhibitedBy: []string{},
						State:       models.AlertStateActive,
					},
				},
			},
		},
		{
			// alert with empty slices to verify they marshal as [] not null
			name: "APIAlert/emptySlices",
			val: models.APIAlert{
				StartsAt:     ts,
				State:        "suppressed",
				Annotations:  models.Annotations{},
				Labels:       models.OrderedLabels{},
				Alertmanager: []models.AlertmanagerInstance{},
			},
		},
		{
			// alertmanager instance with all fields populated
			name: "AlertmanagerInstance/full",
			val: models.AlertmanagerInstance{
				StartsAt:    ts,
				Fingerprint: "fp-abc",
				Name:        "prod-am",
				Cluster:     "us-east",
				Source:      "http://prod-am:9093/#/alerts",
				SilencedBy:  []string{"s1", "s2"},
				InhibitedBy: []string{"i1"},
				State:       models.AlertStateSuppressed,
			},
		},
		{
			// shared maps with all nested types
			name: "APIAlertGroupSharedMaps/full",
			val: models.APIAlertGroupSharedMaps{
				Annotations: models.Annotations{
					{Name: "shared_ann", Value: "shared_val", Visible: true},
				},
				Labels: models.OrderedLabels{
					{Name: "job", Value: "node_exporter"},
				},
				Silences: map[string][]string{
					"am1": {"silence-a"},
				},
				Sources:  []string{"http://am1:9093"},
				Clusters: []string{"cluster1"},
			},
		},
		{
			// shared maps with empty collections
			name: "APIAlertGroupSharedMaps/empty",
			val: models.APIAlertGroupSharedMaps{
				Annotations: models.Annotations{},
				Labels:      models.OrderedLabels{},
				Silences:    map[string][]string{},
				Sources:     []string{},
				Clusters:    []string{},
			},
		},
		{
			// filter with all fields populated
			name: "Filter/full",
			val: models.Filter{
				Text:    "@alertname=~test",
				Name:    "alertname",
				Matcher: "=~",
				Value:   "test",
				Hits:    42,
				IsValid: true,
			},
		},
		{
			// label colors with background and brightness
			name: "LabelColors/full",
			val: models.LabelColors{
				Background: "rgba(255,0,0,255)",
				Brightness: 128,
			},
		},
		{
			// nested color map with multiple labels and values
			name: "LabelsColorMap/populated",
			val: models.LabelsColorMap{
				"severity": {
					"critical": models.LabelColors{Background: "#f00", Brightness: 100},
				},
			},
		},
		{
			// label value stats with all fields
			name: "LabelValueStats/full",
			val: models.LabelValueStats{
				Value:   "critical",
				Raw:     "critical",
				Hits:    10,
				Percent: 50,
				Offset:  0,
			},
		},
		{
			// label value stats list with multiple entries
			name: "LabelValueStatsList/populated",
			val: models.LabelValueStatsList{
				{Value: "critical", Raw: "critical", Hits: 10, Percent: 50, Offset: 0},
				{Value: "warning", Raw: "warning", Hits: 5, Percent: 25, Offset: 50},
			},
		},
		{
			// label name stats with nested values
			name: "LabelNameStats/full",
			val: models.LabelNameStats{
				Name: "severity",
				Values: models.LabelValueStatsList{
					{Value: "critical", Raw: "critical", Hits: 10, Percent: 50, Offset: 0},
				},
				Hits: 10,
			},
		},
		{
			// label name stats list with multiple entries
			name: "LabelNameStatsList/populated",
			val: models.LabelNameStatsList{
				{Name: "severity", Values: models.LabelValueStatsList{{Value: "critical", Raw: "critical", Hits: 10, Percent: 100, Offset: 0}}, Hits: 10},
			},
		},
		{
			// silence matcher with regex enabled
			name: "SilenceMatcher/regex",
			val: models.SilenceMatcher{
				Name:    "alertname",
				Value:   "test.*",
				IsRegex: true,
				IsEqual: true,
			},
		},
		{
			// silence with all fields and multiple matchers
			name: "Silence/full",
			val: models.Silence{
				StartsAt:  ts,
				EndsAt:    ts.Add(time.Hour),
				CreatedAt: ts.Add(-time.Hour),
				ID:        "silence-123",
				CreatedBy: "user@example.com",
				Comment:   "maintenance window",
				TicketID:  "JIRA-456",
				TicketURL: "https://jira.example.com/JIRA-456",
				Matchers: []models.SilenceMatcher{
					{Name: "alertname", Value: "TestAlert", IsRegex: false, IsEqual: true},
				},
			},
		},
		{
			// managed silence wrapping a silence with metadata
			name: "ManagedSilence/full",
			val: models.ManagedSilence{
				Cluster: "prod",
				Silence: models.Silence{
					StartsAt:  ts,
					EndsAt:    ts.Add(time.Hour),
					CreatedAt: ts,
					ID:        "sil-1",
					CreatedBy: "admin",
					Comment:   "test",
					Matchers:  []models.SilenceMatcher{{Name: "env", Value: "prod", IsEqual: true}},
				},
				AlertCount: 5,
				IsExpired:  false,
			},
		},
		{
			// alertmanager API status with all fields
			name: "AlertmanagerAPIStatus/full",
			val: models.AlertmanagerAPIStatus{
				Headers:         map[string]string{"X-Custom": "value"},
				Name:            "am1",
				URI:             "http://am1:9093",
				PublicURI:       "https://am1.example.com",
				CORSCredentials: "include",
				Error:           "",
				Version:         "0.27.0",
				Cluster:         "prod",
				ClusterMembers:  []string{"am1", "am2"},
				ReadOnly:        false,
			},
		},
		{
			// alertmanager API counters
			name: "AlertmanagerAPICounters/full",
			val: models.AlertmanagerAPICounters{
				Total:   3,
				Healthy: 2,
				Failed:  1,
			},
		},
		{
			// alertmanager API summary with nested instances and counters
			name: "AlertmanagerAPISummary/full",
			val: models.AlertmanagerAPISummary{
				Clusters: map[string][]string{"prod": {"am1", "am2"}},
				Instances: []models.AlertmanagerAPIStatus{
					{
						Headers:         map[string]string{},
						Name:            "am1",
						URI:             "http://am1:9093",
						Cluster:         "prod",
						ClusterMembers:  []string{},
						PublicURI:       "",
						CORSCredentials: "",
						Error:           "",
						Version:         "",
					},
				},
				Counters: models.AlertmanagerAPICounters{Total: 1, Healthy: 1},
			},
		},
		{
			// grid settings
			name: "GridSettings/full",
			val: models.GridSettings{
				Order:   "startsAt",
				Label:   "severity",
				Reverse: true,
			},
		},
		{
			// sort settings with nested grid and value mapping
			name: "SortSettings/full",
			val: models.SortSettings{
				ValueMapping: map[string]map[string]string{
					"severity": {"critical": "1", "warning": "2"},
				},
				Grid: models.GridSettings{Order: "label", Label: "cluster", Reverse: false},
			},
		},
		{
			// silence form strip settings
			name: "SilenceFormStripSettings/full",
			val: models.SilenceFormStripSettings{
				Labels: []string{"alertname", "severity"},
			},
		},
		{
			// silence form settings with nested strip
			name: "SilenceFormSettings/full",
			val: models.SilenceFormSettings{
				Strip:                models.SilenceFormStripSettings{Labels: []string{"job"}},
				DefaultAlertmanagers: []string{"am1"},
			},
		},
		{
			// alert acknowledgement settings
			name: "AlertAcknowledgementSettings/full",
			val: models.AlertAcknowledgementSettings{
				Author:          "karma",
				Comment:         "ACK!",
				DurationSeconds: 900,
				Enabled:         true,
			},
		},
		{
			// labels settings map with multiple entries
			name: "LabelsSettings/populated",
			val: models.LabelsSettings{
				"alertname": {IsStatic: true, IsValueOnly: false},
				"job":       {IsStatic: false, IsValueOnly: true},
			},
		},
		{
			// full settings with all nested structures
			name: "Settings/full",
			val: models.Settings{
				Labels: models.LabelsSettings{
					"alertname": {IsStatic: true},
				},
				Sorting: models.SortSettings{
					ValueMapping: map[string]map[string]string{},
					Grid:         models.GridSettings{Order: "label"},
				},
				SilenceForm: models.SilenceFormSettings{
					Strip:                models.SilenceFormStripSettings{Labels: []string{}},
					DefaultAlertmanagers: []string{},
				},
				AnnotationsHidden:  []string{"help"},
				AnnotationsVisible: []string{"summary"},
				AlertAcknowledgement: models.AlertAcknowledgementSettings{
					Enabled: true, DurationSeconds: 900, Author: "karma", Comment: "ACK",
				},
				GridGroupLimit:           40,
				AnnotationsDefaultHidden: false,
				AnnotationsAllowHTML:     true,
				HistoryEnabled:           true,
			},
		},
		{
			// authentication info
			name: "AuthenticationInfo/full",
			val: models.AuthenticationInfo{
				Username: "admin",
				Groups:   []string{"ops", "dev"},
				Enabled:  true,
			},
		},
		{
			// autocomplete entry
			name: "Autocomplete/full",
			val: models.Autocomplete{
				Value:  "@alertname=TestAlert",
				Tokens: []string{"alertname", "alertname=", "TestAlert"},
			},
		},
		{
			// counters with nested label name stats
			name: "Counters/full",
			val: models.Counters{
				Counters: models.LabelNameStatsList{
					{Name: "severity", Values: models.LabelValueStatsList{{Value: "critical", Raw: "critical", Hits: 5, Percent: 100, Offset: 0}}, Hits: 5},
				},
				Total: 5,
			},
		},
		{
			// API grid with state counts, label info, and nested alert groups
			name: "APIGrid/full",
			val: models.APIGrid{
				StateCount: map[string]int{"active": 2, "suppressed": 1},
				LabelName:  "cluster",
				LabelValue: "prod",
				AlertGroups: []models.APIAlertGroup{
					{
						AllLabels:         map[string]map[string][]string{},
						AlertmanagerCount: map[string]int{"am1": 1},
						StateCount:        map[string]int{"active": 1},
						Receiver:          "default",
						ID:                "group-1",
						Shared: models.APIAlertGroupSharedMaps{
							Annotations: models.Annotations{},
							Labels:      models.OrderedLabels{},
							Silences:    map[string][]string{},
							Sources:     []string{},
							Clusters:    []string{},
						},
						Labels: models.OrderedLabels{},
						Alerts: []models.APIAlert{},
					},
				},
				TotalGroups: 1,
			},
		},
		{
			// API grid with populated alert groups containing actual alerts,
			// exercising the inner alert marshaling loop in APIGrid.MarshalJSONTo
			name: "APIGrid/with_alerts",
			val: models.APIGrid{
				StateCount: map[string]int{"active": 1},
				LabelName:  "cluster",
				LabelValue: "prod",
				AlertGroups: []models.APIAlertGroup{
					{
						AllLabels: map[string]map[string][]string{
							"active": {"alertname": {"Test"}},
						},
						AlertmanagerCount: map[string]int{"am1": 1},
						StateCount:        map[string]int{"active": 1},
						Receiver:          "default",
						ID:                "group-1",
						Shared: models.APIAlertGroupSharedMaps{
							Annotations: models.Annotations{},
							Labels:      models.OrderedLabels{{Name: "job", Value: "test"}},
							Silences:    map[string][]string{},
							Sources:     []string{"http://am1:9093"},
							Clusters:    []string{"cluster1"},
						},
						Labels: models.OrderedLabels{{Name: "alertname", Value: "Test"}},
						Alerts: []models.APIAlert{
							{
								StartsAt: ts,
								State:    "active",
								Receiver: "default",
								LabelsFP: "fp1",
								Annotations: models.Annotations{
									{Name: "summary", Value: "test", Visible: true},
								},
								Labels: models.OrderedLabels{
									{Name: "instance", Value: "localhost:9090"},
								},
								Alertmanager: []models.AlertmanagerInstance{
									{
										StartsAt:    ts,
										Fingerprint: "fp1",
										Name:        "am1",
										Cluster:     "cluster1",
										Source:      "http://am1:9093",
										SilencedBy:  []string{},
										InhibitedBy: []string{},
										State:       models.AlertStateActive,
									},
								},
							},
						},
						TotalAlerts: 1,
					},
				},
				TotalGroups: 1,
			},
		},
		{
			// full alert group with all nested structures populated
			name: "APIAlertGroup/full",
			val: models.APIAlertGroup{
				AllLabels: map[string]map[string][]string{
					"active": {
						"alertname": {"TestAlert"},
						"severity":  {"critical", "warning"},
					},
				},
				AlertmanagerCount: map[string]int{"am1": 2},
				StateCount:        map[string]int{"active": 1, "suppressed": 1},
				Receiver:          "default",
				ID:                "group-1",
				Shared: models.APIAlertGroupSharedMaps{
					Annotations: models.Annotations{},
					Labels:      models.OrderedLabels{{Name: "job", Value: "test"}},
					Silences:    map[string][]string{},
					Sources:     []string{"http://am1:9093"},
					Clusters:    []string{"cluster1"},
				},
				Labels: models.OrderedLabels{
					{Name: "alertname", Value: "TestAlert"},
				},
				Alerts: []models.APIAlert{
					{
						StartsAt: ts,
						State:    "active",
						Receiver: "default",
						LabelsFP: "fp1",
						Annotations: models.Annotations{
							{Name: "summary", Value: "test", Visible: true},
						},
						Labels: models.OrderedLabels{
							{Name: "instance", Value: "localhost:9090"},
						},
						Alertmanager: []models.AlertmanagerInstance{
							{
								StartsAt:    ts,
								Fingerprint: "fp1",
								Name:        "am1",
								Cluster:     "cluster1",
								Source:      "http://am1:9093",
								SilencedBy:  []string{},
								InhibitedBy: []string{},
								State:       models.AlertStateActive,
							},
						},
					},
				},
				TotalAlerts: 5,
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			// v1 encoding/json is the reference output
			v1Bytes, err := json.Marshal(tc.val)
			if err != nil {
				t.Fatalf("v1 Marshal failed: %v", err)
			}

			// v2 will dispatch to our MarshalJSONTo methods
			v2Bytes, err := jsonv2.Marshal(tc.val)
			if err != nil {
				t.Fatalf("v2 Marshal failed: %v", err)
			}

			// unmarshal both into any for comparison, this handles
			// non-deterministic map key ordering
			var v1Any, v2Any any
			if err := json.Unmarshal(v1Bytes, &v1Any); err != nil {
				t.Fatalf("v1 Unmarshal failed: %v", err)
			}
			if err := json.Unmarshal(v2Bytes, &v2Any); err != nil {
				t.Fatalf("v2 Unmarshal failed: %v", err)
			}

			if !reflect.DeepEqual(v1Any, v2Any) {
				t.Errorf("v1 vs v2 mismatch\nv1: %s\nv2: %s", v1Bytes, v2Bytes)
			}
		})
	}
}
