package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/joho/godotenv"

	"github.com/UpCloudLtd/upcloud-go-api/upcloud"

	"github.com/UpCloudLtd/upcloud-go-api/upcloud/client"
	"github.com/UpCloudLtd/upcloud-go-api/upcloud/request"
	"github.com/UpCloudLtd/upcloud-go-api/upcloud/service"
)

func cmdexec(command string, args []string) (string, error) {
	cmd := &exec.Cmd {
			Path: command,
			Args: args,
			// Stdout: os.Stdout,
			// Stderr: os.Stdout,
	}
	if out, err := cmd.Output(); err != nil {
		fmt.Fprintf(os.Stderr, "%s error: %#v\n",command,err)
		return "",err
	} else {
		return string(out), nil
  }
	// cmd.Start();
	// cmd.Wait()
}
func run() int {
	runFlags, err := ParseFlags()
	if err != nil {
		log.Fatal(err)
	}
	username := os.Getenv("UPCLOUD_USERNAME")
	password := os.Getenv("UPCLOUD_PASSWORD")
	k := os.Getenv("KUPCLAPI")
	if len(username) == 0 && len(k) == 0 {
		err = godotenv.Load("/etc/upcloud/.env") 
		if err != nil {
		  err = godotenv.Load("/usr/local/etc/upcloud/.env") 
		}
	}	
	if runFlags.encdr == TECODER.cmd || runFlags.encdr == TECODER.abbrv {
		k := os.Getenv("KUPCLAPI")
		if len(k) > 0 {
			kdata := strings.Split(k," ")
			out, err := exec.Command(TECODER.cmd,TECODER.decoder,kdata[0]).Output()
			if err != nil {
		    fmt.Fprintf(os.Stderr, "Auth info: Username not found in ecoded: %#v\n ",err)
		    return 1
			}
			username = strings.TrimSuffix(string(out),"\n")
			out, err = exec.Command(TECODER.cmd,TECODER.decoder,kdata[1]).Output()
			if err != nil {
		    fmt.Fprintf(os.Stderr, "Auth info: Password not found in ecoded: %#v\n",err)
		    return 1
			}
			password = strings.TrimSuffix(string(out),"\n")
		}
	} else {
		username = os.Getenv("UPCLOUD_USERNAME")
	  password = os.Getenv("UPCLOUD_PASSWORD")
	}
	if len(username) == 0 {
		var upcloud_env map[string]string
		upcloud_env, err = godotenv.Read(os.Getenv("HOME")+ "/.config/upctl.yaml")
		if len(upcloud_env["username"]) != 0 {
			username = upcloud_env["username"]
			password = upcloud_env["password"]
		}
	}
	if len(username) == 0 {
		fmt.Fprintln(os.Stderr, "Auth info: Username must be specified")
		return 1
	}
	if len(password) == 0 {
		fmt.Fprintln(os.Stderr, "Auth info: Password must be specified")
		return 2
	}

	c := client.New(username, password)
	s := service.New(c)

	switch runFlags.command {
	case "infoserver":
		datacfg := &DataConfig{}
		if runFlags.id == "" {
       datacfg,err = loadDataConfig(runFlags)
		}
		onServers(s,"info",runFlags,datacfg)
	case "inventory":
		datacfg := &DataConfig{}
		if runFlags.id == "" {
       datacfg,err = loadDataConfig(runFlags)
		}
		onServers(s,"inventory",runFlags,datacfg)
	case "pubhosts":
		datacfg := &DataConfig{}
		if runFlags.id == "" {
       datacfg,err = loadDataConfig(runFlags)
		}
		onServers(s,"pubhosts",runFlags,datacfg)
	case "prvhosts":
		datacfg := &DataConfig{}
		if runFlags.id == "" {
       datacfg,err = loadDataConfig(runFlags)
		}
		onServers(s,"prvhosts",runFlags,datacfg)
	case "deleteserver":
		datacfg := &DataConfig{}
		if runFlags.id == "" {
       datacfg,err = loadDataConfig(runFlags)
		}
		onServers(s,"delete",runFlags,datacfg)
	case "startserver":
		datacfg := &DataConfig{}
		if runFlags.id == "" {
       datacfg,err = loadDataConfig(runFlags)
		}
		onServers(s,"start",runFlags,datacfg)
	case "restartserver":
		datacfg := &DataConfig{}
		if runFlags.id == "" {
       datacfg,err = loadDataConfig(runFlags)
		}
		onServers(s,"restart",runFlags,datacfg)
	case "stopserver":
		datacfg := &DataConfig{}
		if runFlags.id == "" {
     datacfg,err = loadDataConfig(runFlags)
		}
		onServers(s,"stop",runFlags,datacfg)
	case "createserver":
    datacfg,err := loadDataConfig(runFlags)
	  if err != nil {
		  return 99
		}
	  sshkeys := loadLoginUserKeys(runFlags)	
		if err := createServer(s,datacfg,sshkeys); err != nil {
		 	return 3
		}
	case "modifyserver":
		datacfg := &DataConfig{}
		if runFlags.id == "" {
       datacfg,err = loadDataConfig(runFlags)
		}
		if runFlags.target == "" {
			fmt.Fprintln(os.Stderr, "Unable to run modifyserver command: ", runFlags.command)
			return 99
		}
		onServers(s,"modifyserver",runFlags,datacfg)
	case "fixstorage":
    datacfg,err := loadDataConfig(runFlags)
		if err != nil {
			return 99
		}
		if runFlags.target == "" {
       datacfg,err = loadDataConfig(runFlags)
		}
		onServers(s,"fixstorage",runFlags,datacfg)
	case "modifystorage":
	  storecfg,err := loadStoreConfig(runFlags.dataCfgPath)
		if err != nil {
			return 99
		}
		if err := modifyStorage(s,storecfg); err != nil {
			return 3
		}	
	case "deletestorage":
		hasID(runFlags.id)
		if err := deleteStorage(s, runFlags.id); err != nil {
			return 2
		}
	case "liststorages":
		datacfg,_ := loadDataConfig(runFlags)
		if err := listStorages(s, datacfg, runFlags.target); err != nil {
			return 2
		}
	case "createstorageimage":
		hasID(runFlags.id)
		if runFlags.target == "" {
			fmt.Fprintln(os.Stderr, "Unable to run createStorageImage no title: ", runFlags.target)
			return 99
		}
		if err := createStorageImage(s, runFlags.id,runFlags.target); err != nil {
			return 2
		}
	case "runssh":
	  if len(runFlags.runCmd) == 0 {
			fmt.Fprintln(os.Stderr, "Unable to run ssh command: ", runFlags.command)
			return 99
		}
    datacfg,err := loadDataConfig(runFlags)
		if err != nil {
			fmt.Fprintln(os.Stderr, "Unable to run ssh command: ", runFlags.command)
			return 99
		}
		onServers(s,"runssh",runFlags,datacfg)
	case "runcmd":
	  if len(runFlags.runCmd) == 0 {
			fmt.Fprintln(os.Stderr, "Unable to run command: ", runFlags.command)
			return 99
		}
    datacfg,err := loadDataConfig(runFlags)
		if err != nil {
			fmt.Fprintln(os.Stderr, "Unable to run command: ", runFlags.command)
			return 99
		}
		onServers(s,"runcmd",runFlags,datacfg)
	case "infofloatip":
		datacfg := &DataConfig{}
		if runFlags.id == "" {
      datacfg,err = loadDataConfig(runFlags)
		}
		ipinfo, err := getFloatIP(s,runFlags.id,datacfg)
	  if err != nil {
		  fmt.Fprintf(os.Stderr, "error getting info %s: %#v\n", datacfg.FloatIP, err)
		  return 99
		}
		if runFlags.out == "attached" {
			ipData := strings.Split(ipinfo.PTRRecord,".")
			ip := strings.Join(ipData[0:4],".")
			fmt.Fprintf(os.Stdout, "%s\n",ip)
		} else {
			enc := json.NewEncoder(os.Stdout)
			enc.Encode(ipinfo)
		}
		// fmt.Fprintf(os.Stderr, "%s Located in %s %s Server: %s) \n", datacfg.FloatIP, ipinfo.PTRRecord, ipinfo.MAC, ipinfo.ServerUUID)
	case "movefloatip":
		datacfg := &DataConfig{}
		id := runFlags.id
		var data []string
		if id == "" {
			id = os.Getenv("UPCLOUD_MOVEFLOATIP")
		}
		if id == "" {
      datacfg,err = loadDataConfig(runFlags)
			if datacfg.FloatIP == "" {
				fmt.Fprintln(os.Stderr, "Float IP not found in: ", runFlags.dataCfgPath)
				return 99
			}
		} else {
			data = strings.Split(id, SEPARATOR)
		}
		moveFloatIP(s,data,datacfg)
		if err != nil {
			fmt.Fprintln(os.Stderr, "Unable to move Float IP: ", datacfg.FloatIP)
			return 99
		}
	case "modifyip":
		datacfg,err := loadDataConfig(runFlags)
		if err != nil {
		  return 99
		}
		onServers(s,"modifyip",runFlags,datacfg)
	case "infotags":
		datacfg := &DataConfig{}
		if runFlags.id == "" {
       datacfg,err = loadDataConfig(runFlags)
		}
		onServers(s,"infotags",runFlags,datacfg)
	case "addtags":
		datacfg := &DataConfig{}
		if runFlags.id == "" {
       datacfg,err = loadDataConfig(runFlags)
		}
		onServers(s,"addtags",runFlags,datacfg)
	case "deletetags":
		datacfg := &DataConfig{}
		if runFlags.id == "" {
       datacfg,err = loadDataConfig(runFlags)
		}
		onServers(s,"deletetags",runFlags,datacfg)
	default:
		fmt.Fprintln(os.Stderr, "Unknown command: ", runFlags.command)
		return 99
	}
	return 0
}

