package github

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	ghErrors "github.com/github/github-mcp-server/pkg/errors"
	"github.com/github/github-mcp-server/pkg/ifc"
	"github.com/github/github-mcp-server/pkg/inventory"
	"github.com/github/github-mcp-server/pkg/scopes"
	"github.com/github/github-mcp-server/pkg/translations"
	"github.com/github/github-mcp-server/pkg/utils"
	"github.com/google/jsonschema-go/jsonschema"
	"github.com/modelcontextprotocol/go-sdk/mcp"
	"github.com/shurcooL/githubv4"
)

// GetLabel retrieves a specific label by name from a GitHub repository
func GetLabel(t translations.TranslationHelperFunc) inventory.ServerTool {
	return NewTool(
		ToolsetMetadataIssues,
		mcp.Tool{
			Name:        "get_label",
			Description: t("TOOL_GET_LABEL_DESCRIPTION", "Get a specific label from a repository."),
			Annotations: &mcp.ToolAnnotations{
				Title:        t("TOOL_GET_LABEL_TITLE", "Get a specific label from a repository"),
				ReadOnlyHint: true,
			},
			InputSchema: &jsonschema.Schema{
				Type: "object",
				Properties: map[string]*jsonschema.Schema{
					"owner": {
						Type:        "string",
						Description: "Repository owner (username or organization name)",
					},
					"repo": {
						Type:        "string",
						Description: "Repository name",
					},
					"name": {
						Type:        "string",
						Description: "Label name.",
					},
				},
				Required: []string{"owner", "repo", "name"},
			},
		},
		[]scopes.Scope{scopes.Repo},
		func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
			owner, err := RequiredParam[string](args, "owner")
			if err != nil {
				return utils.NewToolResultError(err.Error()), nil, nil
			}

			repo, err := RequiredParam[string](args, "repo")
			if err != nil {
				return utils.NewToolResultError(err.Error()), nil, nil
			}

			name, err := RequiredParam[string](args, "name")
			if err != nil {
				return utils.NewToolResultError(err.Error()), nil, nil
			}

			var query struct {
				Repository struct {
					Label struct {
						ID          githubv4.ID
						Name        githubv4.String
						Color       githubv4.String
						Description githubv4.String
					} `graphql:"label(name: $name)"`
				} `graphql:"repository(owner: $owner, name: $repo)"`
			}

			vars := map[string]any{
				"owner": githubv4.String(owner),
				"repo":  githubv4.String(repo),
				"name":  githubv4.String(name),
			}

			client, err := deps.GetGQLClient(ctx)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
			}

			if err := client.Query(ctx, &query, vars); err != nil {
				return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "Failed to find label", err), nil, nil
			}

			if query.Repository.Label.Name == "" {
				return utils.NewToolResultError(fmt.Sprintf("label '%s' not found in %s/%s", name, owner, repo)), nil, nil
			}

			label := map[string]any{
				"id":          fmt.Sprintf("%v", query.Repository.Label.ID),
				"name":        string(query.Repository.Label.Name),
				"color":       string(query.Repository.Label.Color),
				"description": string(query.Repository.Label.Description),
			}

			out, err := json.Marshal(label)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to marshal label: %w", err)
			}

			result := utils.NewToolResultText(string(out))
			// Labels are structural repo metadata defined by collaborators
			// (trusted); confidentiality follows repo visibility.
			result = attachRepoVisibilityIFCLabelLazy(ctx, deps, owner, repo, result, ifc.LabelRepoMetadata)
			return result, nil, nil
		},
	)
}

// GetLabelForLabelsToolset returns the same GetLabel tool but registered in the labels toolset.
// This provides conformance with the original behavior where get_label was in both toolsets.
func GetLabelForLabelsToolset(t translations.TranslationHelperFunc) inventory.ServerTool {
	tool := GetLabel(t)
	tool.Toolset = ToolsetLabels
	return tool
}

