package main

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

// pveClient is a minimal Proxmox VE API client. With dryRun set, mutating
// calls are printed instead of sent and no network access happens at all.
type pveClient struct {
	base   string // https://host:8006
	token  string // user@realm!tokenid=secret
	node   string
	dryRun bool
	http   *http.Client
	log    func(string)
}

func newPVE(base, token, node string, insecure, dryRun bool, log func(string)) *pveClient {
	tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}}
	return &pveClient{
		base: strings.TrimRight(base, "/"), token: token, node: node, dryRun: dryRun, log: log,
		http: &http.Client{Transport: tr, Timeout: 30 * time.Second},
	}
}

func (c *pveClient) call(method, path string, form url.Values) (json.RawMessage, error) {
	if c.dryRun {
		c.log(fmt.Sprintf("[dry-run] %s /api2/json%s %s", method, path, form.Encode()))
		return json.RawMessage(`null`), nil
	}
	var body io.Reader
	if form != nil {
		body = strings.NewReader(form.Encode())
	}
	req, err := http.NewRequest(method, c.base+"/api2/json"+path, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "PVEAPIToken="+c.token)
	if form != nil {
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	}
	res, err := c.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	b, _ := io.ReadAll(res.Body)
	if res.StatusCode >= 300 {
		return nil, fmt.Errorf("proxmox %s %s: %s: %s", method, path, res.Status, strings.TrimSpace(string(b)))
	}
	var env struct {
		Data json.RawMessage `json:"data"`
	}
	if err := json.Unmarshal(b, &env); err != nil {
		return nil, err
	}
	return env.Data, nil
}

func (c *pveClient) get(path string, out any) error {
	d, err := c.call("GET", path, nil)
	if err != nil {
		return err
	}
	return json.Unmarshal(d, out)
}

type netIface struct {
	Iface     string `json:"iface"`
	Type      string `json:"type"`
	VlanAware any    `json:"bridge_vlan_aware"`
}

func (n netIface) vlanAware() bool {
	switch v := n.VlanAware.(type) {
	case float64:
		return v == 1
	case bool:
		return v
	case string:
		return v == "1"
	}
	return false
}

type vmInfo struct {
	VMID   int    `json:"vmid"`
	Name   string `json:"name"`
	Status string `json:"status"`
	Node   string `json:"node"`
}

func (c *pveClient) networks() ([]netIface, error) {
	var out []netIface
	return out, c.get("/nodes/"+c.node+"/network", &out)
}

func (c *pveClient) vms() ([]vmInfo, error) {
	var out []vmInfo
	return out, c.get("/cluster/resources?type=vm", &out)
}

// createVyOS creates a VyOS router VM: eth0 on the WAN bridge, eth1 on the
// tenant bridge, install ISO attached, then starts it.
func (c *pveClient) createVyOS(o deployOpts) error {
	f := url.Values{}
	f.Set("vmid", fmt.Sprint(o.VMID))
	f.Set("name", o.Name)
	f.Set("ostype", "l26")
	f.Set("cores", fmt.Sprint(o.Cores))
	f.Set("memory", fmt.Sprint(o.MemoryMB))
	f.Set("cpu", "host")
	f.Set("scsihw", "virtio-scsi-pci")
	f.Set("scsi0", fmt.Sprintf("%s:%d", o.Storage, o.DiskGB))
	f.Set("net0", "virtio,bridge="+o.WANBridge)
	f.Set("net1", "virtio,bridge="+o.LANBridge)
	f.Set("ide2", o.ISO+",media=cdrom")
	f.Set("boot", "order=ide2;scsi0")
	f.Set("onboot", "1")
	f.Set("agent", "1")
	if o.CloudInit != "" {
		f.Set("ide0", o.Storage+":cloudinit")
		f.Set("cicustom", "user="+o.CloudInit)
	}
	if _, err := c.call("POST", "/nodes/"+c.node+"/qemu", f); err != nil {
		return err
	}
	_, err := c.call("POST", fmt.Sprintf("/nodes/%s/qemu/%d/status/start", c.node, o.VMID), nil)
	return err
}
