package github

import (
	"context"
	"slices"
	"strings"

	"github.com/google/go-github/v89/github"
	"github.com/shurcooL/githubv4"

	"github.com/github/github-mcp-server/pkg/inventory"
	"github.com/github/github-mcp-server/pkg/translations"
)

type GetClientFn func(context.Context) (*github.Client, error)
type GetGQLClientFn func(context.Context) (*githubv4.Client, error)

// Toolset metadata constants - these define all available toolsets and their descriptions.
// Tools use these constants to declare which toolset they belong to.
// Icons are Octicon names from https://primer.style/foundations/icons
var (
	ToolsetMetadataAll = inventory.ToolsetMetadata{
		ID:          "all",
		Description: "Special toolset that enables all available toolsets",
		Icon:        "apps",
	}
	ToolsetMetadataDefault = inventory.ToolsetMetadata{
		ID:          "default",
		Description: "Special toolset that enables the default toolset configuration. When no toolsets are specified, this is the set that is enabled",
		Icon:        "check-circle",
	}
	ToolsetMetadataContext = inventory.ToolsetMetadata{
		ID:               "context",
		Description:      "Tools that provide context about the current user and GitHub context you are operating in",
		Default:          true,
		Icon:             "person",
		InstructionsFunc: generateContextToolsetInstructions,
	}
	ToolsetMetadataRepos = inventory.ToolsetMetadata{
		ID:          "repos",
		Description: "GitHub Repository related tools",
		Default:     true,
		Icon:        "repo",
	}
	ToolsetMetadataGit = inventory.ToolsetMetadata{
		ID:          "git",
		Description: "GitHub Git API related tools for low-level Git operations",
		Icon:        "git-branch",
	}
	ToolsetMetadataIssues = inventory.ToolsetMetadata{
		ID:               "issues",
		Description:      "GitHub Issues related tools",
		Default:          true,
		Icon:             "issue-opened",
		InstructionsFunc: generateIssuesToolsetInstructions,
	}
	ToolsetMetadataPullRequests = inventory.ToolsetMetadata{
		ID:               "pull_requests",
		Description:      "GitHub Pull Request related tools",
		Default:          true,
		Icon:             "git-pull-request",
		InstructionsFunc: generatePullRequestsToolsetInstructions,
	}
	ToolsetMetadataUsers = inventory.ToolsetMetadata{
		ID:          "users",
		Description: "GitHub User related tools",
		Default:     true,
		Icon:        "people",
	}
	ToolsetMetadataOrgs = inventory.ToolsetMetadata{
		ID:          "orgs",
		Description: "GitHub Organization related tools",
		Icon:        "organization",
	}
	ToolsetMetadataActions = inventory.ToolsetMetadata{
		ID:          "actions",
		Description: "GitHub Actions workflows and CI/CD operations",
		Icon:        "workflow",
	}
	ToolsetMetadataCodeQuality = inventory.ToolsetMetadata{
		ID:          "code_quality",
		Description: "GitHub Code Quality related tools",
		Icon:        "code-square",
	}
	ToolsetMetadataCodeSecurity = inventory.ToolsetMetadata{
		ID:          "code_security",
		Description: "Code security related tools, such as GitHub Code Scanning",
		Icon:        "codescan",
	}
	ToolsetMetadataSecretProtection = inventory.ToolsetMetadata{
		ID:          "secret_protection",
		Description: "Secret protection related tools, such as GitHub Secret Scanning",
		Icon:        "shield-lock",
	}
	ToolsetMetadataDependabot = inventory.ToolsetMetadata{
		ID:          "dependabot",
		Description: "Dependabot tools",
		Icon:        "dependabot",
	}
	ToolsetMetadataNotifications = inventory.ToolsetMetadata{
		ID:          "notifications",
		Description: "GitHub Notifications related tools",
		Icon:        "bell",
	}
	ToolsetMetadataDiscussions = inventory.ToolsetMetadata{
		ID:               "discussions",
		Description:      "GitHub Discussions related tools",
		Icon:             "comment-discussion",
		InstructionsFunc: generateDiscussionsToolsetInstructions,
	}
	ToolsetMetadataGists = inventory.ToolsetMetadata{
		ID:          "gists",
		Description: "GitHub Gist related tools",
		Icon:        "logo-gist",
	}
	ToolsetMetadataSecurityAdvisories = inventory.ToolsetMetadata{
		ID:          "security_advisories",
		Description: "Security advisories related tools",
		Icon:        "shield",
	}
	ToolsetMetadataProjects = inventory.ToolsetMetadata{
		ID:               "projects",
		Description:      "GitHub Projects related tools",
		Icon:             "project",
		InstructionsFunc: generateProjectsToolsetInstructions,
	}
	ToolsetMetadataStargazers = inventory.ToolsetMetadata{
		ID:          "stargazers",
		Description: "GitHub Stargazers related tools",
		Icon:        "star",
	}
	ToolsetLabels = inventory.ToolsetMetadata{
		ID:          "labels",
		Description: "GitHub Labels related tools",
		Icon:        "tag",
	}

	ToolsetMetadataCopilot = inventory.ToolsetMetadata{
		ID:          "copilot",
		Description: "Copilot related tools",
		Default:     true,
		Icon:        "copilot",
	}

	// Feature flag names for granular tool variants.
	// When active, consolidated tools are replaced by single-purpose granular tools.
	FeatureFlagIssuesGranular       = "issues_granular"
	FeatureFlagPullRequestsGranular = "pull_requests_granular"
)

