package main

import (
	"flag"
	"fmt"
	"path/filepath"

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

	"encoding/json"
	"log"
	"math/rand"
	"os"
	"time"

	"gopkg.in/yaml.v3"
	// https://abhinavg.net/posts/flexible-yaml/
)

const SEPARATOR = "::"
const DATA_CFG = "datacfg.yaml"
const DFLT_COMMAND= "infoserver"
const SSH_KEY_PATH = ".sshkeys"

const MONTH_HOURS = 672

var username string
var password string

var TECODER = CoderConfig {
	cmd: "tecoder",
	abbrv: "tc",
	decoder: "-d",
	fdecoder: "--fdecrypt",
	encoder: "-e",
	fencoder: "--fencrypt",
}

type RunFlags struct {
	command string
	dataCfgPath string
	id string
	runCmd string
	encdr string
	out string
	target string
}
type CoderConfig struct {
	cmd string
  abbrv string
	decoder string
	fdecoder string
	encoder string
	fencoder string
}
type StoreConfig struct {
	Title   string `yaml:"title"`
	Uuid string `yaml:"uuid"`
	Size    int `yaml:"size"`	
	Servers []string `yaml:"servers"`
}
type DataStorageDevices struct {
	Action  string `yaml:"action"`
	Title   string `yaml:"title"`
	Storage string `yaml:"storage"`
	Size    int `yaml:"size"`
	FinalSize    int `yaml:"finalSize"`
	PartSizes string `yaml:"partSizes"`
	MakeFs string `yaml:"makefs"`
	Tier    string `yaml:"tier"`
	Source  string `yaml:"source"`
	// Type: string `yaml:"type"`
	// Address string `json:"address,omitempty"`
}

type NetworksConfig struct {
  Access string `yaml:"access"` 
  Family string `yaml:"family"` 
  Network string `yaml:"network"` 
  IPAddress string `yaml:"ipaddress"` 
	SourceIPFiltering string `yaml:"source_ip_filtering"`
//	SourceIPFiltering upcloud.Boolean `yaml:"source_ip_filtering"`
}

type ClusterRole struct {
	Role string `yaml:"role"`
}

type TskSrvc struct {
	name string `yaml:"name"`
	path string `yaml:"path"`
	target string `yaml:"target"`
	req string `yaml:"req"`
	liveness string `yaml:"liveness"`
}

type App struct {
	name string `yaml:"name"`
	path string `yaml:"path"`
	target string `yaml:"target"`
	req string `yaml:"req"`
	liveness string `yaml:"liveness"`
}

type SSHAccess struct {
	Host string `yaml:"host"`
	User string `yaml:"user"`
	Password string `yaml:"password"`
	UType string `yaml:"utype"`
	KeyPath string `yaml:"keyPath"`
	Port int `yaml:"port"`
}
type LoginUserConfig struct {
	// CreatePassword string `yaml:"createPassword"`
	// Username string `yaml:"username"`
	SSHKeys []string `yaml:"sshKeys"`
}

type Cntrllrs struct {
	host string `yaml:"host"`
	sshaccess SSHAccess `yaml:"sshaccess"`
	cldPath string `yaml:"cldPath"`
	masterPath string `yaml:"masterPath"`
}

// https://abhinavg.net/posts/flexible-yaml/

type ServerConfig struct {
  Hostname string `yaml:"hostname"` 
  Title string `yaml:"title"` 
  UserData string `yaml:"userData"`
  Zone string `yaml:"zone"`
  UUID string `yaml:"uuid"`
	Host string `yaml:"host"`
	Tags []string `yaml:"tags"`
	Cluster ClusterRole `yaml:"cluster"`
	SSHAccess SSHAccess `yaml:"sshAccess"`
	LoginUser LoginUserConfig `yaml:"loginUser"`
	Plan string `yaml:"plan"`
	Metadata upcloud.Boolean `yaml:"metadata"`
	TimeZone string `yaml:"timeZone"`
	Networks []NetworksConfig `yaml:"networks"`
	StorageDevices []DataStorageDevices `yaml:"storages"`
	UseFloatIP bool `yaml:"useFloatIP"` 
	TskSrvcs []TskSrvc `yaml:"tsksrvcs"`
	Apps []App `yaml:"apps"`
	Clients []string `yaml:"clients"`
	Rules []string `yaml:"rules"`
	Backup []string `yaml:"backup"`
}

