package graphql

import (
	"context"

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

func (s *Server) doViewer(ctx context.Context) map[string]any {
	u, err := s.Svc.GetCurrentUser(ctx)
	if err != nil {
		return errResp(err.Error())
	}
	return wrap("viewer", s.userGQL(ctx, u))
}

func (s *Server) doViewerWithOrgs(ctx context.Context) map[string]any {
	u, err := s.Svc.GetCurrentUser(ctx)
	if err != nil {
		return errResp(err.Error())
	}
	orgs, err := s.Svc.ListOrgs(ctx)
	logErr(ctx, "doViewerWithOrgs.ListOrgs", err)
	nodes := make([]any, len(orgs))
	for i, o := range orgs {
		nodes[i] = map[string]any{"login": o.Login, "name": o.Name}
	}
	viewer := s.userGQL(ctx, u)
	viewer["organizations"] = gqlConn(nodes)
	return wrap("viewer", viewer)
}

func (s *Server) doUserWithOrgs(ctx context.Context, req gqlRequest) map[string]any {
	login := strFrom(req.Variables, "user")
	if login == "" {
		login = strFrom(req.Variables, "login")
	}
	u, err := s.Svc.GetUser(ctx, login)
	if err != nil {
		return wrap("user", nil)
	}
	orgs, err := s.Svc.ListOrgsForUser(ctx, u.ID)
	logErr(ctx, "doUserWithOrgs.ListOrgs", err)
	nodes := make([]any, len(orgs))
	for i, o := range orgs {
		nodes[i] = map[string]any{"login": o.Login, "name": o.Name}
	}
	return wrap("user", map[string]any{
		"login":         u.Login,
		"organizations": gqlConn(nodes),
	})
}

func (s *Server) doUserOrgOwner(ctx context.Context, req gqlRequest) map[string]any {
	login := strFrom(req.Variables, "login")
	u, err := s.Svc.GetUser(ctx, login)
	if err != nil {
		return map[string]any{
			"data": map[string]any{"user": nil, "organization": nil},
			"errors": []any{
				map[string]any{"type": "NOT_FOUND", "path": []any{"user"}, "message": "not found"},
				map[string]any{"type": "NOT_FOUND", "path": []any{"organization"}, "message": "not found"},
			},
		}
	}

	node := map[string]any{
		"id":         gqlID(u.Type, u.ID),
		"login":      u.Login,
		"name":       u.Name,
		"__typename": u.Type,
	}

	if u.Type == db.TypeOrganization {
		return map[string]any{
			"data": map[string]any{"user": nil, "organization": node},
			"errors": []any{
				map[string]any{"type": "NOT_FOUND", "path": []any{"user"}, "message": "user not found"},
			},
		}
	}
	return map[string]any{
		"data": map[string]any{"user": node, "organization": nil},
		"errors": []any{
			map[string]any{"type": "NOT_FOUND", "path": []any{"organization"}, "message": "organization not found"},
		},
	}
}

func (s *Server) doRepositoryOwner(ctx context.Context, req gqlRequest) map[string]any {
	login := strFrom(req.Variables, "owner")
	if login == "" {
		// viewer case
		u, err := s.Svc.GetCurrentUser(ctx)
		if err != nil {
			return map[string]any{"data": map[string]any{"repositoryOwner": nil}}
		}
		login = u.Login
	}
	u, err := s.Svc.GetUser(ctx, login)
	if err != nil {
		return map[string]any{"data": map[string]any{"repositoryOwner": nil}}
	}
	repos, err := s.Svc.ListReposByOwnerID(ctx, u.ID)
	logErr(ctx, "doRepositoryOwner.ListReposByOwnerID", err)
	userNodes := s.allUserNodes(ctx)
	nodes := make([]any, len(repos))
	for i, r := range repos {
		nodes[i] = s.repoGQL(ctx, r, userNodes)
	}
	return map[string]any{"data": map[string]any{
		"repositoryOwner": map[string]any{
			"login":        u.Login,
			"repositories": gqlConn(nodes),
		},
	}}
}

// ─── __type introspection ────────────────────────────────────────────────────

// DoTypeIntrospection handles all __type introspection queries used by gh CLI
// for feature detection (IssueFeatures, PullRequestFeatures, etc.).
// types is a map of alias → type name extracted during parseQuery.
func DoTypeIntrospection(types map[string]string) map[string]any {
	data := make(map[string]any, len(types))
	for alias, typeName := range types {
		data[alias] = TypeFields(typeName)
	}
	return map[string]any{"data": data}
}

// TypeFields returns the introspection response for a given GraphQL type name.
// These match what GitHub's API returns so the CLI's feature detection works.
func TypeFields(typeName string) map[string]any {
	switch typeName {
	case "Issue":
		return FieldResp("id", "title", "body", "state", "stateReason", "number",
			"author", "labels", "assignees", "comments", "createdAt", "updatedAt",
			"closedAt", "url", "milestone", "projectCards", "projectItems",
			"reactionGroups", "isPinned", "locked", "repository")
	case "PullRequest":
		return FieldRespWithArgs(map[string][]string{
			"timelineItems": {"after", "before", "first", "itemTypes", "last", "since", "skip"},
		}, "id", "title", "body", "state", "number",
			"author", "labels", "assignees", "comments", "createdAt", "updatedAt",
			"closedAt", "mergedAt", "url", "headRefName", "baseRefName",
			"isDraft", "merged", "mergeable", "isInMergeQueue", "isMergeQueueEnabled", "stack", "stackEntry", "mergeQueue", "mergeQueueEntry",
			"reviewRequests", "reviews", "commits", "files",
			"headRefOid", "headRepository", "headRepositoryOwner",
			"isCrossRepository", "statusCheckRollup", "timelineItems", "repository")
	case "PullRequestStack":
		return FieldRespWithArgs(map[string][]string{"entries": {"after", "before", "first", "last"}},
			"id", "number", "size", "baseRefName", "entries")
	case "PullRequestStackEntry":
		return FieldResp("id", "position", "pullRequest", "stack")
	case "PullRequestStackEntryConnection", "MergeQueueEntryConnection":
		return FieldResp("edges", "nodes", "pageInfo", "totalCount")
	case "PullRequestTimelineItemsConnection":
		return FieldResp("edges", "filteredCount", "nodes", "pageCount", "pageInfo", "totalCount", "updatedAt")
	case "PullRequestStackEntryEdge", "MergeQueueEntryEdge":
		return FieldResp("cursor", "node")
	case "MergeQueue":
		return FieldRespWithArgs(map[string][]string{"entries": {"after", "before", "first", "last"}},
			"configuration", "entries", "id", "nextEntryEstimatedTimeToMerge", "repository", "resourcePath", "url")
	case "MergeQueueConfiguration":
		return FieldResp("checkResponseTimeout", "maximumEntriesToBuild", "maximumEntriesToMerge", "mergeMethod",
			"mergingStrategy", "minimumEntriesToMerge", "minimumEntriesToMergeWaitTime")
	case "MergeQueueEntry":
		return FieldResp("baseCommit", "enqueuedAt", "enqueuer", "estimatedTimeToMerge", "headCommit", "id",
			"jump", "mergeQueue", "position", "pullRequest", "solo", "state")
	case "AddedToMergeQueueEvent":
		return FieldResp("actor", "createdAt", "enqueuer", "id", "mergeQueue", "pullRequest")
	case "RemovedFromMergeQueueEvent":
		return FieldResp("actor", "beforeCommit", "createdAt", "enqueuer", "id", "mergeQueue", "pullRequest", "reason")
	case "MergeQueueEntryState":
		return EnumResp("AWAITING_CHECKS", "LOCKED", "MERGEABLE", "QUEUED", "UNMERGEABLE")
	case "MergeQueueGroupingStrategy", "MergeQueueMergingStrategy":
		return EnumResp("ALLGREEN", "HEADGREEN")
	case "MergeQueueMergeMethod":
		return EnumResp("MERGE", "REBASE", "SQUASH")
	case "MergeQueueParameters":
		return FieldResp("checkResponseTimeoutMinutes", "groupingStrategy", "maxEntriesToBuild", "maxEntriesToMerge",
			"mergeMethod", "minEntriesToMerge", "minEntriesToMergeWaitMinutes")
	case "MergeQueueParametersInput":
		return InputObjectResp(
			requiredInput("checkResponseTimeoutMinutes", "SCALAR", "Int"),
			requiredInput("groupingStrategy", "ENUM", "MergeQueueGroupingStrategy"),
			requiredInput("maxEntriesToBuild", "SCALAR", "Int"),
			requiredInput("maxEntriesToMerge", "SCALAR", "Int"),
			requiredInput("mergeMethod", "ENUM", "MergeQueueMergeMethod"),
			requiredInput("minEntriesToMerge", "SCALAR", "Int"),
			requiredInput("minEntriesToMergeWaitMinutes", "SCALAR", "Int"),
		)
	case "EnqueuePullRequestInput":
		return InputObjectResp(
			nullableInput("clientMutationId", "SCALAR", "String"),
			nullableInput("expectedHeadOid", "SCALAR", "GitObjectID"),
			nullableInput("jump", "SCALAR", "Boolean"),
			requiredInput("pullRequestId", "SCALAR", "ID"),
		)
	case "DequeuePullRequestInput":
		return InputObjectResp(
			nullableInput("clientMutationId", "SCALAR", "String"),
			requiredInput("id", "SCALAR", "ID"),
		)
	case "EnqueuePullRequestPayload", "DequeuePullRequestPayload":
		return FieldResp("clientMutationId", "mergeQueueEntry")
	case "StatusCheckRollupContextConnection":
		return FieldResp("checkRunCount", "checkRunCountsByState",
			"statusContextCount", "statusContextCountsByState")
	case "WorkflowRun":
		return FieldResp("id", "name", "status", "conclusion", "event",
			"createdAt", "updatedAt", "url", "workflow")
	case "Repository":
		return FieldResp("id", "name", "nameWithOwner", "description",
			"isPrivate", "isFork", "isArchived", "isEmpty",
			"defaultBranchRef", "owner", "url",
			"hasIssuesEnabled", "hasWikiEnabled", "hasProjectsEnabled",
			"pullRequestTemplates", "visibility", "autoMergeAllowed",
			"mergeCommitAllowed", "rebaseMergeAllowed", "squashMergeAllowed",
			"deleteBranchOnMerge", "viewerPermission", "vulnerabilityAlerts")
	case "RepositoryVulnerabilityAlert":
		return FieldResp("id", "number", "state", "createdAt", "dismissedAt",
			"dismissedReason", "fixedAt", "securityAdvisory", "securityVulnerability",
			"vulnerableManifestFilename", "vulnerableRequirements", "repository")
	case "ProjectV2":
		return FieldRespWithArgs(map[string][]string{
			"items": {"query", "first", "after"},
		}, "id", "title", "number", "shortDescription", "public",
			"closed", "readme", "url", "owner", "fields", "items")
	case "SearchType":
		return EnumResp("ISSUE", "ISSUE_ADVANCED", "REPOSITORY", "USER", "DISCUSSION")
	case "Release":
		return FieldResp("id", "name", "tagName", "description",
			"createdAt", "publishedAt", "url", "isDraft", "isPrerelease",
			"isLatest", "author", "immutable")
	case "LinkedBranch":
		return FieldResp("id", "ref")
	default:
		return FieldResp("id")
	}
}

// FieldResp builds a __type response with a list of field names.
func FieldResp(names ...string) map[string]any {
	fields := make([]any, len(names))
	for i, n := range names {
		fields[i] = map[string]any{"name": n}
	}
	return map[string]any{"kind": "OBJECT", "fields": fields}
}

// FieldRespWithArgs builds a __type response where some fields have args.
func FieldRespWithArgs(argsMap map[string][]string, names ...string) map[string]any {
	fields := make([]any, len(names))
	for i, n := range names {
		f := map[string]any{"name": n}
		if argNames, ok := argsMap[n]; ok {
			args := make([]any, len(argNames))
			for j, a := range argNames {
				args[j] = map[string]any{"name": a}
			}
			f["args"] = args
		}
		fields[i] = f
	}
	return map[string]any{"kind": "OBJECT", "fields": fields}
}

// EnumResp builds a __type response with enum values.
func EnumResp(names ...string) map[string]any {
	vals := make([]any, len(names))
	for i, n := range names {
		vals[i] = map[string]any{"name": n}
	}
	return map[string]any{"kind": "ENUM", "enumValues": vals}
}

type inputFieldSpec struct {
	name    string
	typeRef map[string]any
}

func nullableInput(name, kind, typeName string) inputFieldSpec {
	return inputFieldSpec{name: name, typeRef: map[string]any{"kind": kind, "name": typeName, "ofType": nil}}
}

func requiredInput(name, kind, typeName string) inputFieldSpec {
	return inputFieldSpec{name: name, typeRef: map[string]any{
		"kind": "NON_NULL", "name": nil,
		"ofType": map[string]any{"kind": kind, "name": typeName, "ofType": nil},
	}}
}

func InputObjectResp(fields ...inputFieldSpec) map[string]any {
	inputFields := make([]any, len(fields))
	for i, field := range fields {
		inputFields[i] = map[string]any{"name": field.name, "type": field.typeRef}
	}
	return map[string]any{"kind": "INPUT_OBJECT", "fields": nil, "inputFields": inputFields}
}