// ListLabels lists labels from a repository
func ListLabels(t translations.TranslationHelperFunc) inventory.ServerTool {
	return NewTool(
		ToolsetLabels,
		mcp.Tool{
			Name:        "list_label",
			Description: t("TOOL_LIST_LABEL_DESCRIPTION", "List labels from a repository"),
			Annotations: &mcp.ToolAnnotations{
				Title:        t("TOOL_LIST_LABEL_DESCRIPTION", "List labels from a repository"),
				ReadOnlyHint: true,
			},
			InputSchema: &jsonschema.Schema{
				Type: "object",
				Properties: map[string]*jsonschema.Schema{
					"owner": {
						Type:        "string",
						Description: "Repository owner (username or organization name) - required for all operations",
					},
					"repo": {
						Type:        "string",
						Description: "Repository name - required for all operations",
					},
				},
				Required: []string{"owner", "repo"},
			},
		},
		[]scopes.Scope{scopes.Repo},
		func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
			owner, err := RequiredParam[string](args, "owner")
			if err != nil {
				return utils.NewToolResultError(err.Error()), nil, nil
			}

			repo, err := RequiredParam[string](args, "repo")
			if err != nil {
				return utils.NewToolResultError(err.Error()), nil, nil
			}

			client, err := deps.GetGQLClient(ctx)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
			}

			var query struct {
				Repository struct {
					Labels struct {
						Nodes []struct {
							ID          githubv4.ID
							Name        githubv4.String
							Color       githubv4.String
							Description githubv4.String
						}
						TotalCount githubv4.Int
					} `graphql:"labels(first: 100)"`
				} `graphql:"repository(owner: $owner, name: $repo)"`
			}

			vars := map[string]any{
				"owner": githubv4.String(owner),
				"repo":  githubv4.String(repo),
			}

			if err := client.Query(ctx, &query, vars); err != nil {
				return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "Failed to list labels", err), nil, nil
			}

			labels := make([]map[string]any, len(query.Repository.Labels.Nodes))
			for i, labelNode := range query.Repository.Labels.Nodes {
				labels[i] = map[string]any{
					"id":          fmt.Sprintf("%v", labelNode.ID),
					"name":        string(labelNode.Name),
					"color":       string(labelNode.Color),
					"description": string(labelNode.Description),
				}
			}

			response := map[string]any{
				"labels":     labels,
				"totalCount": int(query.Repository.Labels.TotalCount),
			}

			out, err := json.Marshal(response)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to marshal labels: %w", err)
			}

			result := utils.NewToolResultText(string(out))
			// Labels are structural repo metadata defined by collaborators
			// (trusted); confidentiality follows repo visibility.
			result = attachRepoVisibilityIFCLabelLazy(ctx, deps, owner, repo, result, ifc.LabelRepoMetadata)
			return result, nil, nil
		},
	)
}

