package registry

import (
	"os"
	"sort"
	"strings"
	"sync"

	"github.com/sammcj/mcp-devtools/internal/tools"
	"github.com/sirupsen/logrus"
)

// defaultToolSet is the pre-computed set of tools enabled by default (normalised names).
// Built once at package init time to avoid repeated linear scans + string normalisation.
var defaultToolSet = func() map[string]bool {
	defaults := []string{
		"calculator",
		"fetch-url",
		"get-library-documentation",
		"get-tool-help",
		"internet-search",
		"resolve-library-id",
		"search-packages",
		"sequential-thinking",
		"think",
	}
	m := make(map[string]bool, len(defaults))
	for _, name := range defaults {
		m[name] = true
	}
	return m
}()

// toolNameAliases maps normalised tool names to additional accepted aliases.
var toolNameAliases = map[string][]string{
	"shadcn": {"shadcn-ui"},
}

var (
	// toolRegistry is a map of tool names to tool implementations.
	// Protected by registryMu for concurrent access (async proxy registration).
	toolRegistry = make(map[string]tools.Tool)
	registryMu   sync.RWMutex

	// proxiedTools tracks tools proxied from upstream MCP servers
	proxiedTools = make(map[string]bool)

	// disabledTools is a set of tool names to disable
	disabledTools = make(map[string]bool)

	// enabledAdditionalTools is the cached, parsed set of tools from ENABLE_ADDITIONAL_TOOLS.
	// Parsed lazily on first access via sync.Once to ensure it's available during init().
	enabledAdditionalTools map[string]bool
	// enableAllTools is true when ENABLE_ADDITIONAL_TOOLS is set to "all".
	enableAllTools        bool
	enabledToolsParseOnce sync.Once

	// logger is the shared logger instance
	logger *logrus.Logger

	// cache is the shared cache instance
	cache *sync.Map
)

// normaliseName converts a tool name to its canonical form (lowercase, hyphens).
func normaliseName(name string) string {
	return strings.ToLower(strings.ReplaceAll(strings.TrimSpace(name), "_", "-"))
}

// isToolDisabled checks if a tool is in the disabled set, normalising the name for lookup.
func isToolDisabled(toolName string) bool {
	return disabledTools[normaliseName(toolName)]
}

// Init initialises the registry and shared resources.
// Also re-parses environment variables, which allows tests to update env vars
// between calls to Init().
func Init(l *logrus.Logger) {
	logger = l
	cache = &sync.Map{}

	// Parse DISABLED_TOOLS environment variable
	parseDisabledTools()

	// Reset and re-parse ENABLE_ADDITIONAL_TOOLS (supports test re-initialisation)
	enabledToolsParseOnce = sync.Once{}
	ensureEnabledToolsParsed()
}

// parseDisabledTools parses the DISABLED_TOOLS and DISABLED_FUNCTIONS (legacy) environment variables
func parseDisabledTools() {
	// Clear the map first to ensure we start fresh
	disabledTools = make(map[string]bool)

	disabledEnv := os.Getenv("DISABLED_TOOLS")
	legacyEnv := os.Getenv("DISABLED_FUNCTIONS")

	// Helper function to parse and add tools to the disabled set
	parseAndAdd := func(envValue, source string) {
		if envValue == "" {
			return
		}

		tools := strings.SplitSeq(envValue, ",")
		for tool := range tools {
			tool = strings.TrimSpace(tool)
			if tool != "" {
				normalised := normaliseName(tool)
				disabledTools[normalised] = true
				if logger != nil {
					logger.WithField("tool", normalised).WithField("source", source).Debug("Tool disabled")
				}
			}
		}
	}

	// Parse legacy env var first (if set, warn about deprecation)
	if legacyEnv != "" {
		if logger != nil {
			logger.Warn("DISABLED_FUNCTIONS environment variable is deprecated, please use DISABLED_TOOLS instead")
		}
		parseAndAdd(legacyEnv, "DISABLED_FUNCTIONS")
	}

	// Parse current env var (can override or add to legacy)
	parseAndAdd(disabledEnv, "DISABLED_TOOLS")

	if logger != nil && len(disabledTools) > 0 {
		logger.WithField("count", len(disabledTools)).Debug("Parsed disabled tools from environment")
	}
}