type DataConfig struct {
	Servers []ServerConfig `yaml:"servers"`
	FloatIP string `yaml:"floatIP"`
	Group string `yaml:"group"`
	GroupPath string `yaml:"group_path"`
	HostPrefix string `yaml:"hostPrefix"`
	MainName string `yaml:"main"`
	domainName string `yaml:"domainName"`
	cntrllrs []Cntrllrs `yaml:"cntrllrs"`
}

type InfoServer struct {
	info upcloud.ServerDetails `json:"ServerDetails"`
 	price Price `json:"Price"`
 	// price upcloud.Price `json:"Price"`
}
/*
type CloudGroup struct {
	name string `yaml:"name"`
	path string `yaml:"path"`
	info string `yaml:"info"`
	resources ConfigResources `yaml:"resources"`
}

type KloudHome struct {
	name string `yaml:"name"`
	title string `yaml:"title"` 
	dflts string `yaml:"dflts"`
	provider []ProviderName `yaml:"provider"`
	netips []IpDef `yaml:"netips"`
	cntrllrs []Cntrllrs `yaml:"cntrllrs"`
	groups []CloudGroup `yaml:"groups"`
}
*/
// type DDataConfig struct {
// 	// Servers []ServerConfig `yaml:"servers"`
// 	floatIP string `yaml:"floatIP"`
// 	hostPrefix string `yaml:"hostPrefix"`
// 	mainName string `yaml:"main"`
// }

// Price represents a price
type Price struct {
	Amount int     `yam:"amount"`
	Price  float64 `yaml:"price"`
	Hour  float64 `yaml:"price_hour"`
	Month  float64 `yaml:"price_month"`
}

type DataPrices struct {
	Prices PricesZones `yaml:"prices"`
}
type PricesZones struct {
	Zone []PricesZone `yaml:"zone"`
}

