package service

import (
	"testing"

	"gopkg.in/yaml.v3"
)

func TestWorkflowYAMLMatchesMergeGroupActivityAndTargetBranch(t *testing.T) {
	tests := []struct {
		name    string
		yaml    string
		baseRef string
		want    bool
	}{
		{"scalar shorthand", "on: merge_group\njobs: {}\n", "refs/heads/main", true},
		{"sequence shorthand", "on: [push, merge_group]\njobs: {}\n", "refs/heads/main", true},
		{"main include matches main", "on:\n  merge_group:\n    branches: [main]\njobs: {}\n", "refs/heads/main", true},
		{"main include rejects release", "on:\n  merge_group:\n    branches: [main]\njobs: {}\n", "refs/heads/release", false},
		{"release include matches release", "on:\n  merge_group:\n    types: [checks_requested]\n    branches: [release]\njobs: {}\n", "release", true},
		{"release ignore rejects release", "on:\n  merge_group:\n    branches-ignore: [release]\njobs: {}\n", "refs/heads/release", false},
		{"release ignore permits main", "on:\n  merge_group:\n    branches-ignore: [release]\njobs: {}\n", "refs/heads/main", true},
		{"ordered negative excludes alpha", "on:\n  merge_group:\n    branches: ['releases/**', '!releases/**-alpha']\njobs: {}\n", "refs/heads/releases/10-alpha", false},
		{"ordered positive re-includes", "on:\n  merge_group:\n    branches: ['releases/**', '!releases/**-alpha', 'releases/beta/3-alpha']\njobs: {}\n", "refs/heads/releases/beta/3-alpha", true},
		{"single star does not cross slash", "on:\n  merge_group:\n    branches: ['releases/*']\njobs: {}\n", "refs/heads/releases/beta/1", false},
		{"double star crosses slash", "on:\n  merge_group:\n    branches: ['releases/**']\njobs: {}\n", "refs/heads/releases/beta/1", true},
		{"question quantifies preceding atom", "on:\n  merge_group:\n    branches: ['releases?']\njobs: {}\n", "refs/heads/release", true},
		{"plus and character class quantifiers", "on:\n  merge_group:\n    branches: ['v[12].[0-9]+.[0-9]+']\njobs: {}\n", "refs/heads/v2.10.1", true},
		{"unicode literals match", "on:\n  merge_group:\n    branches: ['发布/**']\njobs: {}\n", "refs/heads/发布/生产", true},
		{"unicode escaped literals match", "on:\n  merge_group:\n    branches: ['发布/\\生*']\njobs: {}\n", "refs/heads/发布/生产", true},
		{"matching activity", "on:\n  merge_group:\n    types: checks_requested\njobs: {}\n", "main", true},
		{"aliased event configuration", "on:\n  pull_request: &main-target\n    branches: [main]\n  merge_group: *main-target\njobs: {}\n", "main", true},
		{"aliased types and branches", "on:\n  pull_request:\n    types: &activities [checks_requested]\n    branches: &targets [main]\n  merge_group:\n    types: *activities\n    branches: *targets\njobs: {}\n", "main", true},
		{"aliased list items", "on:\n  pull_request:\n    branches: [&main main]\n  merge_group:\n    branches: [*main]\njobs: {}\n", "main", true},
		{"other activity", "on:\n  merge_group:\n    types: [destroyed]\njobs: {}\n", "main", false},
		{"empty activity list", "on:\n  merge_group:\n    types: []\njobs: {}\n", "main", false},
		{"boolean event node", "on:\n  merge_group: false\njobs: {}\n", "main", false},
		{"sequence event node", "on:\n  merge_group: [checks_requested]\njobs: {}\n", "main", false},
		{"boolean branches filter", "on:\n  merge_group:\n    branches: false\njobs: {}\n", "main", false},
		{"empty branches filter", "on:\n  merge_group:\n    branches: ['']\njobs: {}\n", "main", false},
		{"conflicting branch filters", "on:\n  merge_group:\n    branches: [main]\n    branches-ignore: [release]\njobs: {}\n", "main", false},
		{"negative-only branches", "on:\n  merge_group:\n    branches: ['!release']\njobs: {}\n", "main", false},
		{"negation in branches-ignore", "on:\n  merge_group:\n    branches-ignore: ['!release']\njobs: {}\n", "main", false},
		{"unknown merge group option", "on:\n  merge_group:\n    paths: ['**']\njobs: {}\n", "main", false},
		{"recursive alias fails closed", "on: &events [*events]\njobs: {}\n", "main", false},
		{"other event", "on: pull_request\njobs: {}\n", "main", false},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if got := workflowYAMLMatchesMergeGroup([]byte(test.yaml), "checks_requested", test.baseRef); got != test.want {
				t.Fatalf("workflowYAMLMatchesMergeGroup(%q, %q) = %v, want %v", test.yaml, test.baseRef, got, test.want)
			}
		})
	}
}