// ensureEnabledToolsParsed lazily parses ENABLE_ADDITIONAL_TOOLS on first access.
// This is safe to call during init() -- the sync.Once ensures it runs exactly once
// until Init() resets the Once for test re-initialisation.
func ensureEnabledToolsParsed() {
	enabledToolsParseOnce.Do(parseEnabledTools)
}

// parseEnabledTools parses and caches the ENABLE_ADDITIONAL_TOOLS environment variable.
func parseEnabledTools() {
	enabledAdditionalTools = make(map[string]bool)
	enableAllTools = false

	envVal := os.Getenv("ENABLE_ADDITIONAL_TOOLS")
	if envVal == "" {
		return
	}

	if strings.TrimSpace(strings.ToLower(envVal)) == "all" {
		enableAllTools = true
		if logger != nil {
			logger.Debug("All additional tools enabled via ENABLE_ADDITIONAL_TOOLS=all")
		}
		return
	}

	for tool := range strings.SplitSeq(envVal, ",") {
		normalised := normaliseName(tool)
		if normalised != "" {
			enabledAdditionalTools[normalised] = true
		}
	}

	if logger != nil && len(enabledAdditionalTools) > 0 {
		logger.WithField("count", len(enabledAdditionalTools)).Debug("Parsed enabled additional tools from environment")
	}
}

// enabledByDefault checks if a tool is enabled by default without requiring ENABLE_ADDITIONAL_TOOLS.
// Tools NOT in this list require explicit enablement via ENABLE_ADDITIONAL_TOOLS.
// This follows the principle of secure-by-default: tools must be explicitly blessed to be enabled.
func enabledByDefault(toolName string) bool {
	return defaultToolSet[normaliseName(toolName)]
}

// requiresEnablement checks if a tool requires explicit enablement via ENABLE_ADDITIONAL_TOOLS.
// This is the inverse of enabledByDefault - any tool not enabled by default requires enablement.
func requiresEnablement(toolName string) bool {
	return !enabledByDefault(toolName)
}

// ShouldRegisterTool checks if a tool should be registered based on:
// 1. DISABLED_TOOLS or DISABLED_FUNCTIONS (legacy) - explicit disable, highest priority
// 2. Tool's enablement requirement
// 3. ENABLE_ADDITIONAL_TOOLS (explicit enable)
func ShouldRegisterTool(toolName string) bool {
	// Check DISABLED_TOOLS/DISABLED_FUNCTIONS first (explicit disable wins)
	if isToolDisabled(toolName) {
		if logger != nil {
			logger.WithField("tool", toolName).Debug("Tool disabled via environment variable")
		}
		return false
	}

	// Check if tool requires enablement
	if requiresEnablement(toolName) {
		// Must be explicitly enabled
		enabled := isToolEnabled(toolName)
		if logger != nil {
			if enabled {
				logger.WithField("tool", toolName).Debug("Tool enabled via ENABLE_ADDITIONAL_TOOLS")
			} else {
				logger.WithField("tool", toolName).Debug("Tool requires enablement but is not enabled")
			}
		}
		return enabled
	}

	// Enabled by default
	if logger != nil {
		logger.WithField("tool", toolName).Debug("Tool enabled by default")
	}
	return true
}

// Register adds a tool implementation to the registry if it should be registered
func Register(tool tools.Tool) {
	if toolRegistry == nil {
		toolRegistry = make(map[string]tools.Tool)
	}

	toolName := tool.Definition().Name

	// Check if tool should be registered
	if !ShouldRegisterTool(toolName) {
		if logger != nil {
			logger.WithField("tool", toolName).Debug("Tool not registered (disabled or requires enablement)")
		}
		return
	}

	toolRegistry[toolName] = tool
	if logger != nil {
		logger.WithField("tool", toolName).Debug("Tool successfully registered")
	}
}

// RegisterProxiedTool adds a tool proxied from an upstream MCP server to the registry.
// Only called if `proxy` tool is enabled via ENABLE_ADDITIONAL_TOOLS and configured with upstreams.
// This is used for tools discovered from upstream proxy servers. The caller (RegisterUpstreamTools)
// is responsible for checking that proxy is enabled before calling this function.
// Proxied tools bypass the normal ENABLE_ADDITIONAL_TOOLS check but still respect DISABLED_TOOLS.
// Safe to call concurrently with GetTool (protected by registryMu).
func RegisterProxiedTool(tool tools.Tool) {
	registryMu.Lock()
	defer registryMu.Unlock()

	if toolRegistry == nil {
		toolRegistry = make(map[string]tools.Tool)
	}

	toolName := tool.Definition().Name

	// Check if explicitly disabled (always respect DISABLED_TOOLS)
	if isToolDisabled(toolName) {
		if logger != nil {
			logger.WithField("tool", toolName).Debug("Proxied tool not registered (explicitly disabled)")
		}
		return
	}

	toolRegistry[toolName] = tool
	proxiedTools[toolName] = true // Mark as proxied from upstream
	if logger != nil {
		logger.WithField("tool", toolName).Debug("Proxied tool registered (bypassed enablement check)")
	}
}

