package proxmox

import "encoding/json"

// Types mirror the exact JSON shapes captured from the live demo host
// (demo-felhom, PVE 9.2.2, 2026-06-08) via `pvesh get ... --output-format json`.
// Decoding ignores unknown fields, so we depend only on the fields we use.

// Version is GET /version.
type Version struct {
	Release string `json:"release"` // "9.2"
	RepoID  string `json:"repoid"`
	Version string `json:"version"` // "9.2.2"
}

// Node is one entry of GET /nodes.
type Node struct {
	Node           string  `json:"node"`   // node name, e.g. "demo-felhom"
	Status         string  `json:"status"` // "online"
	CPU            float64 `json:"cpu"`    // load fraction 0..1
	MaxCPU         int     `json:"maxcpu"`
	Mem            int64   `json:"mem"`
	MaxMem         int64   `json:"maxmem"`
	Disk           int64   `json:"disk"`
	MaxDisk        int64   `json:"maxdisk"`
	Uptime         int64   `json:"uptime"`
	SSLFingerprint string  `json:"ssl_fingerprint"`
}

// NodeStatus is GET /nodes/{node}/status (host metrics; needs Sys.Audit).
type NodeStatus struct {
	CPU        float64  `json:"cpu"` // load fraction 0..1
	Uptime     int64    `json:"uptime"`
	LoadAvg    []string `json:"loadavg"` // 1/5/15-min, as strings in the API
	PVEVersion string   `json:"pveversion"`
	KVersion   string   `json:"kversion"`
	Memory     struct {
		Total     int64 `json:"total"`
		Used      int64 `json:"used"`
		Free      int64 `json:"free"`
		Available int64 `json:"available"`
	} `json:"memory"`
	RootFS struct {
		Total int64 `json:"total"`
		Used  int64 `json:"used"`
		Free  int64 `json:"free"`
		Avail int64 `json:"avail"`
	} `json:"rootfs"`
	Swap struct {
		Total int64 `json:"total"`
		Used  int64 `json:"used"`
		Free  int64 `json:"free"`
	} `json:"swap"`
	CPUInfo struct {
		Cores   int    `json:"cores"`
		CPUs    int    `json:"cpus"`
		Sockets int    `json:"sockets"`
		Model   string `json:"model"`
	} `json:"cpuinfo"`
}

// Guest is one entry of GET /nodes/{node}/lxc and the body of
// GET /nodes/{node}/lxc/{vmid}/status/current. The status/current response has no
// vmid field (it is in the path), so callers set VMID from the request argument.
type Guest struct {
	VMID    int     `json:"vmid"`
	Name    string  `json:"name"`
	Status  string  `json:"status"` // "running" | "stopped"
	Type    string  `json:"type"`   // "lxc"
	CPUs    int     `json:"cpus"`
	CPU     float64 `json:"cpu"`
	Mem     int64   `json:"mem"`
	MaxMem  int64   `json:"maxmem"`
	Disk    int64   `json:"disk"`
	MaxDisk int64   `json:"maxdisk"`
	Uptime  int64   `json:"uptime"`
}

// GuestConfig is GET /nodes/{node}/lxc/{vmid}/config. The config surface is
// dynamic (net0..netN, mp0..mpN, unusedN), so known fields are typed and the full
// raw map is preserved in Extra for the dynamic ones.
type GuestConfig struct {
	Hostname     string `json:"hostname"`
	Arch         string `json:"arch"`
	Cores        int    `json:"cores"`
	Memory       int64  `json:"memory"`
	Swap         int64  `json:"swap"`
	OSType       string `json:"ostype"`
	RootFS       string `json:"rootfs"`
	Features     string `json:"features"`     // e.g. "nesting=1,keyctl=1"
	Unprivileged int    `json:"unprivileged"` // 1 if unprivileged
	Digest       string `json:"digest"`

	// Extra holds every field as raw JSON, including the dynamic netN/mpN/unusedN
	// keys not promoted above.
	Extra map[string]json.RawMessage `json:"-"`
}