func TestWorkflowYAMLDeclaresWorkflowDispatchStrictly(t *testing.T) {
	tests := []struct {
		yaml string
		want bool
	}{
		{"on: workflow_dispatch\n", true},
		{"on: [push, workflow_dispatch]\n", true},
		{"on:\n  workflow_dispatch:\n", true},
		{"on:\n  workflow_dispatch:\n    inputs:\n      environment:\n        required: true\n", true},
		{"on:\n  workflow_call: &manual {}\n  workflow_dispatch: *manual\n", true},
		{"on: &events [push, workflow_dispatch]\n", true},
		{"on:\n  workflow_dispatch: false\n", false},
		{"on:\n  workflow_dispatch: []\n", false},
		{"on: push\n", false},
	}
	for _, test := range tests {
		if got := workflowYAMLDeclaresEvent([]byte(test.yaml), "workflow_dispatch"); got != test.want {
			t.Fatalf("workflowYAMLDeclaresEvent(%q) = %v, want %v", test.yaml, got, test.want)
		}
	}
}

func TestWorkflowYAMLResolveAliasRejectsCycle(t *testing.T) {
	first := &yaml.Node{Kind: yaml.AliasNode}
	second := &yaml.Node{Kind: yaml.AliasNode}
	first.Alias = second
	second.Alias = first
	if _, ok := workflowYAMLResolveAlias(first); ok {
		t.Fatal("cyclic YAML alias was accepted")
	}
}

func TestValidateMergeQueueRuleset(t *testing.T) {
	valid := `[{"type":"merge_queue","parameters":{"check_response_timeout_minutes":60,"grouping_strategy":"ALLGREEN","merge_method":"SQUASH","max_entries_to_build":5,"max_entries_to_merge":5,"min_entries_to_merge":1,"min_entries_to_merge_wait_minutes":5}}]`
	if err := validateRulesetRules(valid); err != nil {
		t.Fatalf("valid merge_queue rule rejected: %v", err)
	}
	zeroLimits := `[{"type":"merge_queue","parameters":{"check_response_timeout_minutes":60,"grouping_strategy":"ALLGREEN","merge_method":"SQUASH","max_entries_to_build":0,"max_entries_to_merge":0,"min_entries_to_merge":0,"min_entries_to_merge_wait_minutes":0}}]`
	if err := validateRulesetRules(zeroLimits); err != nil {
		t.Fatalf("GitHub-valid zero merge queue limits rejected: %v", err)
	}
	invalid := `[{"type":"merge_queue","parameters":{"grouping_strategy":"UNKNOWN"}}]`
	if err := validateRulesetRules(invalid); err == nil {
		t.Fatal("invalid grouping strategy accepted")
	}
	extra := `[{"type":"merge_queue","parameters":{"check_response_timeout_minutes":60,"grouping_strategy":"ALLGREEN","merge_method":"SQUASH","max_entries_to_build":5,"max_entries_to_merge":5,"min_entries_to_merge":1,"min_entries_to_merge_wait_minutes":5,"merge_queue_lock_branch":true}}]`
	if err := validateRulesetRules(extra); err == nil {
		t.Fatal("non-GitHub merge_queue parameter accepted")
	}
}

func TestRulesetRefPatternMatchesGitHubFnmatch(t *testing.T) {
	tests := []struct {
		pattern string
		branch  string
		want    bool
	}{
		{"~ALL", "feature/deep/name", true},
		{"~DEFAULT_BRANCH", "main", true},
		{"qa/*", "qa/one", true},
		{"qa/*", "qa/one/two", false},
		{"releases/**/*", "releases/one/two", true},
		{"refs/heads/release-*", "release-2026", true},
	}
	for _, test := range tests {
		if got := rulesetRefPatternMatches(test.pattern, test.branch, "main"); got != test.want {
			t.Fatalf("rulesetRefPatternMatches(%q, %q) = %v, want %v", test.pattern, test.branch, got, test.want)
		}
	}
}

func TestCollectRequiredStatusChecksTreatsLegacyMinusOneAsAnyProvider(t *testing.T) {
	anyApp := int64(-1)
	checks := collectRequiredStatusChecks(branchProtectionRequiredStatusChecks{
		Checks: []branchProtectionCheckEntry{{Context: "CI", AppID: &anyApp}},
	})
	if len(checks) != 1 || checks[0].Context != "CI" || checks[0].IntegrationID != nil {
		t.Fatalf("legacy any-provider check = %+v", checks)
	}
}
