package slackscot

import (
	"fmt"
	"github.com/alexandre-normand/slackscot/config"
	"io"
	"strings"
)

type helpPlugin struct {
	Plugin

	name                   string
	slackscotVersion       string
	timeLocation           string
	commands               map[string][]ActionDefinition
	hearActions            []ActionDefinition
	pluginScheduledActions []pluginScheduledAction
	cmdPrefix              string
}

const (
	helpPluginName = "help"
)

// pluginScheduledAction represents a plugin's scheduled action with the plugin name and the action's definition
type pluginScheduledAction struct {
	plugin string
	ScheduledActionDefinition
}

func (s *Slackscot) newHelpPlugin(version string) *helpPlugin {
	commands, hearActions, scheduledActions := findAllActions(s.namespaceCommands, s.plugins)

	helpPlugin := new(helpPlugin)
	helpPlugin.timeLocation = s.config.GetString(config.TimeLocationKey)
	helpPlugin.name = s.name
	helpPlugin.slackscotVersion = version
	helpPlugin.commands = commands
	helpPlugin.hearActions = hearActions
	helpPlugin.pluginScheduledActions = scheduledActions
	helpPlugin.cmdPrefix = s.cmdMatcher.UsagePrefix()

	helpPlugin.Plugin = Plugin{Name: helpPluginName, Commands: []ActionDefinition{{
		Match: func(m *IncomingMessage) bool {
			return strings.HasPrefix(m.NormalizedText, "help")
		},
		Usage:       helpPluginName,
		Description: "Reply with usage instructions",
		Answer:      helpPlugin.showHelp,
	}}, HearActions: nil}

	return helpPlugin
}

// showHelp generates a message providing a list of all of the slackscot commands and hear actions.
// Note that ActionDefinitions with the flag Hidden set to true won't be included in the list
func (h *helpPlugin) showHelp(m *IncomingMessage) *Answer {
	var b strings.Builder

	// Get the user's first name using the botservices
	userID := m.User
	user, err := h.UserInfoFinder.GetUserInfo(userID)
	if err != nil {
		h.Logger.Debugf("Error getting user info for user id [%s] so skipping mentioning the name (it would be awkward): %v", userID, err)
	} else {
		fmt.Fprintf(&b, "🤝 Hi, `%s`! ", user.RealName)
	}

	fmt.Fprintf(&b, "I'm `%s` (engine `v%s`) and I listen to the team's chat and provides automated functions :genie:.\n", h.name, h.slackscotVersion)

	if lenCommands(h.commands) > 0 {
		fmt.Fprintf(&b, "\nI currently support the following commands:\n")

		for n, commands := range h.commands {
			appendActions(&b, h.cmdPrefix, n, commands)
		}
	}

	if len(h.hearActions) > 0 {
		fmt.Fprintf(&b, "\nAnd listen for the following:\n")

		appendActions(&b, "", "", h.hearActions)
	}

	if len(h.pluginScheduledActions) > 0 {
		fmt.Fprintf(&b, "\nAnd do those things periodically:\n")

		appendScheduledActions(&b, h.timeLocation, h.pluginScheduledActions)
	}

	return &Answer{Text: b.String(), Options: []AnswerOption{AnswerInThread()}}
}

// lenCommands returns the length of a map of string to array of values by summing
// up the length of all array values
func lenCommands(entries map[string][]ActionDefinition) (length int) {
	length = 0
	for _, v := range entries {
		length = length + len(v)
	}

	return length
}

func appendActions(w io.Writer, prefix string, pluginNamespace string, actions []ActionDefinition) {
	for _, value := range actions {
		if value.Usage != "" && !value.Hidden {
			if len(pluginNamespace) > 0 {
				fmt.Fprintf(w, "\t• `%s%s %s` - %s\n", prefix, pluginNamespace, value.Usage, value.Description)
			} else {
				fmt.Fprintf(w, "\t• `%s%s` - %s\n", prefix, value.Usage, value.Description)
			}
		}
	}
}

func appendScheduledActions(w io.Writer, timeLocationName string, scheduledActions []pluginScheduledAction) {
	for _, value := range scheduledActions {
		if !value.ScheduledActionDefinition.Hidden {
			fmt.Fprintf(w, "\t• [`%s`] `%s` (`%s`) - %s\n", value.plugin, value.ScheduledActionDefinition.Schedule, timeLocationName, value.ScheduledActionDefinition.Description)
		}
	}
}

func findAllActions(namespaceCommands bool, plugins []*Plugin) (commands map[string][]ActionDefinition, hearActions []ActionDefinition, pluginScheduledActions []pluginScheduledAction) {
	commands = make(map[string][]ActionDefinition)
	hearActions = make([]ActionDefinition, 0)
	pluginScheduledActions = make([]pluginScheduledAction, 0)

	for _, p := range plugins {
		namespace := ""
		if namespaceCommands && p.NamespaceCommands {
			namespace = p.Name
		}

		if _, ok := commands[namespace]; !ok {
			commands[namespace] = make([]ActionDefinition, 0)
		}

		commands[namespace] = append(commands[namespace], filterNonHiddenActions(p.Commands)...)
		hearActions = append(hearActions, filterNonHiddenActions(p.HearActions)...)
		pluginScheduledActions = append(pluginScheduledActions, filterNonHiddenScheduledActions(p.Name, p.ScheduledActions)...)
	}

	return commands, hearActions, pluginScheduledActions
}

func filterNonHiddenActions(actions []ActionDefinition) (visibleActions []ActionDefinition) {
	visibleActions = make([]ActionDefinition, 0)
	for _, a := range actions {
		if !a.Hidden {
			visibleActions = append(visibleActions, a)
		}
	}

	return visibleActions
}

func filterNonHiddenScheduledActions(pluginName string, actions []ScheduledActionDefinition) (visibleActions []pluginScheduledAction) {
	visibleActions = make([]pluginScheduledAction, 0)

	for _, sa := range actions {
		if !sa.Hidden {
			visibleActions = append(visibleActions, pluginScheduledAction{plugin: pluginName, ScheduledActionDefinition: sa})
		}
	}

	return visibleActions
}
