package graphql

import (
	"context"
	"reflect"
	"sort"
	"testing"

	"github.com/pingcap/agent-git-service/internal/db"
	"github.com/pingcap/agent-git-service/internal/service"
	"github.com/pingcap/agent-git-service/internal/tenant"
)

func TestStackAndMergeQueueGraphQLTypeFieldsMatchGitHub(t *testing.T) {
	tests := map[string][]string{
		"PullRequestStack":      {"baseRefName", "entries", "id", "number", "size"},
		"PullRequestStackEntry": {"id", "position", "pullRequest", "stack"},
		"MergeQueue": {"configuration", "entries", "id", "nextEntryEstimatedTimeToMerge",
			"repository", "resourcePath", "url"},
		"MergeQueueConfiguration": {"checkResponseTimeout", "maximumEntriesToBuild", "maximumEntriesToMerge",
			"mergeMethod", "mergingStrategy", "minimumEntriesToMerge", "minimumEntriesToMergeWaitTime"},
		"MergeQueueEntry": {"baseCommit", "enqueuedAt", "enqueuer", "estimatedTimeToMerge", "headCommit",
			"id", "jump", "mergeQueue", "position", "pullRequest", "solo", "state"},
		"AddedToMergeQueueEvent": {"actor", "createdAt", "enqueuer", "id", "mergeQueue", "pullRequest"},
		"RemovedFromMergeQueueEvent": {"actor", "beforeCommit", "createdAt", "enqueuer", "id",
			"mergeQueue", "pullRequest", "reason"},
		"EnqueuePullRequestPayload": {"clientMutationId", "mergeQueueEntry"},
		"DequeuePullRequestPayload": {"clientMutationId", "mergeQueueEntry"},
	}
	for typeName, want := range tests {
		got := introspectedFieldNames(TypeFields(typeName))
		sort.Strings(want)
		if !reflect.DeepEqual(got, want) {
			t.Errorf("%s fields = %v, want %v", typeName, got, want)
		}
	}
}

func TestMergeQueueGraphQLInputIntrospectionMatchesGitHub(t *testing.T) {
	tests := []struct {
		typeName string
		want     map[string]string
	}{
		{typeName: "EnqueuePullRequestInput", want: map[string]string{
			"clientMutationId": "String", "expectedHeadOid": "GitObjectID", "jump": "Boolean", "pullRequestId": "ID!",
		}},
		{typeName: "DequeuePullRequestInput", want: map[string]string{
			"clientMutationId": "String", "id": "ID!",
		}},
		{typeName: "MergeQueueParametersInput", want: map[string]string{
			"checkResponseTimeoutMinutes": "Int!", "groupingStrategy": "MergeQueueGroupingStrategy!",
			"maxEntriesToBuild": "Int!", "maxEntriesToMerge": "Int!", "mergeMethod": "MergeQueueMergeMethod!",
			"minEntriesToMerge": "Int!", "minEntriesToMergeWaitMinutes": "Int!",
		}},
	}
	for _, test := range tests {
		response := TypeFields(test.typeName)
		if response["kind"] != "INPUT_OBJECT" || response["fields"] != nil {
			t.Fatalf("%s introspection = %#v", test.typeName, response)
		}
		if got := introspectedInputTypes(response); !reflect.DeepEqual(got, test.want) {
			t.Errorf("%s input fields = %#v, want %#v", test.typeName, got, test.want)
		}
	}
}

func TestStackQueueConnectionArgumentsMatchGitHub(t *testing.T) {
	tests := []struct {
		typeName string
		field    string
		want     []string
	}{
		{"PullRequestStack", "entries", []string{"after", "before", "first", "last"}},
		{"MergeQueue", "entries", []string{"after", "before", "first", "last"}},
		{"PullRequest", "timelineItems", []string{"after", "before", "first", "itemTypes", "last", "since", "skip"}},
	}
	for _, test := range tests {
		fields := TypeFields(test.typeName)["fields"].([]any)
		var got []string
		for _, raw := range fields {
			field := raw.(map[string]any)
			if field["name"] != test.field {
				continue
			}
			for _, rawArg := range field["args"].([]any) {
				got = append(got, rawArg.(map[string]any)["name"].(string))
			}
		}
		if !reflect.DeepEqual(got, test.want) {
			t.Errorf("%s.%s args = %v, want %v", test.typeName, test.field, got, test.want)
		}
	}
}