// HeaderAllowedFeatureFlags returns the feature flags that clients may enable via
// the X-MCP-Features header. It delegates to AllowedFeatureFlags as the single
// source of truth.
func HeaderAllowedFeatureFlags() []string {
	return slices.Clone(AllowedFeatureFlags)
}

var (
	// Remote-only toolsets - these are only available in the remote MCP server
	// but are documented here for consistency and to enable automated documentation.
	ToolsetMetadataCopilotSpaces = inventory.ToolsetMetadata{
		ID:          "copilot_spaces",
		Description: "Copilot Spaces tools",
		Icon:        "copilot",
	}
	ToolsetMetadataSupportSearch = inventory.ToolsetMetadata{
		ID:          "github_support_docs_search",
		Description: "Retrieve documentation to answer GitHub product and support questions. Topics include: GitHub Actions Workflows, Authentication, ...",
		Icon:        "book",
	}
)

// AllTools returns all tools with their embedded toolset metadata.
// Tool functions return ServerTool directly with toolset info.
func AllTools(t translations.TranslationHelperFunc) []inventory.ServerTool {
	return withCSVOutput([]inventory.ServerTool{
		// Context tools
		GetMe(t),
		GetTeams(t),
		GetTeamMembers(t),

		// Repository tools
		SearchRepositories(t),
		GetFileContents(t),
		LegacyGetFileContents(t),
		ListCommits(t),
		LegacyListCommits(t),
		SearchCode(t),
		LegacySearchCode(t),
		SearchCommits(t),
		GetCommit(t),
		GetFileBlame(t),
		ListBranches(t),
		ListTags(t),
		GetTag(t),
		ListReleases(t),
		LegacyListReleases(t),
		GetLatestRelease(t),
		GetReleaseByTag(t),
		CreateOrUpdateFile(t),
		CreateRepository(t),
		ForkRepository(t),
		CreateBranch(t),
		PushFiles(t),
		DeleteFile(t),
		ListStarredRepositories(t),
		StarRepository(t),
		UnstarRepository(t),
		ListRepositoryCollaborators(t),

		// Git tools
		GetRepositoryTree(t),

		// Issue tools
		IssueRead(t),
		SearchIssues(t),
		LegacySearchIssues(t),
		ListIssues(t),
		LegacyListIssues(t),
		ListIssueTypes(t),
		ListIssueFields(t),
		IssueWrite(t),
		AddIssueComment(t),
		SubIssueWrite(t),
		IssueDependencyRead(t),
		IssueDependencyWrite(t),

		// User tools
		SearchUsers(t),

		// Organization tools
		SearchOrgs(t),

		// Pull request tools
		PullRequestRead(t),
		ListPullRequests(t),
		LegacyListPullRequests(t),
		SearchPullRequests(t),
		LegacySearchPullRequests(t),
		MergePullRequest(t),
		UpdatePullRequestBranch(t),
		CreatePullRequest(t),
		UpdatePullRequest(t),
		PullRequestReviewWrite(t),
		AddCommentToPendingReview(t),
		AddReplyToPullRequestComment(t),

		// Copilot tools
		AssignCopilotToIssue(t),
		RequestCopilotReview(t),

		// Code quality tools
		GetCodeQualityFinding(t),

		// Code security tools
		GetCodeScanningAlert(t),
		ListCodeScanningAlerts(t),

		// Secret protection tools
		GetSecretScanningAlert(t),
		ListSecretScanningAlerts(t),

		// Dependabot tools
		GetDependabotAlert(t),
		ListDependabotAlerts(t),

		// Notification tools
		ListNotifications(t),
		GetNotificationDetails(t),
		DismissNotification(t),
		MarkAllNotificationsRead(t),
		ManageNotificationSubscription(t),
		ManageRepositoryNotificationSubscription(t),

		// Discussion tools
		ListDiscussions(t),
		GetDiscussion(t),
		GetDiscussionComments(t),
		DiscussionCommentWrite(t),
		ListDiscussionCategories(t),

		// Actions tools
		ActionsList(t),
		ActionsGet(t),
		ActionsRunTrigger(t),
		ActionsGetJobLogs(t),

		// Security advisories tools
		ListGlobalSecurityAdvisories(t),
		GetGlobalSecurityAdvisory(t),
		ListRepositorySecurityAdvisories(t),
		ListOrgRepositorySecurityAdvisories(t),

		// Gist tools
		ListGists(t),
		GetGist(t),
		CreateGist(t),
		UpdateGist(t),

		// Project tools
		ProjectsList(t),
		ProjectsGet(t),
		ProjectsWrite(t),

		// Label tools
		GetLabel(t),
		GetLabelForLabelsToolset(t),
		ListLabels(t),
		LabelWrite(t),

		// UI tools (insiders only)
		UIGet(t),

		// Granular issue tools (feature-flagged, replace consolidated issue_write/sub_issue_write)
		GranularCreateIssue(t),
		GranularUpdateIssueTitle(t),
		GranularUpdateIssueBody(t),
		GranularUpdateIssueAssignees(t),
		GranularUpdateIssueLabels(t),
		GranularUpdateIssueMilestone(t),
		GranularUpdateIssueType(t),
		GranularUpdateIssueState(t),
		GranularAddSubIssue(t),
		GranularRemoveSubIssue(t),
		GranularReprioritizeSubIssue(t),
		GranularSetIssueFields(t),
		GranularAddIssueReaction(t),
		GranularAddIssueCommentReaction(t),

		// Granular pull request tools (feature-flagged, replace consolidated update_pull_request/pull_request_review_write)
		GranularUpdatePullRequestTitle(t),
		GranularUpdatePullRequestBody(t),
		GranularUpdatePullRequestState(t),
		GranularUpdatePullRequestDraftState(t),
		GranularRequestPullRequestReviewers(t),
		GranularCreatePullRequestReview(t),
		GranularSubmitPendingPullRequestReview(t),
		GranularDeletePendingPullRequestReview(t),
		GranularAddPullRequestReviewComment(t),
		GranularResolveReviewThread(t),
		GranularUnresolveReviewThread(t),
		GranularAddPullRequestReviewCommentReaction(t),
	})
}

