package cmd

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/apache/cloudberry-go-libs/cluster"
)

type envVar string

const (
	gpHome   envVar = "GPHOME"
	pxfHome  envVar = "PXF_HOME"
	pxfBase  envVar = "PXF_BASE"
	javaHome envVar = "JAVA_HOME"
	// For pxf migrate
	pxfConf envVar = "PXF_CONF"
)

type messageType int

const (
	success messageType = iota
	status
	err
	warning
	standby
)

type command struct {
	name       commandName
	messages   map[messageType]string
	whereToRun cluster.Scope
	envVars    []envVar
	warn       bool // whether the command requires a warning/prompt
}

func (cmd *command) Warn(input io.Reader) error {
	if !cmd.warn || promptUser(input, cmd.messages[warning]) {
		return nil
	}
	return fmt.Errorf("pxf %s cancelled", cmd.name)
}

func (cmd *command) GetFunctionToExecute() (func(string) string, error) {
	inputs, err := makeValidCliInputs(cmd)
	if err != nil {
		return nil, err
	}

	switch cmd.name {
	case sync:
		rsyncCommand := "rsync -az%s -e 'ssh -o StrictHostKeyChecking=no' '%s/conf' '%s/lib' '%s/servers' '%s:%s'"
		deleteString := ""
		if DeleteOnSync {
			deleteString = " --delete"
		}
		return func(hostname string) string {
			return fmt.Sprintf(
				rsyncCommand,
				deleteString,
				inputs[pxfBase],
				inputs[pxfBase],
				inputs[pxfBase],
				hostname,
				inputs[pxfBase])
		}, nil
	default:
		var effectivePxfBase string

		pxfCommand := ""
		if inputs[gpHome] != "" {
			pxfCommand += "GPHOME=" + inputs[gpHome] + " "
		}
		if inputs[pxfConf] != "" {
			pxfCommand += "PXF_CONF=" + inputs[pxfConf] + " "
		}
		if inputs[pxfBase] != "" {
			pxfCommand += "PXF_BASE=" + inputs[pxfBase] + " "
			effectivePxfBase = inputs[pxfBase]
		} else {
			// PXF_BASE defaults to PXF_HOME
			effectivePxfBase = inputs[pxfHome]
		}
		if inputs[javaHome] != "" {
			pxfCommand += "JAVA_HOME=" + inputs[javaHome] + " "
		}
		pxfCommand += inputs[pxfHome] + "/bin/pxf" + " " + string(cmd.name)
		if cmd.name == prepare && inputs[pxfHome] == effectivePxfBase {
			// error out when PXF_BASE equals PXF_HOME
			return nil, errors.New("the PXF_BASE value must be different from your PXF installation directory")
		}
		if cmd.name == migrate && inputs[pxfConf] == effectivePxfBase {
			// error out when PXF_BASE equals PXF_CONF
			return nil, errors.New("your target PXF_BASE directory must be different from your existing PXF_CONF directory")
		}
		if cmd.name == reset {
			pxfCommand += " --force" // there is a prompt for local reset as well
		}
		return func(_ string) string { return pxfCommand }, nil
	}
}

func promptUser(input io.Reader, prompt string) bool {
	reader := bufio.NewReader(input)
	fmt.Print(prompt)
	text, _ := reader.ReadString('\n')
	text = strings.TrimRight(text, "\r\n")
	return strings.ToLower(text) == "y"
}

type commandName string

const (
	pxfInit  = "init"
	start    = "start"
	stop     = "stop"
	sync     = "sync"
	statuses = "status"
	reset    = "reset"
	register = "register"
	restart  = "restart"
	prepare  = "prepare"
	migrate  = "migrate"
)

