package matcher_test

import (
	"log/slog"
	"testing"

	"github.com/andygrunwald/go-jira"
	"github.com/trivago/tgo/tcontainer"

	"github.com/tidbcloud/jiraport/pkg/entity"
	"github.com/tidbcloud/jiraport/pkg/matcher"
)

// rawIssue builds a minimal *entity.RawIssue that the assign-oncall.yml
// extractors can evaluate without nil-pointer panics (Priority/Project/Summary
// are read by Section 1 rules that run before the component fallback).
func rawIssue(project, summary, component, alertLabelsJSON string) *entity.RawIssue {
	unknowns := tcontainer.NewMarshalMap()
	if alertLabelsJSON != "" {
		unknowns["customfield_10160"] = alertLabelsJSON
	}
	fields := &jira.IssueFields{
		Project:    jira.Project{Key: project},
		Summary:    summary,
		Priority:   &jira.Priority{Name: "Warning"},
		Unknowns:   unknowns,
		Components: []*jira.Component{},
	}
	if component != "" {
		fields.Components = []*jira.Component{{Name: component}}
	}
	return &entity.RawIssue{Fields: fields}
}

// TestProdAssignOncallSection3 guards the oncall_team extractor / Section 3
// component fallback in assign-oncall.yml. Regression for O11Y-2647602: an
// O11Y next-gen tidb alert with NO oncall_team label must route to the
// tidb_executor team via Section 3. Historically the extractor rendered
// "<no value>" for a missing label, which never matched the `oncall_team: ^$`
// guard, so the whole of Section 3 was silently skipped (-> no_team fallback).
func TestProdAssignOncallSection3(t *testing.T) {
	m, err := matcher.NewMatcher(slog.Default(), "../../config/prod-tidbcloud/assign-oncall.yml")
	if err != nil {
		t.Fatal(err)
	}

	tests := []struct {
		name      string
		issue     *entity.RawIssue
		want      []string // every receiver must be present
		doNotWant []string
	}{
		{
			// The O11Y-2647602 shape: alert labels exist but carry no oncall_team.
			name:  "next-gen tidb alert without oncall_team routes to tidb_executor",
			issue: rawIssue("O11Y", "PremiumHighResourceUnitUsage", "tidb", `{"labels":{"component":"tidb","tier":"next-gen","o11y_biz_type":"byoc","severity":"warning"}}`),
			want:  []string{"tidb_executor"},
		},
		{
			// An explicit oncall_team must outrank Section 3 (see #415): the
			// component fallback is skipped and no Section 1 rule claims it.
			name:      "explicit oncall_team skips Section 3 component fallback",
			issue:     rawIssue("O11Y", "PremiumHighResourceUnitUsage", "tidb", `{"labels":{"component":"tidb","tier":"next-gen","oncall_team":"tidb_txn"}}`),
			doNotWant: []string{"tidb_executor", "resilience"},
		},
		{
			// Non-alert issue (no customfield_10160) must still reach Section 3,
			// i.e. oncall_team extractor yields "" not "<no value>" (see #419).
			name:  "non-alert Clinic issue routes to clinic",
			issue: rawIssue("TCOC", "SomeClinicProblem", "Clinic", ""),
			want:  []string{"clinic"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			receivers, err := m.Match(tt.issue)
			if err != nil {
				t.Fatalf("Match() error = %v", err)
			}
			got, err := receivers.TryToStringSlice()
			if err != nil {
				t.Fatalf("TryToStringSlice() error = %v", err)
			}
			for _, r := range tt.want {
				if !contains(got, r) {
					t.Errorf("receivers %v do not contain %q", got, r)
				}
			}
			for _, r := range tt.doNotWant {
				if contains(got, r) {
					t.Errorf("receivers %v unexpectedly contain %q", got, r)
				}
			}
		})
	}
}