// ToBoolPtr converts a bool to a *bool pointer.
func ToBoolPtr(b bool) *bool {
	return &b
}

// ToStringPtr converts a string to a *string pointer.
// Returns nil if the string is empty.
func ToStringPtr(s string) *string {
	if s == "" {
		return nil
	}
	return &s
}

// GenerateToolsetsHelp generates the help text for the toolsets flag
func GenerateToolsetsHelp() string {
	// Get toolset group to derive defaults and available toolsets
	// Build() can only fail if WithTools specifies invalid tools - not used here
	r, _ := NewInventory(stubTranslator).Build()

	// Format default tools from metadata using strings.Builder
	var defaultBuf strings.Builder
	defaultIDs := r.DefaultToolsetIDs()
	for i, id := range defaultIDs {
		if i > 0 {
			defaultBuf.WriteString(", ")
		}
		defaultBuf.WriteString(string(id))
	}

	// Get all available toolsets (excludes context for display)
	allToolsets := r.AvailableToolsets("context")
	var availableBuf strings.Builder
	const maxLineLength = 70
	currentLine := ""

	for i, toolset := range allToolsets {
		id := string(toolset.ID)
		switch {
		case i == 0:
			currentLine = id
		case len(currentLine)+len(id)+2 <= maxLineLength:
			currentLine += ", " + id
		default:
			if availableBuf.Len() > 0 {
				availableBuf.WriteString(",\n\t     ")
			}
			availableBuf.WriteString(currentLine)
			currentLine = id
		}
	}
	if currentLine != "" {
		if availableBuf.Len() > 0 {
			availableBuf.WriteString(",\n\t     ")
		}
		availableBuf.WriteString(currentLine)
	}

	// Build the complete help text using strings.Builder
	var buf strings.Builder
	buf.WriteString("Comma-separated list of tool groups to enable (no spaces).\n")
	buf.WriteString("Available: ")
	buf.WriteString(availableBuf.String())
	buf.WriteString("\n")
	buf.WriteString("Special toolset keywords:\n")
	buf.WriteString("  - all: Enables all available toolsets\n")
	buf.WriteString("  - default: Enables the default toolset configuration of:\n\t     ")
	buf.WriteString(defaultBuf.String())
	buf.WriteString("\n")
	buf.WriteString("Examples:\n")
	buf.WriteString("  - --toolsets=actions,gists,notifications\n")
	buf.WriteString("  - Default + additional: --toolsets=default,actions,gists\n")
	buf.WriteString("  - All tools: --toolsets=all")

	return buf.String()
}