func TestMergeQueueGraphQLEnumsMatchGitHub(t *testing.T) {
	tests := map[string][]string{
		"MergeQueueEntryState":       {"AWAITING_CHECKS", "LOCKED", "MERGEABLE", "QUEUED", "UNMERGEABLE"},
		"MergeQueueGroupingStrategy": {"ALLGREEN", "HEADGREEN"},
		"MergeQueueMergingStrategy":  {"ALLGREEN", "HEADGREEN"},
		"MergeQueueMergeMethod":      {"MERGE", "REBASE", "SQUASH"},
	}
	for typeName, want := range tests {
		got := introspectedEnumNames(TypeFields(typeName))
		sort.Strings(want)
		if !reflect.DeepEqual(got, want) {
			t.Errorf("%s values = %v, want %v", typeName, got, want)
		}
	}
}

func TestMergeQueueTimelineConcreteShapesMatchGitHub(t *testing.T) {
	actor := map[string]any{"login": "queue-actor"}
	pullRequest := map[string]any{"id": "pr"}

	added := mergeQueueTimelineEventShape(db.PullRequestMergeQueueEvent{
		ID: 1, QueueID: 2, EventType: "added",
	}, actor, actor, pullRequest)
	assertExactMapKeys(t, added, "__typename", "actor", "createdAt", "enqueuer", "id", "mergeQueue", "pullRequest")
	if !reflect.DeepEqual(added["enqueuer"], actor) {
		t.Fatalf("added enqueuer = %#v, want queue actor", added["enqueuer"])
	}

	removed := mergeQueueTimelineEventShape(db.PullRequestMergeQueueEvent{
		ID: 3, QueueID: 2, EventType: "removed", BeforeSHA: "abc", Reason: "removed from merge queue",
	}, actor, actor, pullRequest)
	assertExactMapKeys(t, removed, "__typename", "actor", "beforeCommit", "createdAt", "enqueuer", "id", "mergeQueue", "pullRequest", "reason")
	if !reflect.DeepEqual(removed["actor"], actor) || !reflect.DeepEqual(removed["enqueuer"], actor) {
		t.Fatalf("removed actor/enqueuer = %#v/%#v", removed["actor"], removed["enqueuer"])
	}
}

func TestMergeQueueReferenceConcreteShapeMatchesGitHub(t *testing.T) {
	ctx := tenant.WithInfo(context.Background(), tenant.Info{
		ID:               "stack-queue-contract",
		CanonicalBaseURL: "https://api.example.test",
	})
	srv := &Server{Svc: &service.Service{}}
	got := srv.mergeQueueReferenceGQL(ctx, service.MergeQueueView{
		Queue: db.MergeQueue{ID: 4, BaseRef: "main", Repository: db.Repository{FullName: "octo/repo"}},
	})
	assertExactMapKeys(t, got, "__typename", "configuration", "entries", "id", "nextEntryEstimatedTimeToMerge", "repository", "resourcePath", "url")
	assertExactMapKeys(t, got["configuration"].(map[string]any),
		"checkResponseTimeout", "maximumEntriesToBuild", "maximumEntriesToMerge", "mergeMethod",
		"mergingStrategy", "minimumEntriesToMerge", "minimumEntriesToMergeWaitTime")
}

func assertExactMapKeys(t *testing.T, value map[string]any, want ...string) {
	t.Helper()
	got := make([]string, 0, len(value))
	for key := range value {
		got = append(got, key)
	}
	sort.Strings(got)
	sort.Strings(want)
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("keys = %v, want %v", got, want)
	}
}

func introspectedFieldNames(response map[string]any) []string {
	fields, _ := response["fields"].([]any)
	out := make([]string, 0, len(fields))
	for _, raw := range fields {
		out = append(out, raw.(map[string]any)["name"].(string))
	}
	sort.Strings(out)
	return out
}

func introspectedEnumNames(response map[string]any) []string {
	values, _ := response["enumValues"].([]any)
	out := make([]string, 0, len(values))
	for _, raw := range values {
		out = append(out, raw.(map[string]any)["name"].(string))
	}
	sort.Strings(out)
	return out
}

func introspectedInputTypes(response map[string]any) map[string]string {
	result := make(map[string]string)
	fields, _ := response["inputFields"].([]any)
	for _, raw := range fields {
		field := raw.(map[string]any)
		typeRef := field["type"].(map[string]any)
		suffix := ""
		if typeRef["kind"] == "NON_NULL" {
			typeRef = typeRef["ofType"].(map[string]any)
			suffix = "!"
		}
		result[field["name"].(string)] = typeRef["name"].(string) + suffix
	}
	return result
}
