package redeo

import (
	"errors"
	"strings"

	"github.com/bsm/redeo/v2/resp"
)

// UnknownCommand returns an unknown command error string
func UnknownCommand(cmd string) string {
	return "ERR unknown command '" + cmd + "'"
}

// ErrUnknownCommand returns an unknown command error
func ErrUnknownCommand(cmd string) error {
	return errors.New(UnknownCommand(cmd))
}

// WrongNumberOfArgs returns an unknown command error string
func WrongNumberOfArgs(cmd string) string {
	return "ERR wrong number of arguments for '" + cmd + "' command"
}

// ErrWrongNumberOfArgs returns an unknown command error
func ErrWrongNumberOfArgs(cmd string) error {
	return errors.New(WrongNumberOfArgs(cmd))
}

// Ping returns a ping handler.
// https://redis.io/commands/ping
func Ping() Handler {
	return HandlerFunc(func(w resp.ResponseWriter, c *resp.Command) {
		switch c.ArgN() {
		case 0:
			w.AppendInlineString("PONG")
		case 1:
			w.AppendBulk(c.Arg(0))
		default:
			w.AppendError(WrongNumberOfArgs(c.Name))
		}
	})
}

// Echo returns an echo handler.
// https://redis.io/commands/echo
func Echo() Handler {
	return HandlerFunc(func(w resp.ResponseWriter, c *resp.Command) {
		switch c.ArgN() {
		case 1:
			w.AppendBulk(c.Arg(0))
		default:
			w.AppendError(WrongNumberOfArgs(c.Name))
		}
	})
}

// Info returns an info handler.
// https://redis.io/commands/info
func Info(s *Server) Handler {
	return HandlerFunc(func(w resp.ResponseWriter, c *resp.Command) {
		info := s.Info()
		resp := ""
		if c.ArgN() == 1 {
			resp = info.Find(c.Args[0].String()).String()
		} else {
			resp = info.String()
		}
		w.AppendBulkString(resp)
	})
}

// CommandDescriptions returns a command handler.
// https://redis.io/commands/command
type CommandDescriptions []CommandDescription

func (s CommandDescriptions) ServeRedeo(w resp.ResponseWriter, c *resp.Command) {
	w.AppendArrayLen(len(s))

	for _, cmd := range s {
		w.AppendArrayLen(6)
		w.AppendBulkString(strings.ToLower(cmd.Name))
		w.AppendInt(cmd.Arity)
		w.AppendArrayLen(len(cmd.Flags))
		for _, flag := range cmd.Flags {
			w.AppendBulkString(flag)
		}
		w.AppendInt(cmd.FirstKey)
		w.AppendInt(cmd.LastKey)
		w.AppendInt(cmd.KeyStepCount)
	}
}

// SubCommands returns a handler that is parsing sub-commands
type SubCommands map[string]Handler

func (s SubCommands) ServeRedeo(w resp.ResponseWriter, c *resp.Command) {

	// First, check if we have a subcommand
	if c.ArgN() == 0 {
		w.AppendError(WrongNumberOfArgs(c.Name))
		return
	}

	firstArg := c.Arg(0).String()
	if h, ok := s[strings.ToLower(firstArg)]; ok {
		cmd := resp.NewCommand(c.Name+" "+firstArg, c.Args[1:]...)
		cmd.SetContext(c.Context())
		h.ServeRedeo(w, cmd)
		return
	}

	w.AppendError("ERR Unknown " + strings.ToLower(c.Name) + " subcommand '" + firstArg + "'")

}

// --------------------------------------------------------------------

// Handler is an abstract handler interface for responding to commands
type Handler interface {
	// ServeRedeo serves a request.
	ServeRedeo(w resp.ResponseWriter, c *resp.Command)
}

// HandlerFunc is a callback function, implementing Handler.
type HandlerFunc func(w resp.ResponseWriter, c *resp.Command)

// ServeRedeo calls f(w, c).
func (f HandlerFunc) ServeRedeo(w resp.ResponseWriter, c *resp.Command) { f(w, c) }

// WrapperFunc implements Handler, accepts a command and must return one of
// the following types:
//
//	nil
//	error
//	string
//	[]byte
//	bool
//	float32, float64
//	int, int8, int16, int32, int64
//	uint, uint8, uint16, uint32, uint64
//	resp.CustomResponse instances
//	slices of any of the above typs
//	maps containing keys and values of any of the above types
type WrapperFunc func(c *resp.Command) interface{}

// ServeRedeo implements Handler
func (f WrapperFunc) ServeRedeo(w resp.ResponseWriter, c *resp.Command) {
	if err := w.Append(f(c)); err != nil {
		w.AppendError("ERR " + err.Error())
	}
}

// --------------------------------------------------------------------

// StreamHandler is an  interface for responding to streaming commands
type StreamHandler interface {
	// ServeRedeoStream serves a streaming request.
	ServeRedeoStream(w resp.ResponseWriter, c *resp.CommandStream)
}

// StreamHandlerFunc is a callback function, implementing Handler.
type StreamHandlerFunc func(w resp.ResponseWriter, c *resp.CommandStream)

// ServeRedeoStream calls f(w, c).
func (f StreamHandlerFunc) ServeRedeoStream(w resp.ResponseWriter, c *resp.CommandStream) { f(w, c) }