// stubTranslator is a passthrough translator for cases where we need an Inventory
// but don't need actual translations (e.g., getting toolset IDs for CLI help).
func stubTranslator(_, fallback string) string { return fallback }

// AddDefaultToolset removes the default toolset and expands it to the actual default toolset IDs
func AddDefaultToolset(result []string) []string {
	hasDefault := false
	seen := make(map[string]bool)
	for _, toolset := range result {
		seen[toolset] = true
		if toolset == string(ToolsetMetadataDefault.ID) {
			hasDefault = true
		}
	}

	// Only expand if "default" keyword was found
	if !hasDefault {
		return result
	}

	result = RemoveToolset(result, string(ToolsetMetadataDefault.ID))

	// Get default toolset IDs from the Inventory
	// Build() can only fail if WithTools specifies invalid tools - not used here
	r, _ := NewInventory(stubTranslator).Build()
	for _, id := range r.DefaultToolsetIDs() {
		if !seen[string(id)] {
			result = append(result, string(id))
		}
	}
	return result
}

func RemoveToolset(tools []string, toRemove string) []string {
	result := make([]string, 0, len(tools))
	for _, tool := range tools {
		if tool != toRemove {
			result = append(result, tool)
		}
	}
	return result
}

func ContainsToolset(tools []string, toCheck string) bool {
	return slices.Contains(tools, toCheck)
}

// CleanTools cleans tool names by removing duplicates and trimming whitespace.
// Validation of tool existence is done during registration.
func CleanTools(toolNames []string) []string {
	seen := make(map[string]bool)
	result := make([]string, 0, len(toolNames))

	// Remove duplicates and trim whitespace
	for _, tool := range toolNames {
		trimmed := strings.TrimSpace(tool)
		if trimmed == "" {
			continue
		}
		if !seen[trimmed] {
			seen[trimmed] = true
			result = append(result, trimmed)
		}
	}

	return result
}

// GetDefaultToolsetIDs returns the IDs of toolsets marked as Default.
// This is a convenience function that builds an inventory to determine defaults.
func GetDefaultToolsetIDs() []string {
	// Build() can only fail if WithTools specifies invalid tools - not used here
	r, _ := NewInventory(stubTranslator).Build()
	ids := r.DefaultToolsetIDs()
	result := make([]string, len(ids))
	for i, id := range ids {
		result[i] = string(id)
	}
	return result
}

// RemoteOnlyToolsets returns toolset metadata for toolsets that are only
// available in the remote MCP server. These are documented but not registered
// in the local server.
func RemoteOnlyToolsets() []inventory.ToolsetMetadata {
	return []inventory.ToolsetMetadata{
		ToolsetMetadataCopilotSpaces,
		ToolsetMetadataSupportSearch,
	}
}
