package shell

import (
	"context"
	"fmt"
	"io"
	"strings"

	"github.com/seaweedfs/seaweedfs/weed/operation"
	"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
	"github.com/seaweedfs/seaweedfs/weed/storage/needle_map"

	"google.golang.org/grpc"

	"github.com/seaweedfs/seaweedfs/weed/pb"
	"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
	"github.com/seaweedfs/seaweedfs/weed/util"
	"github.com/seaweedfs/seaweedfs/weed/wdclient"
	"github.com/seaweedfs/seaweedfs/weed/wdclient/exclusive_locks"
)

type ShellOptions struct {
	Masters        *string
	GrpcDialOption grpc.DialOption
	// shell transient context
	FilerHost    string
	FilerPort    int64
	FilerGroup   *string
	FilerAddress pb.ServerAddress
	Directory    string
	Debug        bool
}

type CommandEnv struct {
	env          map[string]string
	MasterClient *wdclient.MasterClient
	option       *ShellOptions
	locker       *exclusive_locks.ExclusiveLocker
	noLock       bool
	forceNoLock  bool
	verbose      bool
}

func NewCommandEnv(options *ShellOptions) *CommandEnv {
	ce := &CommandEnv{
		env:          make(map[string]string),
		MasterClient: wdclient.NewMasterClient(options.GrpcDialOption, *options.FilerGroup, pb.AdminShellClient, "", "", "", *pb.ServerAddresses(*options.Masters).ToServiceDiscovery()),
		option:       options,
		noLock:       false,
	}
	ce.locker = exclusive_locks.NewExclusiveLocker(ce.MasterClient, "shell")
	return ce
}

func (ce *CommandEnv) parseUrl(input string) (path string, err error) {
	if strings.HasPrefix(input, "http") {
		err = fmt.Errorf("http://<filer>:<port> prefix is not supported any more")
		return
	}
	if !strings.HasPrefix(input, "/") {
		input = util.Join(ce.option.Directory, input)
	}
	return input, err
}

func (ce *CommandEnv) isDirectory(path string) bool {

	return ce.checkDirectory(path) == nil

}

func (ce *CommandEnv) confirmIsLocked(args []string) error {

	if ce.noLock || ce.forceNoLock {
		return nil
	}
	if ce.locker.IsLocked() {
		return nil
	}
	ce.locker.SetMessage(fmt.Sprintf("%v", args))

	return fmt.Errorf("need to run \"lock\" first to continue")

}

func (ce *CommandEnv) SetNoLock(noLock bool) {
	if ce == nil {
		return
	}
	ce.noLock = noLock
}

func (ce *CommandEnv) ForceNoLock() {
	if ce == nil {
		return
	}
	ce.forceNoLock = true
}

func (ce *CommandEnv) isLocked() bool {
	if ce == nil {
		return true
	}
	if ce.noLock || ce.forceNoLock {
		return true
	}
	return ce.locker.IsLocked()
}

func (ce *CommandEnv) checkDirectory(path string) error {

	dir, name := util.FullPath(path).DirAndName()

	exists, err := filer_pb.Exists(context.Background(), ce, dir, name, true)

	if !exists {
		return fmt.Errorf("%s is not a directory", path)
	}

	return err

}

var _ = filer_pb.FilerClient(&CommandEnv{})

func (ce *CommandEnv) WithFilerClient(streamingMode bool, fn func(filer_pb.SeaweedFilerClient) error) error {

	return pb.WithGrpcFilerClient(streamingMode, 0, ce.option.FilerAddress, ce.option.GrpcDialOption, fn)

}

func (ce *CommandEnv) AdjustedUrl(location *filer_pb.Location) string {
	return location.Url
}

func (ce *CommandEnv) GetDataCenter() string {
	return ce.MasterClient.GetDataCenter()
}

func findInputDirectory(args []string) (input string) {
	input = "."
	if len(args) > 0 {
		input = args[len(args)-1]
		if strings.HasPrefix(input, "-") {
			input = "."
		}
	}
	return input
}

// isHelpRequest checks if the args contain a help flag (-h, --help, or -help)
// It also handles combined short flags like -lh or -hl
func isHelpRequest(args []string) bool {
	for _, arg := range args {
		// Check for exact matches
		if arg == "-h" || arg == "--help" || arg == "-help" {
			return true
		}
		// Check for combined short flags (e.g., -lh, -hl, -rfh)
		// Limit to reasonable length (2-4 chars total) to avoid matching long options like -verbose
		if strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") && len(arg) > 1 && len(arg) <= 4 {
			for _, char := range arg[1:] {
				if char == 'h' {
					return true
				}
			}
		}
	}
	return false
}

// handleHelpRequest checks for help flags and prints the help message if requested.
// It returns true if the help message was printed, indicating the command should exit.
func handleHelpRequest(c command, args []string, writer io.Writer) bool {
	if isHelpRequest(args) {
		fmt.Fprintln(writer, c.Help())
		return true
	}
	return false
}

func readNeedleStatus(grpcDialOption grpc.DialOption, sourceVolumeServer pb.ServerAddress, volumeId uint32, needleValue needle_map.NeedleValue) (resp *volume_server_pb.VolumeNeedleStatusResponse, err error) {
	err = operation.WithVolumeServerClient(false, sourceVolumeServer, grpcDialOption,
		func(client volume_server_pb.VolumeServerClient) error {
			if resp, err = client.VolumeNeedleStatus(context.Background(), &volume_server_pb.VolumeNeedleStatusRequest{
				VolumeId: volumeId,
				NeedleId: uint64(needleValue.Key),
			}); err != nil {
				return err
			}
			return nil
		},
	)
	return
}

func getCollectionName(commandEnv *CommandEnv, bucket string) string {
	if *commandEnv.option.FilerGroup != "" {
		return fmt.Sprintf("%s_%s", *commandEnv.option.FilerGroup, bucket)
	}
	return bucket
}
