package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"github.com/google/uuid"
	"github.com/jasonlvhit/gocron"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
	"io"
	"io/ioutil"
	"log"
	"log/syslog"
	"net/http"
	"net/smtp"
	"os"
	"strconv"
	"strings"
	"time"
)

var _appversion string = "0.1"
var _appname string = "ZiCloud-API"
var URL string = "https://ipa-cl.zi-tel.com"
var OvirtURL string = "https://ovirt-cl.zi-tel.com"
var RealIP string
var secretKey = []byte("P*%!5+u!$y+cgM+P8bybzgnXpsd2Lv2z") // 32 bytes
type _response struct {
	Message string `json:"message"`
	Origin  string `json:"origin"`
	Code    int    `json:"code"`
}

func uuidgen(resource string) (string, int) {
	id := uuid.New()
	if len(resource) < 3 {
		return "resource name should be at least 3 characters!", 1001
	}

	//fmt.Println("uuidGen for ", id, " at ", resource)
	{
		db, err := sql.Open("mysql", MySQLUSER+":"+MySQLPASS+"@tcp(127.0.0.1:3306)/zicloud")
		if err != nil {
			return "Error in DB Layer", 1001
		}
		defer db.Close()
		insert, err := db.Query("INSERT INTO uuid VALUES ( '" + fmt.Sprintf("%s", id) + "'," +
			"'" + resource + "'," +
			"NOW()" +
			" )")
		if err != nil {
			return "Error in DB Layer", 1001
		}
		defer insert.Close()

	}
	return fmt.Sprintf("%s", id), 1000
}
func audit(txt string) {

	syslogger, err := syslog.New(syslog.LOG_INFO, _appname)
	if err != nil {
		log.Fatalln(err)
	}
	log.SetOutput(syslogger)
	log.Println(txt)
}
func basicAuth(username, password string) string {
	auth := username + "@IPA:" + password
	return base64.StdEncoding.EncodeToString([]byte(auth))
}
func sendMail(str string, subject string, recipient string) {
	auth := smtp.PlainAuth("", "zicloud@zi-tel.com", "5Sd?^AQx@r2OGRvS?i|DO0", "mail.zi-tel.com")
	to := []string{recipient}
	buff := make([]byte, 8)
	rand.Read(buff)
	random_str := base64.StdEncoding.EncodeToString(buff)
	msg := []byte("To:" + recipient + "\r\n" +
		"Date: " + time.Now().Format(time.RFC1123) + "\r\n" +
		"Message-Id: <" + random_str + "@ZiCloud.com>" + "\r\n" +
		"subject: " + subject + "\r\n" +
		"From: ZiCloud <" + "zicloud@zi-tel.com" + ">\r\n" +
		str)
	err := smtp.SendMail("mail.zi-tel.com:25", auth, "zicloud@zi-tel.com", to, msg)
	if err != nil {
		log.Fatal(err)
	}
}
func extractIP(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		RealIP = c.RealIP()
		audit("Recieved request from: " + RealIP)
		return next(c)
	}
}
func main() {
	if len(os.Args) != 3 {
		fmt.Println("Wrong Usage:\n\t ./CMD IP Port")
		audit("Application in the wrong way")
		os.Exit(1)
	}
	s := gocron.NewScheduler()
	s.Every(5).Seconds().Do(task)

	go s.Start()
	echoHandler := echo.New()
	echoHandler.Use(extractIP)
	echoHandler.Use(middleware.CORSWithConfig(middleware.CORSConfig{
		AllowOrigins: []string{"*", "*"},
		AllowMethods: []string{http.MethodGet, http.MethodPost},
	}))
	audit("Application " + _appname + " (" + _appversion + ") Started by " + os.Getenv("USER"))
	echoHandler.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello, World!")
	})

	h := &handler{}
	echoHandler.POST("/login", h.login)
	echoHandler.POST("/uuidgen", h.uuidgen)

	//echoHandler.GET("/admin", h.uuidgen, isLoggedIn, isAdmin)
	echoHandler.POST("/addUser", h.addUser, isLoggedIn, isAdmin)
	echoHandler.POST("/disableUser", h.disableUser, isLoggedIn, isAdmin)
	echoHandler.POST("/resetUser", h.resetUser)
	echoHandler.GET("/verifyUser", h.verifyUser)
	echoHandler.POST("/dnsrecordadd", h.dnsrecordadd, isLoggedIn, isAdmin)
	echoHandler.POST("/forgetpassword", h.forgetpassword, isLoggedIn, isAdmin)
	echoHandler.POST("/token", h.token, isLoggedIn)
	echoHandler.POST("/ListServices", h.ListServices, isLoggedIn)
	echoHandler.POST("/PriceCalc", h.PriceCalc, isLoggedIn)

	iaas := &ovirt{}
	echoHandler.GET("/ovirtListVMs", iaas.listVM, isLoggedIn)
	echoHandler.POST("/ovirtAddVM", iaas.addvm, isLoggedIn)
	echoHandler.POST("/ovirtStartVM", iaas.StartVM, isLoggedIn)
	echoHandler.POST("/ovirtStopVM", iaas.StopVM, isLoggedIn)
	echoHandler.POST("/ovirtRebootVM", iaas.RebootVM, isLoggedIn)
	echoHandler.POST("/ovirtPowerOffVM", iaas.PowerOffVM, isLoggedIn)
	echoHandler.POST("/ovirtResetVM", iaas.ResetVM, isLoggedIn)
	echoHandler.POST("/ovirtAddNIC", iaas.AddNIC, isLoggedIn)
	echoHandler.POST("/ovirtAddDisk", iaas.AddDisk, isLoggedIn)
	echoHandler.POST("/ovirtEditVM", iaas.EditVM, isLoggedIn)
	echoHandler.POST("/ovirtResetPassword", iaas.ResetPassword, isLoggedIn)
	echoHandler.POST("/vmDetails", iaas.vmDetails, isLoggedIn)
	echoHandler.POST("/ovirtPayment", iaas.ovirtPayment, isLoggedIn )

	echoHandler.Logger.Fatal(echoHandler.Start(os.Args[1] + ":" + os.Args[2]))
}
func encrypt(key []byte, text string) string {
	// key := []byte(keyText)
	//fmt.Println("Encrypt by: ", key)
	plaintext := []byte(text)
	block, err := aes.NewCipher(key)
	if err != nil {
		//panic(err)
		fmt.Sprintf("encrypt got error")
		return ""
	}
	// The IV needs to be unique, but not secure. Therefore it's common to
	// include it at the beginning of the ciphertext.
	ciphertext := make([]byte, aes.BlockSize+len(plaintext))
	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		fmt.Sprintf("encrypt got error")
		return ""
	}

	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)

	// convert to base64
	return base64.URLEncoding.EncodeToString(ciphertext)
}
func decrypt(key []byte, cryptoText string) string {
	ciphertext, _ := base64.URLEncoding.DecodeString(cryptoText)
	//fmt.Println("Decrypt by: ", key)
	block, err := aes.NewCipher(key)
	if err != nil {
		fmt.Sprintf("decrypt got error")
		return ""
		//panic(err)
	}

	// The IV needs to be unique, but not secure. Therefore it's common to
	// include it at the beginning of the ciphertext.
	if len(ciphertext) < aes.BlockSize {
		fmt.Sprintf("ciphertext too short")
		return ""
		//panic("ciphertext too short")
	}
	iv := ciphertext[:aes.BlockSize]
	ciphertext = ciphertext[aes.BlockSize:]

	stream := cipher.NewCFBDecrypter(block, iv)

	// XORKeyStream can work in-place if the two arguments are the same.
	stream.XORKeyStream(ciphertext, ciphertext)
	return fmt.Sprintf("%s", ciphertext)
}
func task() {
	type Task struct {
		UUID           string `json:"uuid"`
		TaskAPICall    string `json:"taskapicall"`
		Ruuid          string `json:"ruuid"`
		CronExpression string `json:"cronexpression"`
		Type           string `json:"type"` //0: createVM , 1: VMInitialization
	}
	//fmt.Println("Task is being performed.")
	db, err := sql.Open("mysql", MySQLUSER+":"+MySQLPASS+"@tcp(127.0.0.1:3306)/zicloud")
	results, err := db.Query("SELECT uuid as UUID ,task_apiCall as TaskAPICall , cron_expression as CronExpression , related_uuid as Ruuid, type FROM scheduler where active=1")
	for results.Next() {
		var res Task
		err = results.Scan(&res.UUID, &res.TaskAPICall, &res.CronExpression, &res.Ruuid, &res.Type)
		if err != nil {
			panic(err.Error()) // proper error handling instead of panic in your app
		}
		taskType := 0
		taskType, _ = strconv.Atoi(res.Type)
		if taskType == 0 {
			createVM(res.Ruuid, res.TaskAPICall, res.UUID)
		} else if taskType == 1 {
			VMInitialization(res.Ruuid, res.TaskAPICall, res.UUID)
		} else if taskType == 2 {

		} else {

		}
		//fmt.Println("query ruuid: ", res.Ruuid)
	}
	if err != nil {
		panic(err.Error()) // proper error handling instead of panic in your app
	}
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()
	//iaas := &ovirt{}
	//fmt.Println("down",iaas.vmStatus("4f0e58ff-fb75-4a3d-9262-bd3714db52a6"))
	//fmt.Println("up",iaas.vmStatus("6b84a5dc-1c83-43e0-9ea6-dd86413bccc0"))
}
func addTask(uuid string, taskAPICall string, cronExpression string, origin string, description string, related_uuid string, _type string, active string) string {
	{
		/*
			types:
				0: VM Create
				1: VM Initialize
				2: VM Start
				3: VM Stop
		*/
		//fmt.Println("Add Task:", uuid)
		db, err := sql.Open("mysql", MySQLUSER+":"+MySQLPASS+"@tcp(127.0.0.1:3306)/zicloud")
		if err != nil {
		}
		defer db.Close()
		//INSERT INTO `zicloud`.`scheduler`(`uuid`, `taskAPICall`, active`, `cron_expression`, `origin`, NOW()) VALUES ('xx', 'xx', 'xx', 0, 'xx', 'xx');
		insert, err := db.Query("INSERT INTO scheduler VALUES (" +
			" '" + uuid + "'," +
			"'" + taskAPICall + "'," +
			"'" + active + "'," +
			"'" + cronExpression + "'," +
			"'" + origin + "'," +
			"'" + description + "'," +
			"'" + related_uuid + "'," +
			_type + "," +
			"NOW()" +
			" )")
		if err != nil {
		}
		defer insert.Close()

	}
	return "OK"
}
func toggleTask(uuid string, active int) {
	db, err := sql.Open("mysql", MySQLUSER+":"+MySQLPASS+"@tcp(127.0.0.1:3306)/zicloud")
	if err != nil {
	}
	update, err := db.Query("update scheduler set active='" + fmt.Sprintf("%s", active) + "' where uuid='" + uuid + "'")
	defer db.Close()
	if err != nil {
	}
	defer update.Close()

}
func runAPICall(apiCall addVMTask) []byte {
	url := apiCall.URL
	method := apiCall.Method
	__json := apiCall.JSON
	//fmt.Println("runAPICALL: ", __json)
	client := &http.Client{}
	payload := strings.NewReader(__json)
	req, err := http.NewRequest(method, url, payload)
	_headers := apiCall.Headers
	for _, v := range _headers {
		req.Header.Add(v.Name, v.Value)
	}
	if err != nil {
		fmt.Println(err)
	}
	res, err := client.Do(req)
	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)
	return body
}
func VMPowerMng(relatedUuid string, apiCall string, uuid string) {
	startVM := addVMTask{}
	_err := json.Unmarshal([]byte(apiCall), &startVM)
	if _err != nil {
		fmt.Println("Error in VMPowerMng: ", _err.Error())
	}
	_sha256 := sha256.Sum256([]byte(relatedUuid))
	var hashChannel_ = make(chan []byte, 1)
	hashChannel_ <- _sha256[:]
	//fmt.Println(" startVM.JSON: ", startVM.JSON)
	startVM.JSON = decrypt(<-hashChannel_, startVM.JSON)
	//fmt.Println("decrypted json: ",startVM.JSON)
	runAPICall(startVM)
	toggleTask(uuid, 0)

}