// GetTool retrieves a tool by name, returns false if disabled.
// Safe to call concurrently with RegisterProxiedTool (protected by registryMu).
func GetTool(name string) (tools.Tool, bool) {
	// Check if tool is disabled
	if isToolDisabled(name) {
		return nil, false
	}
	registryMu.RLock()
	tool, ok := toolRegistry[name]
	registryMu.RUnlock()
	return tool, ok
}

// GetTools returns all registered tools, excluding disabled ones
func GetTools() map[string]tools.Tool {
	registryMu.RLock()
	filteredTools := make(map[string]tools.Tool, len(toolRegistry))
	for name, tool := range toolRegistry {
		// Skip disabled tools
		if isToolDisabled(name) {
			continue
		}
		filteredTools[name] = tool
	}
	registryMu.RUnlock()
	return filteredTools
}

// GetEnabledTools returns all tools that are enabled for MCP server registration
func GetEnabledTools() map[string]tools.Tool {
	registryMu.RLock()
	filteredTools := make(map[string]tools.Tool, len(toolRegistry))
	for name, tool := range toolRegistry {
		// Skip disabled tools
		if isToolDisabled(name) {
			continue
		}

		// Include proxied tools (bypass enablement check)
		if proxiedTools[name] {
			filteredTools[name] = tool
			continue
		}

		// Skip tools that require enablement but aren't enabled
		if requiresEnablement(name) && !isToolEnabled(name) {
			continue
		}

		filteredTools[name] = tool
	}
	registryMu.RUnlock()
	return filteredTools
}

// GetLogger returns the shared logger instance
func GetLogger() *logrus.Logger {
	return logger
}

// GetCache returns the shared cache instance
func GetCache() *sync.Map {
	return cache
}

// GetEnabledToolNames returns a sorted list of enabled tool names
func GetEnabledToolNames() []string {
	registryMu.RLock()
	names := make([]string, 0, len(toolRegistry))
	for name := range toolRegistry {
		// Skip disabled tools
		if isToolDisabled(name) {
			continue
		}

		// Include proxied tools (bypass enablement check)
		if proxiedTools[name] {
			names = append(names, name)
			continue
		}

		// Skip tools that require enablement but aren't enabled
		if requiresEnablement(name) && !isToolEnabled(name) {
			continue
		}

		names = append(names, name)
	}
	registryMu.RUnlock()
	sort.Strings(names)
	return names
}

// GetToolNamesWithExtendedHelp returns a sorted list of enabled tool names that provide extended help
func GetToolNamesWithExtendedHelp() []string {
	registryMu.RLock()
	var names []string
	for name, tool := range toolRegistry {
		// Skip disabled tools
		if isToolDisabled(name) {
			continue
		}

		// Skip tools that require enablement but are not enabled
		if requiresEnablement(name) && !isToolEnabled(name) {
			continue
		}

		// Only include tools that implement ExtendedHelpProvider
		if _, ok := tool.(tools.ExtendedHelpProvider); ok {
			names = append(names, name)
		}
	}
	registryMu.RUnlock()
	sort.Strings(names)
	return names
}

// isToolEnabled checks if a tool is enabled via the cached ENABLE_ADDITIONAL_TOOLS set.
func isToolEnabled(toolName string) bool {
	ensureEnabledToolsParsed()

	if enableAllTools {
		return true
	}

	if len(enabledAdditionalTools) == 0 {
		return false
	}

	normalised := normaliseName(toolName)

	// Check direct match
	if enabledAdditionalTools[normalised] {
		return true
	}

	// Check aliases for backwards compatibility
	if aliases, ok := toolNameAliases[normalised]; ok {
		for _, alias := range aliases {
			if enabledAdditionalTools[alias] {
				return true
			}
		}
	}

	return false
}
