package main

import (
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"
)

func env(k, def string) string {
	if v := os.Getenv(k); v != "" {
		return v
	}
	return def
}

var client = &http.Client{
	Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, // lab self-signed cert only
}

func main() {
	base := strings.TrimRight(env("VSPHERE_BASE", "https://localhost"), "/")
	user := env("VSPHERE_USER", "administrator@vsphere.local")
	password := env("VSPHERE_PASSWORD", "VMware1!")
	vmName := env("VSPHERE_VM_NAME", "go-lab-01")

	auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(user+":"+password))
	var sessionID string
	call(base+"/api/session", "POST", map[string]string{"Authorization": auth}, nil, &sessionID)
	fmt.Println("session:", sessionID)

	headers := map[string]string{"vmware-api-session-id": sessionID}

	var vms []map[string]any
	call(base+"/api/vcenter/vm", "GET", headers, nil, &vms)
	fmt.Printf("vms before: %d\n", len(vms))

	body, _ := json.Marshal(map[string]any{
		"name":     vmName,
		"guest_OS": "OTHER_GUEST_64",
		"placement": map[string]string{
			"folder":         "group-v23",
			"host":           "host-11",
			"datastore":      "datastore-31",
			"resource_pool":  "resgroup-22",
		},
		"cpu":    map[string]int{"count": 1},
		"memory": map[string]int{"size_MiB": 512},
	})
	var moid string
	call(base+"/api/vcenter/vm", "POST", headers, body, &moid)
	fmt.Println("created:", moid)

	var power map[string]string
	call(base+"/api/vcenter/vm/"+moid+"/power?action=start", "POST", headers, nil, &power)
	waitTask(base, headers, power["task"])

	var detail map[string]any
	call(base+"/api/vcenter/vm/"+moid, "GET", headers, nil, &detail)
	fmt.Println("power_state:", detail["power_state"])

	call(base+"/api/vcenter/vm/"+moid+"/power?action=stop", "POST", headers, nil, &power)
	waitTask(base, headers, power["task"])

	call(base+"/api/vcenter/vm/"+moid, "DELETE", headers, nil, nil)
	call(base+"/api/session", "DELETE", headers, nil, nil)
	fmt.Println("ok")
}

func waitTask(base string, headers map[string]string, task string) {
	if task == "" {
		return
	}
	deadline := time.Now().Add(2 * time.Minute)
	for time.Now().Before(deadline) {
		var status map[string]any
		call(base+"/api/cis/tasks/"+task, "GET", headers, nil, &status)
		if s, _ := status["status"].(string); s == "SUCCEEDED" || s == "FAILED" {
			return
		}
		time.Sleep(300 * time.Millisecond)
	}
	panic("timeout waiting for task " + task)
}

func call(url, method string, headers map[string]string, body []byte, out any) {
	var reqBody io.Reader
	if body != nil {
		reqBody = strings.NewReader(string(body))
	}
	req, err := http.NewRequest(method, url, reqBody)
	if err != nil {
		panic(err)
	}
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	raw, _ := io.ReadAll(resp.Body)
	if resp.StatusCode >= 300 {
		panic(fmt.Sprintf("%s %s: %d %s", method, url, resp.StatusCode, raw))
	}
	if out == nil || len(raw) == 0 {
		return
	}
	if err := json.Unmarshal(raw, out); err != nil {
		panic(err)
	}
}