type PricesZone struct {
	Name string `yaml:"name"`
	Firewall               Price `yaml:"firewall"`
	IORequestBackup        Price `yaml:"io_request_backup"`
	IORequestHDD        	 Price `yaml:"io_request_hdd"`
	IORequestMaxIOPS       Price `yaml:"io_request_maxiops"`
	IPv4Address            Price `yaml:"ipv4_address"`
	IPv6Address            Price `yaml:"ipv6_address"`
	ManagedDatabase1x1CPU2GB25GB    Price `yaml:"managed_database_1x1xCPU-2GB-25GB"`
	ManagedDatabase1x2CPU4GB100GB   Price `yaml:"managed_database_1x2xCPU-4GB-100GB"`
	ManagedDatabase1x2CPU4GB50GB    Price `yaml:"managed_database_1x2xCPU-4GB-50GB"`
	ManagedDatabase2x2CPU4GB100GB   Price `yaml:"managed_database_2x2xCPU-4GB-100GB"`
	ManagedDatabase2x2CPU4GB50GB    Price `yaml:"managed_database_2x2xCPU-4GB-50GB"`
	ManagedDatabase2x4CPU8GB100GB   Price `yaml:"managed_database_2x4xCPU-8GB-100GB"`
	ManagedDatabase2x4CPU8GB50GB    Price `yaml:"managed_database_2x4xCPU-8GB-50GB"`
	ManagedDatabase2x6CPU16GB100GB  Price `yaml:"managed_database_2x6xCPU-16GB-100GB"`
	ManagedDatabase2x6CPU16GB50GB   Price `yaml:"managed_database_2x6xCPU-16GB-250GB"`
	ManagedDatabase2x8CPU32GB100GB  Price `yaml:"managed_database_2x8xCPU-32GB-100GB"`
	ManagedDatabase2x8CPU22GB250GB  Price `yaml:"managed_database_2x8xCPU-32GB-250GB"`
	ManagedDatabase3x2CPU4GB100GB   Price `yaml:"managed_database_3x2xCPU-4GB-100GB"`
	ManagedDatabase3x2CPU4GB200GB   Price `yaml:"managed_database_3x2xCPU-4GB-200GB"`
	ManagedDatabase3x4CPU8GB100GB   Price `yaml:"managed_database_3x4xCPU-8GB-100GB"`
	ManagedDatabase3x4CPU8GB200GB   Price `yaml:"managed_database_3x4xCPU-8GB-200GB"`
	ManagedDatabase3x6CPU16GB200GB  Price `yaml:"managed_database_3x6xCPU-16GB-200GB"`
	ManagedDatabase3x6CPU16GB500GB  Price `yaml:"managed_database_3x6xCPU-16GB-500GB"`
	ManagedDatabase3x8CPU32GB200GB  Price `yaml:"managed_database_3x8xCPU-32GB-200GB"`
	ManagedDatabase3x8CPU32GB500GB  Price `yaml:"managed_database_3x8xCPU-32GB-500GB"`
	NetworkPrivateVLAN     Price `yaml:"network_private_vlan"`
	ObjectStorage1TB       Price `yaml:"object_storage_1TB"`
	ObjectStorage250GB     Price `yaml:"object_storage_250GB"`
  ObjectStorage500GB     Price `yaml:"object_storage_500GB"`
	PublicIPv4BandwidthIn  Price `yaml:"public_ipv4_bandwidth_in"`
	PublicIPv4BandwidthOut Price `yaml:"public_ipv4_bandwidth_out"`
	PublicIPv6BandwidthIn  Price `yaml:"public_ipv6_bandwidth_in"`
	PublicIPv6BandwidthOut Price `yaml:"public_ipv6_bandwidth_out"`
	ServerCore             Price `yaml:"server_core"`
	ServerMemory           Price `yaml:"server_memory"`
	ServerPlan6xCPU8GB     Price `yaml:"server_plan_6xCPU-8GB"`
	ServerPlan12xCPU48GB   Price `yaml:"server_plan_12xCPU-48GB"`
  ServerPlan16xCPU64GB   Price `yaml:"server_plan_16xCPU-64GB"`
  ServerPlan1xCPU1GB     Price `yaml:"server_plan_1xCPU-1GB"`
  ServerPlan1xCPU2GB     Price `yaml:"server_plan_1xCPU-2GB"`
  ServerPlan20xCPU128GB  Price `yaml:"server_plan_20xCPU-128GB"`
  ServerPlan20xCPU96GB   Price `yaml:"server_plan_20xCPU-96GB"`
  ServerPlan2xCPU4GB     Price `yaml:"server_plan_2xCPU-4GB"`
  ServerPlan4xCPU8GB     Price `yaml:"server_plan_4xCPU-8GB"`
  ServerPlan6xCPU16GB    Price `yaml:"server_plan_6xCPU-16GB"`
  ServerPlan8xCPU32GB    Price `yaml:"server_plan_8xCPU-32GB"`
	SimpleBackupDailies12xCPU48GB     Price `yaml:"simple_backup_dailies_12xCPU-48GB"`
	SimpleBackupDailies16xCPU64GB     Price `yaml:"simple_backup_dailies_16xCPU-64GB"`
	SimpleBackupDailiesx1CPU1GB       Price `yaml:"simple_backup_dailies_1xCPU-1GB"`
	SimpleBackupDailies1xCPU2GB       Price `yaml:"simple_backup_dailies_1xCPU-2GB"`
	SimpleBackupDailies20xCPU128GB    Price `yaml:"simple_backup_dailies_20xCPU-128GB"`
	SimpleBackupDailies20xCPU96GB     Price `yaml:"simple_backup_dailies_20xCPU-96GB"`
	SimpleBackupDailies2xCPU4GB       Price `yaml:"simple_backup_dailies_2xCPU-4GB"`
	SimpleBackupDailies4xCPU8GB       Price `yaml:"simple_backup_dailies_4xCPU-8GB"`
	SimpleBackupDailies6xCPU16GB      Price `yaml:"simple_backup_dailies_6xCPU-16GB"`
	SimpleBackupDailies8xCPU32GB      Price `yaml:"simple_backup_dailies_8xCPU-32GB"`
	SimpleBackupExtraDailies          Price `yaml:"simple_backup_extra_dailies"`
	SimpleBackupExtraMontlies         Price `yaml:"simple_backup_extra_monthlies"`
	SimpleBackupExtraWeeklies         Price `yaml:"simple_backup_extra_weeklies"`
	SimpleBackupMonthlies12xCPU48GB   Price `yaml:"simple_backup_monthlies_12xCPU-48GB"`
	SimpleBackupMonthlies16xCPU64GB   Price `yaml:"simple_backup_monthlies_16xCPU-64GB"`
	SimpleBackupMonthlies1xCPU1GB     Price `yaml:"simple_backup_monthlies_1xCPU-1GB"`
	SimpleBackupMonthlies1xCPU2GB     Price `yaml:"simple_backup_monthlies_1xCPU-2GB"`
	SimpleBackupMonthlies20xCPU128GB  Price `yaml:"simple_backup_monthlies_20xCPU-128GB"`
	SimpleBackupMonthlies20xCPU96GB   Price `yaml:"simple_backup_monthlies_20xCPU-96GB"`
	SimpleBackupMonthlies2xCPU4GB     Price `yaml:"simple_backup_monthlies_2xCPU-4GB"`
	SimpleBackupMonthlies4xCPU8GB     Price `yaml:"simple_backup_monthlies_4xCPU-8GB"`
	SimpleBackupMonthlies6xCPU16GB    Price `yaml:"simple_backup_monthlies_6xCPU-16GB"`
	SimpleBackupMonthlies8xCPU32GB    Price `yaml:"simple_backup_monthlies_8xCPU-32GB"`
	SimpleBackupWeeklies12xCPU48GB    Price `yaml:"simple_backup_weeklies_12xCPU-48GB"`
	SimpleBackupWeeklies16xCPU64GB    Price `yaml:"simple_backup_weeklies_16xCPU-64GB"`
	SimpleBackupWeeklies1xCPU1GB      Price `yaml:"simple_backup_weeklies_1xCPU-1GB"`
	SimpleBackupWeeklies1xCPU2GB      Price `yaml:"simple_backup_weeklies_1xCPU-2GB"`
	SimpleBackupWeeklies20xCPU128GB   Price `yaml:"simple_backup_weeklies_20xCPU-128GB"`
	SimpleBackupWeeklies20xCPU96GB    Price `yaml:"simple_backup_weeklies_20xCPU-96GB"`
	SimpleBackupWeeklies2xCPU4GB      Price `yaml:"simple_backup_weeklies_2xCPU-4GB"`
	SimpleBackupWeeklies4xCPU8GB      Price `yaml:"simple_backup_weeklies_4xCPU-8GB"`
	SimpleBackupWeeklies6xCPU16GB     Price `yaml:"simple_backup_weeklies_6xCPU-16GB"`
	SimpleBackupWeeklies8xCPU32GB     Price `yaml:"simple_backup_weeklies_8xCPU-32GB"`
	StorageHDD          Price `yaml:"storage_hdd"`
	StorageBackup          Price `yaml:"storage_backup"`
	StorageMaxIOPS         Price `yaml:"storage_maxiops"`
	StorageTemplate        Price `yaml:"storage_template"`
}

