package main

import (
	"context"
	"fmt"
	"os"
	"reflect"
	"sort"
	"strings"

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

// generateInsidersFeaturesDocs refreshes the auto-generated section of
// docs/insiders-features.md with the tools and schemas affected by each
// Insiders feature flag.
func generateInsidersFeaturesDocs(docsPath string) error {
	body := generateFlaggedToolsDoc(github.InsidersFeatureFlags, "_No Insiders-only tool changes._")
	return rewriteAutomatedSection(docsPath, "START AUTOMATED INSIDERS TOOLS", "END AUTOMATED INSIDERS TOOLS", body)
}

// generateFeatureFlagsDocs refreshes the auto-generated section of
// docs/feature-flags.md with the tools and schemas affected by each
// user-controllable feature flag.
func generateFeatureFlagsDocs(docsPath string) error {
	body := generateFlaggedToolsDoc(github.AllowedFeatureFlags, "_No user-controllable feature flags affect tool registration._")
	return rewriteAutomatedSection(docsPath, "START AUTOMATED FEATURE FLAG TOOLS", "END AUTOMATED FEATURE FLAG TOOLS", body)
}

// generateFlaggedToolsDoc renders, for each flag in the input set, the tools
// whose registration or definition differs from the default user experience.
// Each affected tool is printed with its full schema using the same writer
// used by the README so the output style stays consistent.
func generateFlaggedToolsDoc(flags []string, emptyMessage string) string {
	t, _ := translations.TranslationHelper()
	defaultTools := indexToolsByName(buildInventoryWithFlags(t, nil).ToolsForRegistration(context.Background()))

	var buf strings.Builder
	hasAny := false

	for _, flag := range flags {
		affected := flaggedToolDiff(t, flag, defaultTools)
		if len(affected) == 0 {
			continue
		}

		if hasAny {
			buf.WriteString("\n\n")
		}
		hasAny = true

		fmt.Fprintf(&buf, "### `%s`\n\n", flag)
		for i, tool := range affected {
			writeToolDoc(&buf, tool)
			if i < len(affected)-1 {
				buf.WriteString("\n\n")
			}
		}
	}

	if !hasAny {
		return emptyMessage
	}
	// Leading/trailing newlines around the body produce blank lines between
	// our content and the surrounding marker comments, so the trailing comment
	// doesn't get absorbed into the final list item by markdown renderers.
	return "\n" + strings.TrimSuffix(buf.String(), "\n") + "\n"
}

// flaggedToolDiff returns the tools whose definition (input schema or meta)
// differs from the default-flagged inventory when only the given flag is on,
// plus tools that exist only in the flag-on inventory. Results are sorted by
// tool name.
func flaggedToolDiff(t translations.TranslationHelperFunc, flag string, defaultTools map[string]inventory.ServerTool) []inventory.ServerTool {
	flagTools := buildInventoryWithFlags(t, map[string]bool{flag: true}).ToolsForRegistration(context.Background())

	out := make([]inventory.ServerTool, 0)
	seen := make(map[string]struct{}, len(flagTools))

	for _, tool := range flagTools {
		if _, ok := seen[tool.Tool.Name]; ok {
			continue
		}
		seen[tool.Tool.Name] = struct{}{}

		baseline, hadBaseline := defaultTools[tool.Tool.Name]
		if hadBaseline && reflect.DeepEqual(tool.Tool.InputSchema, baseline.Tool.InputSchema) && reflect.DeepEqual(tool.Tool.Meta, baseline.Tool.Meta) {
			continue
		}
		out = append(out, tool)
	}

	sort.Slice(out, func(i, j int) bool { return out[i].Tool.Name < out[j].Tool.Name })
	return out
}

// buildInventoryWithFlags constructs an inventory whose feature checker treats
// the given flags as enabled and every other flag as disabled. Passing nil
// produces the default-flagged inventory.
func buildInventoryWithFlags(t translations.TranslationHelperFunc, enabled map[string]bool) *inventory.Inventory {
	checker := func(_ context.Context, flag string) (bool, error) {
		return enabled[flag], nil
	}
	inv, _ := github.NewInventory(t).
		WithToolsets([]string{"all"}).
		WithFeatureChecker(checker).
		Build()
	return inv
}

// indexToolsByName returns a map keyed by tool name. When duplicates exist
// (e.g. flag-gated dual registrations), the first occurrence wins, mirroring
// AvailableTools' deterministic sort order.
func indexToolsByName(tools []inventory.ServerTool) map[string]inventory.ServerTool {
	out := make(map[string]inventory.ServerTool, len(tools))
	for _, tool := range tools {
		if _, ok := out[tool.Tool.Name]; ok {
			continue
		}
		out[tool.Tool.Name] = tool
	}
	return out
}

// rewriteAutomatedSection reads a markdown file, replaces the content between
// the named markers with body, and writes it back.
func rewriteAutomatedSection(path, startMarker, endMarker, body string) error {
	content, err := os.ReadFile(path) //#nosec G304
	if err != nil {
		return fmt.Errorf("failed to read docs file: %w", err)
	}
	updated, err := replaceSection(string(content), startMarker, endMarker, body)
	if err != nil {
		return err
	}
	return os.WriteFile(path, []byte(updated), 0600) //#nosec G306
}