// UnmarshalJSON fills both the typed known fields and the raw Extra map.
func (g *GuestConfig) UnmarshalJSON(b []byte) error {
	type alias GuestConfig // avoid recursion
	var a alias
	if err := json.Unmarshal(b, &a); err != nil {
		return err
	}
	*g = GuestConfig(a)
	return json.Unmarshal(b, &g.Extra)
}

// MountPoints returns the mpN entries (e.g. "mp0" -> "local-lvm:1,mp=/mnt/mp1,backup=0")
// pulled from Extra. Relevant for later slices' bulk-volume placement.
func (g *GuestConfig) MountPoints() map[string]string {
	return g.prefixed("mp")
}

// Nets returns the netN entries from Extra.
func (g *GuestConfig) Nets() map[string]string {
	return g.prefixed("net")
}

func (g *GuestConfig) prefixed(prefix string) map[string]string {
	out := map[string]string{}
	for k, raw := range g.Extra {
		if len(k) <= len(prefix) || k[:len(prefix)] != prefix {
			continue
		}
		// require the suffix to be a digit (mp0, net0 — not "memory")
		if c := k[len(prefix)]; c < '0' || c > '9' {
			continue
		}
		var s string
		if json.Unmarshal(raw, &s) == nil {
			out[k] = s
		}
	}
	return out
}

// Storage is one entry of GET /storage (cluster) and GET /nodes/{node}/storage
// (the latter adds usage fields). Unused fields stay zero.
//
// The lower block (Server/Export/Share/Datastore/Fingerprint/VGName/ThinPool) are the
// type-specific config fields the cluster /storage definition carries; they are the
// source for slice-5's deterministic durable_id derivation (server:export for NFS/CIFS,
// repo+fingerprint for PBS, vg/pool for lvmthin). Additive parse-only fields — decoding
// ignores unknown keys, so a storage type that lacks one simply leaves it zero.
type Storage struct {
	Storage      string  `json:"storage"`
	Type         string  `json:"type"`    // "dir" | "lvmthin" | "nfs" | "cifs" | "pbs"
	Content      string  `json:"content"` // comma list, e.g. "vztmpl,backup,iso,import"
	Path         string  `json:"path,omitempty"`
	Total        int64   `json:"total,omitempty"`
	Used         int64   `json:"used,omitempty"`
	Avail        int64   `json:"avail,omitempty"`
	Active       int     `json:"active,omitempty"`
	Enabled      int     `json:"enabled,omitempty"`
	Shared       int     `json:"shared,omitempty"`
	UsedFraction float64 `json:"used_fraction,omitempty"`

	// Type-specific config (durable_id sources).
	Server      string `json:"server,omitempty"`      // nfs/cifs/pbs server host
	Export      string `json:"export,omitempty"`      // nfs export path
	Share       string `json:"share,omitempty"`       // cifs share name
	Datastore   string `json:"datastore,omitempty"`   // pbs datastore name
	Fingerprint string `json:"fingerprint,omitempty"` // pbs server cert fingerprint
	Username    string `json:"username,omitempty"`    // pbs auth id, e.g. "felhom@pbs!n100"
	VGName      string `json:"vgname,omitempty"`      // lvm/lvmthin volume group
	ThinPool    string `json:"thinpool,omitempty"`    // lvmthin pool LV name
}

// StorageContent is one entry of GET /nodes/{node}/storage/{store}/content
// (e.g. vzdump archives, CT templates, guest volumes).
type StorageContent struct {
	VolID   string `json:"volid"` // e.g. "local:backup/vzdump-lxc-9001-...tar.zst"
	Content string `json:"content"`
	Format  string `json:"format"`
	Size    int64  `json:"size"`
	CTime   int64  `json:"ctime"`
	VMID    int    `json:"vmid,omitempty"`
}