func (m *InfoServer) MarshalJSON() ([]byte, error) {
	// meta := `"Id":` + strconv.Itoa(m.Meta.Id)
  info, err_info := json.Marshal(m.info)
	if err_info != nil {
		return nil, err_info
	}
	price, err_price := json.Marshal(m.price)
	if err_price != nil {
		return nil, err_price
	}
	// fmt.Printf("price %s \n", price)
	// Stitching it all together
	// return []byte(`{` + meta + `,"Contents":` + string(cont) + `}`), nil
	return []byte(`{"info":` + string(info) + `,"price":` + string(price) + `}`), nil
}
//func (s *InfoServer) UnmarshalJSON(b []byte) error {
	// type serverWrapper struct {
	// 	PriceZones []PriceZone `json:"zone"`
	// }

	// v := struct {
	// 	PriceZones serverWrapper `json:"prices"`
	// }{}
	// err := json.Unmarshal(b, &v)
	// if err != nil {
	// 	return err
	// }

	// s.PriceZones = v.PriceZones.PriceZones

// 	return nil
// }

func getDataPrices() (*DataPrices, error) {
	dataPrices := &DataPrices{}
	if len(os.Getenv("UPCLOUD_PRICES")) == 0 {
	  return dataPrices,nil
	}
	file, err := os.Open(os.Getenv("UPCLOUD_PRICES"))
	if err != nil {
	  return dataPrices,err
	}
	defer file.Close()

	// Init new YAML decode
	d := yaml.NewDecoder(file)
	// Start YAML decoding from file
	if err := d.Decode(&dataPrices); err != nil {
	  return dataPrices,err
	}
	// fmt.Printf("Loaded prices: %#v\n", dataPrices)
	return dataPrices,nil
}
// NewDataConfig returns a new decoded DataConfig struct
func NewDataConfig(runFlags RunFlags) (*DataConfig, error) {
	// Load config structure from YAML
	dataConfig := &DataConfig{}
	// fmt.Printf("Loaded config: %#v\n", runFlags.dataCfgPath)
	/*
	if runFlags.encdr == TECODER.cmd || runFlags.encdr == TECODER.abbrv {
		// content, err := ioutil.ReadFile(runFlags.dataCfgPath)
    // if err != nil {
		// 	log.Fatal(err)
		// 	return nil,err
    // }
	  //out, err := exec.Command(CODER,ARGS_DECODER,string(content)).Output()
	  out, err := exec.Command(TECODER.cmd,TECODER.fdecoder,runFlags.dataCfgPath).Output()
		if err != nil {
		  fmt.Fprintf(os.Stderr, "Unable to decode %s error: %#v\n ",runFlags.dataCfgPath,err)
		  return nil, err
		}
		err = yaml.Unmarshal(out,&dataConfig)
		if err != nil {
			return nil, err
		}
	} else {
/*
	  ddataConfig := &DDataConfig{}
		bytes, err := ioutil.ReadFile(runFlags.dataCfgPath)
		if err != nil {
			panic(err)
		}
		err = dhall.Unmarshal(bytes, &ddataConfig)
		if err != nil {
			panic(err)
		}
		fmt.Printf("Loaded config: %#v\n", ddataConfig)
*/	
		// Open config file
		file, err := os.Open(runFlags.dataCfgPath)
		if err != nil {
			return nil, err
		}
		defer file.Close()

		// Init new YAML decode
		d := yaml.NewDecoder(file)
		// Start YAML decoding from file
		if err := d.Decode(&dataConfig); err != nil {
			return nil, err
		}
		// fmt.Printf("Loaded config: %#v\n", dataConfig)
//	}
	return dataConfig, nil
}
func getAuthKeysData(path string) string {
	pathAuth := fmt.Sprintf("%s/%s", path,SSH_KEY_PATH)
	// fmt.Fprintf(os.Stdout, "%#v\n",pathAuth)
	if _, err := os.Stat(pathAuth); err == nil {
		file, err := os.Open(pathAuth)
		if err != nil {
			return ""
		}
		defer file.Close() 
	}
  return pathAuth
}
func loadLoginUserKeys(runFlags RunFlags) request.SSHKeySlice {
	dirAuth := filepath.Dir(runFlags.dataCfgPath)
	keysPath := getAuthKeysData(dirAuth)
	for i := 0; i < 3; i++ {
	  if keysPath == "" {
			break
		}
	  dirAuth = filepath.Dir(dirAuth)
	  keysPath = getAuthKeysData(dirAuth)
	}
	if keysPath == "" {
		return nil
	}
	file, err := os.Open(keysPath)
	if err != nil {
		return nil
	}
	defer file.Close() 
	// fmt.Fprintf(os.Stdout, "%#v\n",keysPath)
	var sshKeys request.SSHKeySlice
	d := yaml.NewDecoder(file)
	if err := d.Decode(&sshKeys); err != nil {
		fmt.Fprintf(os.Stdout, "%#v\n",err)
		return nil
	}
	// fmt.Fprintf(os.Stdout, "%#v\n",sshKeys)
  return sshKeys
}

