package proxmox

import (
	"context"
	"errors"
	"net/http"
	"testing"
)

func TestAPIError_403ExtractsPrivilege(t *testing.T) {
	d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) {
		return jsonResp(403, `{"message":"Permission check failed (/nodes/demo-felhom, Sys.Audit)\n"}`), nil
	}}
	_, err := newTestClient(d).NodeStatus(context.Background())
	var ae *APIError
	if !errors.As(err, &ae) {
		t.Fatalf("want *APIError, got %T: %v", err, err)
	}
	if !ae.IsForbidden() {
		t.Errorf("IsForbidden = false")
	}
	if ae.Privilege != "Sys.Audit" {
		t.Errorf("privilege = %q, want Sys.Audit", ae.Privilege)
	}
	if ae.DeniedPath != "/nodes/demo-felhom" {
		t.Errorf("denied path = %q", ae.DeniedPath)
	}
}

func TestDecode_ListLXC(t *testing.T) {
	// Exact shape captured from the live host.
	body := `{"data":[{"cpu":0,"cpus":2,"disk":0,"maxdisk":10737418240,"maxmem":2147483648,"mem":0,"name":"spike-lxc","status":"stopped","type":"lxc","uptime":0,"vmid":9001}]}`
	d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) { return jsonResp(200, body), nil }}
	gs, err := newTestClient(d).ListLXC(context.Background())
	if err != nil {
		t.Fatalf("ListLXC: %v", err)
	}
	if len(gs) != 1 {
		t.Fatalf("len = %d", len(gs))
	}
	g := gs[0]
	if g.VMID != 9001 || g.Name != "spike-lxc" || g.Status != "stopped" || g.CPUs != 2 {
		t.Errorf("decoded guest wrong: %+v", g)
	}
}

func TestDecode_NodeStatus(t *testing.T) {
	body := `{"data":{"cpu":0.0057,"uptime":73078,"loadavg":["0.11","0.09","0.05"],"pveversion":"pve-manager/9.2.2","memory":{"total":16537989120,"used":2043027456,"free":13587857408,"available":14494961664},"rootfs":{"total":100861726720,"used":4943888384,"free":95917838336,"avail":90747101184},"cpuinfo":{"cores":4,"cpus":4,"sockets":1,"model":"Intel(R) N100"}}}`
	d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) { return jsonResp(200, body), nil }}
	s, err := newTestClient(d).NodeStatus(context.Background())
	if err != nil {
		t.Fatalf("NodeStatus: %v", err)
	}
	if len(s.LoadAvg) != 3 || s.LoadAvg[0] != "0.11" {
		t.Errorf("loadavg = %v", s.LoadAvg)
	}
	if s.Memory.Total != 16537989120 || s.CPUInfo.Cores != 4 {
		t.Errorf("decoded node status wrong: %+v", s)
	}
}

func TestDecode_GuestConfig_FeaturesAndExtra(t *testing.T) {
	// keyctl must survive as a string; mpN/netN land in Extra.
	body := `{"data":{"arch":"amd64","cores":2,"features":"nesting=1,keyctl=1","hostname":"spike-lxc","memory":2048,"net0":"name=eth0,bridge=vmbr0,hwaddr=BC:24:11:D1:6D:CB,ip=dhcp,type=veth","rootfs":"local-lvm:vm-9001-disk-0,size=10G","unprivileged":1,"mp0":"local-lvm:1,mp=/mnt/bulk,backup=0"}}`
	d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) { return jsonResp(200, body), nil }}
	cfg, err := newTestClient(d).GuestConfig(context.Background(), 9001)
	if err != nil {
		t.Fatalf("GuestConfig: %v", err)
	}
	if cfg.Features != "nesting=1,keyctl=1" {
		t.Errorf("features = %q", cfg.Features)
	}
	if cfg.Unprivileged != 1 {
		t.Errorf("unprivileged = %d", cfg.Unprivileged)
	}
	if mp := cfg.MountPoints(); mp["mp0"] != "local-lvm:1,mp=/mnt/bulk,backup=0" {
		t.Errorf("mountpoints = %v", mp)
	}
	if nets := cfg.Nets(); nets["net0"] == "" {
		t.Errorf("nets = %v", nets)
	}
	// "memory" must NOT be misread as an mp/net prefix match.
	if mp := cfg.MountPoints(); len(mp) != 1 {
		t.Errorf("expected exactly 1 mountpoint, got %v", mp)
	}
}

func TestDataString_ReturnsUPID(t *testing.T) {
	d := &mockDoer{fn: func(r *http.Request) (*http.Response, error) {
		if r.Method != http.MethodPost {
			t.Errorf("method = %s", r.Method)
		}
		return jsonResp(200, `{"data":"`+testUPID+`"}`), nil
	}}
	upid, err := newTestClient(d).Snapshot(context.Background(), 9001, "s1", "")
	if err != nil {
		t.Fatalf("Snapshot: %v", err)
	}
	if upid != testUPID {
		t.Errorf("upid = %q", upid)
	}
}
