package cmd

import (
	"errors"
	"fmt"
	"net"
	"os"
	"text/template"

	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"gopkg.in/AlecAivazis/survey.v1"
	surveyCore "gopkg.in/AlecAivazis/survey.v1/core"
)

// initCmd represents the init command
var initCmd = &cobra.Command{
	Use:   "init",
	Short: "Set the TiDB cluster topology",
	Long:  `Enter information of deployment servers and set the TiDB cluster topology.`,
	Run:   initCluster,
}

var errInvalidIPv4Address = errors.New("Invalid IPv4 address")

// Server ... infomation of deployment server
type Server struct {
	IP           string
	RootPassword string
}

// Inventory ... TiDB cluster topology
type Inventory struct {
	Servers       []Server
	TiDBServers   []string
	TiKVServers   []string
	PDServers     []string
	MonitorServer string
}

func isIPv4(val interface{}) error {
	if str, ok := val.(string); ok {
		address := net.ParseIP(str)
		if address == nil || address.To4() == nil {
			return errInvalidIPv4Address
		}
	} else {
		return errInvalidIPv4Address
	}

	return nil
}

func init() {
	rootCmd.AddCommand(initCmd)
	surveyCore.QuestionIcon = ">"
}

func serverCount() int {
	serverCount := 0
	prompt := &survey.Input{
		Message: "How many servers do you prepare to deploy the tidb cluster:",
	}
	survey.AskOne(prompt, &serverCount, survey.Required)
	return serverCount
}

func confirmPassword() bool {
	response := false
	prompt := &survey.Confirm{
		Message: "Is the password the same as the first server?",
	}
	survey.AskOne(prompt, &response, survey.Required)
	return response
}

func (s *Server) setIP() {
	prompt := &survey.Input{
		Message: "Add server, please enter IP address:",
	}
	survey.AskOne(prompt, &s.IP, survey.ComposeValidators(survey.Required, isIPv4))
}

func (s *Server) setRootPassword() {
	prompt := &survey.Password{
		Message: "Please enter root password:",
	}
	survey.AskOne(prompt, &s.RootPassword, survey.Required)
}

func listServers(servers []Server) {
	fmt.Println("Name       Address")

	for i, server := range servers {
		fmt.Printf("server%03d  %s\n", i+1, server.IP)
	}
}

func initServers(count int) []Server {
	serverList := []Server{}
	universalPassword := ""

	for i := 1; i <= count; i++ {
		server := Server{}

		server.setIP()

		if i == 1 {
			server.setRootPassword()
			universalPassword = server.RootPassword
		} else {
			confirmation := confirmPassword()
			if confirmation {
				server.RootPassword = universalPassword
			} else {
				server.setRootPassword()
			}
		}

		serverList = append(serverList, server)
		listServers(serverList)
	}

	return serverList
}

func initServiceServers(service string, ips []string) []string {
	servers := []string{}
	prompt := &survey.MultiSelect{
		Message: fmt.Sprintf("What servers do you prefer to deploy %s service:", service),
		Options: ips,
	}
	survey.AskOne(prompt, &servers, nil)

	return servers
}

func initServiceServer(service string, ips []string) string {
	servers := ""
	prompt := &survey.Select{
		Message: fmt.Sprintf("Choose a server to deploy %s service:", service),
		Options: ips,
	}
	survey.AskOne(prompt, &servers, nil)

	return servers
}

func initCluster(cmd *cobra.Command, args []string) {
	cdWorkerDir()

	checkDataDirExist()

	log.Infoln("Prepare deployment servers:")

	// Count how many servers are used to deploy the tidb cluster
	serverCount := serverCount()
	if serverCount < 4 {
		log.Fatalln("Please enter a valid number, at least 4.")
	}
	log.Infof("You plan to deploy a tidb cluster with %d servers.\n", serverCount)

	// Set the TiDB cluster topology
	serverList := initServers(serverCount)
	serverIPList := []string{}
	for _, server := range serverList {
		serverIPList = append(serverIPList, server.IP)
	}

	tidbServers := initServiceServers("tidb", serverIPList)
	tikvServers := initServiceServers("tikv", serverIPList)
	pdServers := initServiceServers("pd", serverIPList)

	m := make(map[string]struct{}, len(serverIPList))
	for _, s := range serverIPList {
		m[s] = struct{}{}
	}

	for _, item := range tidbServers {
		delete(m, item)
	}

	for _, item := range pdServers {
		delete(m, item)
	}

	availableIPList := make([]string, 0, len(m))

	for k := range m {
		availableIPList = append(availableIPList, k)
	}

	if len(availableIPList) < 1 {
		log.Fatalln("Please reserve a separate server for deploying monitor service.")
	}
	monitorServers := initServiceServer("monitor", availableIPList)

	storeDashboardInfo(monitorServers)

	storeTiDBInfo(tidbServers)

	// Initialize inventory.ini file
	inventory := Inventory{serverList, tidbServers, tikvServers, pdServers, monitorServers}

	f, err := os.Create(inventoryFilePath)
	if err != nil {
		log.Fatalf("failed to create inventory.ini file, err: %v", err)
	}
	defer f.Close()

	tpl, err := template.ParseFiles("inventory.ini.tmpl")
	if err != nil {
		log.Fatalf("failed to parse template inventory.ini.tmpl file, err: %v", err)
	}
	err = tpl.Execute(f, inventory)
	if err != nil {
		log.Fatalf("failed to initialize inventory.ini file, err: %v", err)
	}
}