func init() {
	rand.Seed(time.Now().Unix())
}

func main() {
	if len(os.Args) > 1 || len(os.Getenv("UPCLAPI_COMMAND")) > 0 {
		 os.Exit(run())
	} else {
		fmt.Fprintln(os.Stderr, "Use --help to see options")
	}
}

func hasID(id string) bool {
	if len(id) == 0 {
		fmt.Fprintln(os.Stderr, "D must be specified")
		os.Exit(2)
	}
	return true
}

func loadDataConfig(runFlags RunFlags) (*DataConfig, error) {
	// datacfg := &DataConfig{}
	datacfg, err := NewDataConfig(runFlags)
	if err != nil {
		log.Fatal(err)
	  return datacfg, err
	}
	return datacfg, nil
}

// ValidateDataPath just makes sure, that the path provided is a file,
// that can be read
func ValidateDataPath(path string) error {
	s, err := os.Stat(path); os.IsNotExist(err) 
	if err != nil {
		return fmt.Errorf("'%s' config file not found", path)
	}
	if s.IsDir() {
		return fmt.Errorf("'%s' is a directory, not a normal file", path)
	}
	return nil
}

// ParseFlags will create and parse the CLI flags
// and return the path to be used elsewhere
func ParseFlags() (RunFlags,error) {
	// target := flag.Arg(1)
	// fmt.Printf("%+v ...\n",target)	// Print with Variable Name
	// String that contains the configured configuration path
	runFlags := RunFlags {
		command: 		 os.Getenv("UPCLAPI_COMMAND"),
		dataCfgPath: os.Getenv("UPCLAPI_DATACFG"),
		id: 				 os.Getenv("UPCLAPI_ID"),
		runCmd:			 os.Getenv("UPCLAPI_RUNCMD"),
		encdr: 			 os.Getenv("UPCLAPI_ENCODER"),
		out:         os.Getenv("UPCLAPI_OUT"),
		target:      os.Getenv("UPCLAPI_TARGET"),
	}
	if runFlags.command == "" {
		runFlags.command = DFLT_COMMAND
	}
	if runFlags.dataCfgPath == "" {
		runFlags.dataCfgPath = DATA_CFG
	}
	// var command string
	// var dataCfgPath string
	// var id string
	// var cmd string
	// var encdr string

	// Set up a CLI flag called "-config" to allow users
	// to supply the configuration file
	commandInfo := fmt.Sprintf("command to run [\n%s\n%s\n%s\n%s\n%s\n%s\n]", 
		"createserver, infoserver, restartserver, startserver, stopserver, modifyserver, deleteserver,",
		"infofloatip, movefloatip, modifyip,",
		"infotags, addtags, deletetags (use -t keep to keep storages)",
		"liststorages, fixstorage (-t size | part0 | part1 | final), deletestorage, modifystorage, createstorageimage (-t title),",
		"inventory, pubhosts, prvhosts",
		"runssh, runcmd")
	flag.StringVar(&runFlags.command, "c", runFlags.command, commandInfo)
	flag.StringVar(&runFlags.dataCfgPath, "f", runFlags.dataCfgPath, "path to data file")
	flag.StringVar(&runFlags.id, "id", runFlags.id, "resource name or uuid")
	flag.StringVar(&runFlags.runCmd, "cmd", runFlags.runCmd, "run [ssh] command")
	flag.StringVar(&runFlags.encdr, "kdr", runFlags.encdr, "use coder ")
	flag.StringVar(&runFlags.out, "o", runFlags.out, "output format ")
	flag.StringVar(&runFlags.target, "t", runFlags.out, "target item for command ")

	// Actually parse the flags
	flag.Parse()

	if len(runFlags.id) == 0 { 
		if _, err := os.Stat(runFlags.dataCfgPath); os.IsNotExist(err) {
			msg := fmt.Errorf("'%s' config file not found", DATA_CFG)
			if len(os.Getenv("UPCLAPI_DATACFG")) == 0 {
				err = fmt.Errorf("%s\nUPCLAPI_DATACFG environment value not set\n",msg)
			  return runFlags,err
			}
		}
		// fmt.Printf("dataCfgPath: %+v\n",dataCfgPath)	// Print with Variable Name
		// Validate the path first
		// if err := ValidateDataPath(runFlags.dataCfgPath); err != nil {
		// 	return runFlags,err
		// }
	} else {
		if runFlags.command != os.Getenv("UPCLAPI_COMMAND") && runFlags.id == os.Getenv("UPCLAPI_ID") {
			fmt.Fprintf(os.Stdout, "Warning: Resource ID is set in UPCLAPI_ID enviroment to: %s\n",runFlags.id)
		}
  }
	// Return the configuration path
  return runFlags,nil
}