// LabelWrite handles create, update, and delete operations for GitHub labels
func LabelWrite(t translations.TranslationHelperFunc) inventory.ServerTool {
	return NewTool(
		ToolsetLabels,
		mcp.Tool{
			Name:        "label_write",
			Description: t("TOOL_LABEL_WRITE_DESCRIPTION", "Perform write operations on repository labels. To set labels on issues, use the 'update_issue' tool."),
			Annotations: &mcp.ToolAnnotations{
				Title:        t("TOOL_LABEL_WRITE_TITLE", "Write operations on repository labels"),
				ReadOnlyHint: false,
			},
			InputSchema: &jsonschema.Schema{
				Type: "object",
				Properties: map[string]*jsonschema.Schema{
					"method": {
						Type:        "string",
						Description: "Operation to perform: 'create', 'update', or 'delete'",
						Enum:        []any{"create", "update", "delete"},
					},
					"owner": {
						Type:        "string",
						Description: "Repository owner (username or organization name)",
					},
					"repo": {
						Type:        "string",
						Description: "Repository name",
					},
					"name": {
						Type:        "string",
						Description: "Label name - required for all operations",
					},
					"new_name": {
						Type:        "string",
						Description: "New name for the label (used only with 'update' method to rename)",
					},
					"color": {
						Type:        "string",
						Description: "Label color as 6-character hex code without '#' prefix (e.g., 'f29513'). Required for 'create', optional for 'update'.",
					},
					"description": {
						Type:        "string",
						Description: "Label description text. Optional for 'create' and 'update'.",
					},
				},
				Required: []string{"method", "owner", "repo", "name"},
			},
		},
		[]scopes.Scope{scopes.Repo},
		func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
			// Get and validate required parameters
			method, err := RequiredParam[string](args, "method")
			if err != nil {
				return utils.NewToolResultError(err.Error()), nil, nil
			}
			method = strings.ToLower(method)

			owner, err := RequiredParam[string](args, "owner")
			if err != nil {
				return utils.NewToolResultError(err.Error()), nil, nil
			}

			repo, err := RequiredParam[string](args, "repo")
			if err != nil {
				return utils.NewToolResultError(err.Error()), nil, nil
			}

			name, err := RequiredParam[string](args, "name")
			if err != nil {
				return utils.NewToolResultError(err.Error()), nil, nil
			}

			// Get optional parameters
			newName, _ := OptionalParam[string](args, "new_name")
			color, _ := OptionalParam[string](args, "color")
			description, _ := OptionalParam[string](args, "description")

			client, err := deps.GetGQLClient(ctx)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
			}

			switch method {
			case "create":
				// Validate required params for create
				if color == "" {
					return utils.NewToolResultError("color is required for create"), nil, nil
				}

				// Get repository ID
				repoID, err := getRepositoryID(ctx, client, owner, repo)
				if err != nil {
					return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "Failed to find repository", err), nil, nil
				}

				input := githubv4.CreateLabelInput{
					RepositoryID: repoID,
					Name:         githubv4.String(name),
					Color:        githubv4.String(color),
				}
				if description != "" {
					d := githubv4.String(description)
					input.Description = &d
				}

				var mutation struct {
					CreateLabel struct {
						Label struct {
							Name githubv4.String
							ID   githubv4.ID
						}
					} `graphql:"createLabel(input: $input)"`
				}

				if err := client.Mutate(ctx, &mutation, input, nil); err != nil {
					return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "Failed to create label", err), nil, nil
				}

				return utils.NewToolResultText(fmt.Sprintf("label '%s' created successfully", mutation.CreateLabel.Label.Name)), nil, nil

			case "update":
				// Validate required params for update
				if newName == "" && color == "" && description == "" {
					return utils.NewToolResultError("at least one of new_name, color, or description must be provided for update"), nil, nil
				}

				// Get the label ID
				labelID, err := getLabelID(ctx, client, owner, repo, name)
				if err != nil {
					return utils.NewToolResultError(err.Error()), nil, nil
				}

				input := githubv4.UpdateLabelInput{
					ID: labelID,
				}
				if newName != "" {
					n := githubv4.String(newName)
					input.Name = &n
				}
				if color != "" {
					c := githubv4.String(color)
					input.Color = &c
				}
				if description != "" {
					d := githubv4.String(description)
					input.Description = &d
				}

				var mutation struct {
					UpdateLabel struct {
						Label struct {
							Name githubv4.String
							ID   githubv4.ID
						}
					} `graphql:"updateLabel(input: $input)"`
				}

				if err := client.Mutate(ctx, &mutation, input, nil); err != nil {
					return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "Failed to update label", err), nil, nil
				}

				return utils.NewToolResultText(fmt.Sprintf("label '%s' updated successfully", mutation.UpdateLabel.Label.Name)), nil, nil

			case "delete":
				// Get the label ID
				labelID, err := getLabelID(ctx, client, owner, repo, name)
				if err != nil {
					return utils.NewToolResultError(err.Error()), nil, nil
				}

				input := githubv4.DeleteLabelInput{
					ID: labelID,
				}

				var mutation struct {
					DeleteLabel struct {
						ClientMutationID githubv4.String
					} `graphql:"deleteLabel(input: $input)"`
				}

				if err := client.Mutate(ctx, &mutation, input, nil); err != nil {
					return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "Failed to delete label", err), nil, nil
				}

				return utils.NewToolResultText(fmt.Sprintf("label '%s' deleted successfully", name)), nil, nil

			default:
				return utils.NewToolResultError(fmt.Sprintf("unknown method: %s. Supported methods are: create, update, delete", method)), nil, nil
			}
		},
	)
}

// Helper function to get repository ID
func getRepositoryID(ctx context.Context, client *githubv4.Client, owner, repo string) (githubv4.ID, error) {
	var repoQuery struct {
		Repository struct {
			ID githubv4.ID
		} `graphql:"repository(owner: $owner, name: $repo)"`
	}
	vars := map[string]any{
		"owner": githubv4.String(owner),
		"repo":  githubv4.String(repo),
	}
	if err := client.Query(ctx, &repoQuery, vars); err != nil {
		return "", err
	}
	return repoQuery.Repository.ID, nil
}

// Helper function to get label by name
func getLabelID(ctx context.Context, client *githubv4.Client, owner, repo, labelName string) (githubv4.ID, error) {
	var query struct {
		Repository struct {
			Label struct {
				ID   githubv4.ID
				Name githubv4.String
			} `graphql:"label(name: $name)"`
		} `graphql:"repository(owner: $owner, name: $repo)"`
	}
	vars := map[string]any{
		"owner": githubv4.String(owner),
		"repo":  githubv4.String(repo),
		"name":  githubv4.String(labelName),
	}
	if err := client.Query(ctx, &query, vars); err != nil {
		return "", err
	}
	if query.Repository.Label.Name == "" {
		return "", fmt.Errorf("label '%s' not found in %s/%s", labelName, owner, repo)
	}
	return query.Repository.Label.ID, nil
}