func onServers(s *service.Service, tsksrvc string, runFlags RunFlags, datacfg *DataConfig) error {
	uuid := runFlags.id
	runCommand := runFlags.runCmd
	servers, err := s.GetServers()
	if err != nil || len(servers.Servers) == 0 {
		fmt.Fprintf(os.Stderr, "Unable to get servers: %#v\n", err)
		return err
	}
	inventory := map[string][]string{}
	sshAccess := SSHAccess{}
	var target ServerConfig
	for _, server := range servers.Servers {
		target_srvr := ""
		role_srvr := ""
		if uuid != "" {
			if server.UUID == uuid || server.Hostname == uuid {
				target_srvr = uuid
				for _,srv := range datacfg.Servers {
					if srv.Hostname == server.Hostname {
						target = srv
						role_srvr = srv.Cluster.Role
						sshAccess = srv.SSHAccess
						break;
					}
				}
			}
		} else {
			if datacfg.HostPrefix != "" && !strings.Contains(server.Hostname,datacfg.HostPrefix) {
				continue
			}
			for _,srv := range datacfg.Servers {
				if srv.Hostname == server.Hostname || srv.UUID == server.UUID {
					target_srvr = server.UUID
					target = srv
					role_srvr = srv.Cluster.Role
					sshAccess = srv.SSHAccess
					break;
				}
			}
		}
		if target_srvr == "" {
			continue
		}
		switch tsksrvc {
			case "delete":
				err := deleteOneServer(s, server, runFlags.target)
				if err != nil {
						fmt.Fprintf(os.Stderr, "Unable to delete server %#v: %#v\n", server.UUID, err)
						return err
				}
			case "stop":
				err := stopOneServer(s, server)
				if err != nil {
						fmt.Fprintf(os.Stderr, "Unable to stop server %#v: %#v\n", server.UUID, err)
				}
			case "start":
				err := startOneServer(s, server)
				if err != nil {
					fmt.Fprintf(os.Stderr, "Unable to start server %#v: %#v\n", server.UUID, err)
				}
			case "restart":
				err := restarOneServer(s, server)
				if err != nil {
					fmt.Fprintf(os.Stderr, "Unable to restart server %#v: %#v\n", server.UUID, err)
				}
			case "info":
				dataPrices,err_prices := getDataPrices()
				if err_prices != nil {
					fmt.Fprintf(os.Stderr, "Unable to load prices for server %#v: %#v\n", server.UUID, err_prices)					
				}
				err := detailsOneServer(s, server, *dataPrices)
				if err != nil {
					fmt.Fprintf(os.Stderr, "Unable to get details for server %#v: %#v\n", server.UUID, err)
				}
			case "inventory":
				info, err := inventoryOneServer(s, datacfg.FloatIP, server)
				if err != nil {
					fmt.Fprintf(os.Stderr, "Unable to make inventory from server %#v: %#v\n", server.UUID, err)
				} else {
					inventory[role_srvr] = append(inventory[role_srvr],info)
				}
			case "pubhosts":
				info, err := hostsOneServer(s,"pub", datacfg.FloatIP, server, target)
				if err != nil {
					fmt.Fprintf(os.Stderr, "Unable to make hosts list from server %#v: %#v\n", server.UUID, err)
				} else {
					fmt.Printf("%s",info) 
				}
			case "prvhosts":
				info, err := hostsOneServer(s,"prv", datacfg.FloatIP, server, target)
				if err != nil {
					fmt.Fprintf(os.Stderr, "Unable to make hosts list from server %#v: %#v\n", server.UUID, err)
				} else {
					fmt.Printf("%s",info) 
				}
			case "modifyip":
				nettype := upcloud.NetworkTypePrivate
				err := modifyIPOneServer(s, server, nettype, target)
				if err != nil {
					fmt.Fprintf(os.Stderr, "Unable to modify IP server %#v: %#v\n", server.UUID, err)
				}
			case "infotags":
				tags, err := infoTagsFromId(s, server.UUID)
				if err != nil {
					fmt.Fprintf(os.Stderr, "Unable to get tags from server %#v: %#v\n", server.UUID, err)
				} else {
					enc := json.NewEncoder(os.Stdout)
	  			enc.Encode(tags)
				}
			case "addtags":
				if uuid != "" {
					fmt.Fprintf(os.Stderr, "No tags found, for server %#v: %#v\n", server.UUID, err)
				} else {
					err := addTagsToId(s, server.UUID, target.Tags)
					if err != nil {
						fmt.Fprintf(os.Stderr, "Unable to add tags to server %#v: %#v\n", server.UUID, err)
					}
				}
			case "deletetags":
				if uuid != "" {
					fmt.Fprintf(os.Stderr, "No tags found, for server %#v: %#v\n", server.UUID, err)
				} else {
					err := deleteTagsFromId(s, server.UUID, target.Tags)
					if err != nil {
						fmt.Fprintf(os.Stderr, "Unable to delete tags from server %#v: %#v\n", server.UUID, err)
					}
				}
			case "fixstorage":
				info,err := s.GetServerDetails(&request.GetServerDetailsRequest{
					UUID: server.UUID,
       	   })
				if err != nil {
					fmt.Fprintf(os.Stderr, "Unable to get details from server %#v: %#v\n", server.UUID, err)
				} else {
			 	  fixStorageDevices(s, target, info, runFlags.target)
				}
			case "modifyserver":
				info,err := s.GetServerDetails(&request.GetServerDetailsRequest{
					UUID: server.UUID,
       	})
				if err != nil {
					fmt.Fprintf(os.Stderr, "Unable to get details from server %#v: %#v\n", server.UUID, err)
				} else {
			 	  modifyServer(s, target, info, runFlags.target)
				}
			case "runssh":
			  if sshAccess.Host != "" {
				  output, err := runSSH(sshAccess, runCommand) 
					if err != nil {
						log.Fatal(err)
					}
					fmt.Println(string(output))
				} else {
				 	fmt.Fprintf(os.Stderr, "No Host to run ssh on %s \n", server.UUID)	
				}
			case "runcmd":
				args := strings.Split(runCommand, " ")
				output, err := cmdexec(args[0],args[1:]) 
				if err != nil {
					log.Fatal(err)
				 	fmt.Fprintf(os.Stderr, "No Host to run on %s \n", server.UUID)	
				}
				fmt.Println(string(output))
		}	
	}
	if tsksrvc == "inventory" {
		for key,items := range inventory {
			fmt.Printf("[%s]\n",key) 
		  for _,item := range items {
			   fmt.Printf("%s\n",item) 
			}
		}
	}
	return nil
}