// The pxf cli commands, exported for testing
var (
	InitCommand = command{
		name: pxfInit,
		messages: map[messageType]string{
			success: "PXF initialized successfully on %d out of %d host%s\n",
			status: "*****************************************************************************\n" +
				"* DEPRECATION NOTICE:\n" +
				"* The \"pxf cluster init\" command is deprecated and will be removed\n" +
				"* in a future release of PXF.\n" +
				"*\n" +
				"* Use the \"pxf cluster register\" command instead.\n" +
				"*\n" +
				"*****************************************************************************\n\n" +
				"Initializing PXF on coordinator host%s and %d segment host%s...\n",
			standby: ", standby coordinator host,",
			err:     "PXF failed to initialize on %d out of %d host%s\n",
		},
		warn:       false,
		envVars:    []envVar{gpHome, pxfHome, javaHome},
		whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
	}
	StartCommand = command{
		name: start,
		messages: map[messageType]string{
			success: "PXF started successfully on %d out of %d host%s\n",
			status:  "Starting PXF on coordinator host%s and %d segment host%s...\n",
			standby: ", standby coordinator host,",
			err:     "PXF failed to start on %d out of %d host%s\n",
		},
		warn:       false,
		envVars:    []envVar{pxfHome, pxfBase},
		whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
	}
	StopCommand = command{
		name: stop,
		messages: map[messageType]string{
			success: "PXF stopped successfully on %d out of %d host%s\n",
			status:  "Stopping PXF on coordinator host%s and %d segment host%s...\n",
			standby: ", standby coordinator host,",
			err:     "PXF failed to stop on %d out of %d host%s\n",
		},
		warn:       false,
		envVars:    []envVar{pxfHome, pxfBase},
		whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
	}
	SyncCommand = command{
		name: sync,
		messages: map[messageType]string{
			success: "PXF configs synced successfully on %d out of %d host%s\n",
			status:  "Syncing PXF configuration files from coordinator host to%s %d segment host%s...\n",
			standby: " standby coordinator host and",
			err:     "PXF configs failed to sync on %d out of %d host%s\n",
		},
		warn:    false,
		envVars: []envVar{pxfBase},
		// cluster.ON_LOCAL | cluster.ON_HOSTS: the command will target host%s, but be run from coordinator
		// this is ideal for copying files from coordinator to segment host(s) using rsync.
		// since the files are already on coordinator, we exclude coordinator but include standby coordinator
		whereToRun: cluster.ON_LOCAL | cluster.ON_HOSTS | cluster.EXCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
	}
	StatusCommand = command{
		name: statuses,
		messages: map[messageType]string{
			success: "PXF is running on %d out of %d host%s\n",
			status:  "Checking status of PXF servers on coordinator host%s and %d segment host%s...\n",
			standby: ", standby coordinator host,",
			err:     "PXF is not running on %d out of %d host%s\n",
		},
		warn:       false,
		envVars:    []envVar{pxfHome, pxfBase},
		whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
	}
	RegisterCommand = command{
		name: register,
		messages: map[messageType]string{
			success: "PXF extension has been installed on %d out of %d host%s\n",
			status:  "Installing PXF extension on coordinator host%s and %d segment host%s...\n",
			standby: ", standby coordinator host,",
			err:     "Failed to install PXF extension on %d out of %d host%s\n",
		},
		warn:       false,
		envVars:    []envVar{gpHome, pxfHome},
		whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
	}
	ResetCommand = command{
		name: reset,
		messages: map[messageType]string{
			success: "PXF has been reset on %d out of %d host%s\n",
			status: "*****************************************************************************\n" +
				"* DEPRECATION NOTICE:\n" +
				"* The \"pxf cluster reset\" command is deprecated and will be removed\n" +
				"* in a future release of PXF.\n" +
				"*****************************************************************************\n\n" +
				"Resetting PXF on coordinator host%s and %d segment host%s...\n",
			standby: ", standby coordinator host,",
			err:     "Failed to reset PXF on %d out of %d host%s\n",
		},
		warn:       false,
		envVars:    []envVar{pxfHome},
		whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
	}
	RestartCommand = command{
		name: restart,
		messages: map[messageType]string{
			success: "PXF restarted successfully on %d out of %d host%s\n",
			status:  "Restarting PXF on coordinator host%s and %d segment host%s...\n",
			standby: ", standby coordinator host,",
			err:     "PXF failed to restart on %d out of %d host%s\n",
		},
		warn:       false,
		envVars:    []envVar{pxfHome, pxfBase},
		whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
	}
	PrepareCommand = command{
		name: prepare,
		messages: map[messageType]string{
			success: "PXF prepared successfully on %d out of %d host%s\n",
			status:  "Preparing PXF on coordinator host%s and %d segment host%s...\n",
			standby: ", standby coordinator host,",
			err:     "PXF failed to prepare on %d out of %d host%s\n",
		},
		warn:       false,
		envVars:    []envVar{pxfHome, pxfBase},
		whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
	}
	MigrateCommand = command{
		name: migrate,
		messages: map[messageType]string{
			success: "PXF configuration migrated successfully on %d out of %d host%s\n",
			status:  "Migrating PXF configuration on coordinator host%s and %d segment host%s...\n",
			standby: ", standby coordinator host,",
			err:     "PXF failed to migrate configuration on %d out of %d host%s\n",
		},
		warn:       false,
		envVars:    []envVar{pxfHome, pxfConf, pxfBase},
		whereToRun: cluster.ON_REMOTE | cluster.ON_HOSTS | cluster.INCLUDE_MASTER | cluster.INCLUDE_MIRRORS,
	}
)

func makeValidCliInputs(cmd *command) (map[envVar]string, error) {
	envVars := make(map[envVar]string)
	for _, e := range cmd.envVars {
		val, err := validateEnvVar(e)
		if err != nil {
			return nil, err
		}
		envVars[e] = val
	}
	return envVars, nil
}

func validateEnvVar(envVariable envVar) (string, error) {
	envVarValue, isEnvVarSet := os.LookupEnv(string(envVariable))
	if !isEnvVarSet {
		return "", errors.New(string(envVariable) + " must be set")
	}
	if envVarValue == "" {
		return "", errors.New(string(envVariable) + " cannot be blank")
	}
	return envVarValue, nil
}
