package generic

import (
	"errors"
	"fmt"
	"github.com/kballard/go-shellquote"
	"os"
	"os/exec"

	"github.com/carapace-sh/carapace"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"

	"github.com/chainreactors/malice-network/client/command/common"
	"github.com/chainreactors/malice-network/client/core"
	"github.com/chainreactors/malice-network/client/repl"
	"github.com/chainreactors/malice-network/helper/consts"
	"github.com/chainreactors/malice-network/helper/intermediate"
	"github.com/chainreactors/malice-network/helper/proto/client/clientpb"
)

func Commands(con *repl.Console) []*cobra.Command {
	loginCmd := &cobra.Command{
		Use:   consts.CommandLogin,
		Short: "Login to server",
		RunE: func(cmd *cobra.Command, args []string) error {
			return LoginCmd(cmd, con)
		},
	}

	versionCmd := &cobra.Command{
		Use:   consts.CommandVersion,
		Short: "show server version",
		Run: func(cmd *cobra.Command, args []string) {
			VersionCmd(cmd, con)
			return
		},
	}

	exitCmd := &cobra.Command{
		Use:   consts.CommandExit,
		Short: "exit client",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("Exiting...")
			os.Exit(0)
			return
		},
	}

	broadcastCmd := &cobra.Command{
		Use:   consts.CommandBroadcast + " [message]",
		Short: "Broadcast a message to all clients",
		Args:  cobra.MinimumNArgs(1),
		Run: func(cmd *cobra.Command, args []string) {
			BroadcastCmd(cmd, con)
		},
	}

	common.BindFlag(broadcastCmd, func(f *pflag.FlagSet) {
		f.BoolP("notify", "n", false, "notify the message to third-party services")
	})

	cmdCmd := &cobra.Command{
		Use:   "! [command]",
		Short: "Run a command",
		Args:  cobra.MinimumNArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			// os exec

			if len(args) < 1 {
				return errors.New("command requires one or more arguments")
			}

			// Above, the length of args is checked to be at least 2
			path, err := exec.LookPath(args[0])
			if err != nil {
				return err
			}

			shellCmd := exec.Command(path, args[1:]...)

			// Load OS environment
			shellCmd.Env = os.Environ()

			out, err := shellCmd.CombinedOutput()
			if err != nil {
				return err
			}

			con.Log.Console(string(out))
			return nil
		},
	}
	fileAction := carapace.ActionFiles()
	common.BindArgCompletions(cmdCmd, &fileAction)

	pivotCmd := &cobra.Command{
		Use:   consts.CommandPivot,
		Short: "List all pivot agents",
		Long:  "List all active pivot agents with their details",
		RunE: func(cmd *cobra.Command, args []string) error {
			return ListPivotCmd(cmd, con)
		},
		Example: `List all pivot agents:
~~~
pivot
~~~`,
	}

	common.BindFlag(pivotCmd, func(f *pflag.FlagSet) {
		f.BoolP("all", "a", false, "list all pivot agents")
	})

	licenseInfoCmd := &cobra.Command{
		Use:   consts.CommandLicense,
		Short: "show server license info",
		Long:  "show server license info",
		RunE: func(cmd *cobra.Command, args []string) error {
			return GetLicenseCmd(cmd, con)
		},
		Example: `~~~
license
~~~`,
	}

	return []*cobra.Command{loginCmd, versionCmd, exitCmd, broadcastCmd, cmdCmd, pivotCmd, licenseInfoCmd}
}

func Log(con *repl.Console, sess *core.Session, msg string, notify bool) (bool, error) {
	_, err := con.Rpc.SessionEvent(sess.Context(), &clientpb.Event{
		Type:    consts.EventSession,
		Op:      consts.CtrlSessionLog,
		Session: sess.Session,
		Client:  con.Client,
		Message: []byte(msg),
	})
	if err != nil {
		return false, err
	}
	if notify {
		return Notify(con, &clientpb.Event{
			Type:    consts.EventNotify,
			Client:  con.Client,
			Message: []byte(msg),
		})
	}
	return true, nil
}

func Register(con *repl.Console) {
	con.RegisterServerFunc("console", func(con *repl.Console) *repl.Console {
		return con
	}, nil)

	con.RegisterServerFunc("sessions", func(con *repl.Console) map[string]*core.Session {
		return con.Sessions
	}, nil)

	con.RegisterServerFunc("listeners", func(con *repl.Console) map[string]*clientpb.Listener {
		return con.Listeners
	}, nil)

	con.RegisterServerFunc("pipelines", func(con *repl.Console) map[string]*clientpb.Pipeline {
		return con.Pipelines
	}, nil)

	con.RegisterServerFunc("run", repl.RunCommand, nil)

	con.RegisterServerFunc("async_run", func(con *repl.Console, cmdline interface{}) (bool, error) {
		var args []string
		var err error
		switch c := cmdline.(type) {
		case string:
			args, err = shellquote.Split(c)
			if err != nil {
				return false, err
			}
		case []string:
			args = c
		}

		err = con.App.Execute(con.Context(), con.App.ActiveMenu(), args, true)
		if err != nil {
			return false, err
		}
		return true, nil
	}, nil)

	con.RegisterServerFunc(consts.CommandBroadcast, func(con *repl.Console, msg string) (bool, error) {
		return Broadcast(con, &clientpb.Event{
			Type:    consts.EventBroadcast,
			Client:  con.Client,
			Message: []byte(msg),
		})
	}, nil)

	con.RegisterServerFunc(consts.CommandNotify, func(con *repl.Console, msg string) (bool, error) {
		return Notify(con, &clientpb.Event{
			Type:    consts.EventNotify,
			Client:  con.Client,
			Message: []byte(msg),
		})
	}, nil)

	con.RegisterServerFunc("callback_log", func(con *repl.Console, sess *core.Session, notify bool) intermediate.BuiltinCallback {
		return func(content interface{}) (interface{}, error) {
			return Log(con, sess, fmt.Sprintf("%v", content), notify)
		}
	}, nil)

	con.RegisterServerFunc("log", func(con *repl.Console, sess *core.Session, msg string, notify bool) (bool, error) {
		return Log(con, sess, msg, notify)
	}, nil)

	con.RegisterServerFunc("blog", func(con *repl.Console, sess *core.Session, msg string) (bool, error) {
		return Log(con, sess, msg, false)
	}, nil)

}
