package cli

import (
	"bytes"
	"encoding/json"
	"strings"
	"testing"

	"git.cloudinit.dev/coreci/orca/internal/certpaths"
	"git.cloudinit.dev/coreci/orca/internal/store"
)

func TestNodeCapacitySetMissingArgs(t *testing.T) {
	_, cleanup := initTestEnv(t)
	defer cleanup()
	resetRootFlags(t)
	var buf bytes.Buffer
	rootCmd.SetOut(&buf)
	rootCmd.SetErr(&buf)
	rootCmd.SetArgs([]string{"node", "capacity", "set", "--cpu", "1000"})
	if err := rootCmd.Execute(); err == nil {
		t.Fatal("expected error for capacity set missing memory/disk, got nil")
	}
}

func TestNodeCapacitySet(t *testing.T) {
	_, cleanup := initTestEnv(t)
	defer cleanup()
	resetRootFlags(t)
	var buf bytes.Buffer
	rootCmd.SetOut(&buf)
	rootCmd.SetErr(&buf)
	rootCmd.SetArgs([]string{"node", "capacity", "set", "--cpu", "2000", "--memory", "4096", "--disk", "51200"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("capacity set: %v", err)
	}
	if !strings.Contains(buf.String(), "Capacity set") {
		t.Errorf("capacity set output unexpected: %s", buf.String())
	}
}

func TestNodeCapacitySetJSON(t *testing.T) {
	_, cleanup := initTestEnv(t)
	defer cleanup()
	resetRootFlags(t)
	var buf bytes.Buffer
	rootCmd.SetOut(&buf)
	rootCmd.SetErr(&buf)
	rootCmd.SetArgs([]string{"node", "capacity", "set", "--cpu", "3000", "--memory", "8192", "--disk", "102400", "--json"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("capacity set --json: %v", err)
	}
	var c map[string]any
	if err := json.Unmarshal(bytes.TrimSpace(buf.Bytes()), &c); err != nil {
		t.Fatalf("unmarshal capacity set json: %v\n%s", err, buf.String())
	}
	if c["NodeID"] != "self" {
		t.Errorf("capacity set --json NodeID = %v, want self", c["NodeID"])
	}
}

func TestNodeCapacityShowNotFound(t *testing.T) {
	_, cleanup := initTestEnv(t)
	defer cleanup()
	resetRootFlags(t)
	var buf bytes.Buffer
	rootCmd.SetOut(&buf)
	rootCmd.SetErr(&buf)
	rootCmd.SetArgs([]string{"node", "capacity", "show", "missing-node"})
	if err := rootCmd.Execute(); err == nil {
		t.Fatal("expected error for capacity show missing node, got nil")
	}
}

func TestNodeCapacityShowAfterSet(t *testing.T) {
	_, cleanup := initTestEnv(t)
	defer cleanup()
	seedCapacity(t, "show-node", 4000, 4096, 51200)
	resetRootFlags(t)
	var buf bytes.Buffer
	rootCmd.SetOut(&buf)
	rootCmd.SetErr(&buf)
	rootCmd.SetArgs([]string{"node", "capacity", "show", "show-node"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("capacity show: %v", err)
	}
	out := buf.String()
	if !strings.Contains(out, "show-node") {
		t.Errorf("capacity show missing node id: %s", out)
	}
	if !strings.Contains(out, "4000") {
		t.Errorf("capacity show missing cpu: %s", out)
	}
}

func TestNodeCapacityShowJSON(t *testing.T) {
	_, cleanup := initTestEnv(t)
	defer cleanup()
	seedCapacity(t, "jsonshow-node", 4000, 4096, 51200)
	resetRootFlags(t)
	var buf bytes.Buffer
	rootCmd.SetOut(&buf)
	rootCmd.SetErr(&buf)
	rootCmd.SetArgs([]string{"node", "capacity", "show", "jsonshow-node", "--json"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("capacity show --json: %v", err)
	}
	var c map[string]any
	if err := json.Unmarshal(bytes.TrimSpace(buf.Bytes()), &c); err != nil {
		t.Fatalf("unmarshal capacity show json: %v\n%s", err, buf.String())
	}
	if c["NodeID"] != "jsonshow-node" {
		t.Errorf("capacity show --json NodeID = %v, want jsonshow-node", c["NodeID"])
	}
}

func TestNodeCapacityListEmpty(t *testing.T) {
	_, cleanup := initTestEnv(t)
	defer cleanup()
	resetRootFlags(t)
	var buf bytes.Buffer
	rootCmd.SetOut(&buf)
	rootCmd.SetErr(&buf)
	rootCmd.SetArgs([]string{"node", "capacity", "list"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("capacity list: %v", err)
	}
	if !strings.Contains(buf.String(), "No capacity") {
		t.Errorf("capacity list empty output unexpected: %s", buf.String())
	}
}

func TestNodeCapacityListAfterSet(t *testing.T) {
	_, cleanup := initTestEnv(t)
	defer cleanup()
	seedCapacity(t, "list-node", 5000, 4096, 51200)
	resetRootFlags(t)
	var buf bytes.Buffer
	rootCmd.SetOut(&buf)
	rootCmd.SetErr(&buf)
	rootCmd.SetArgs([]string{"node", "capacity", "list"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("capacity list: %v", err)
	}
	if !strings.Contains(buf.String(), "list-node") {
		t.Errorf("capacity list missing node: %s", buf.String())
	}
}

func TestNodeCapacityListJSON(t *testing.T) {
	_, cleanup := initTestEnv(t)
	defer cleanup()
	seedCapacity(t, "jsonlist-node", 5000, 4096, 51200)
	resetRootFlags(t)
	var buf bytes.Buffer
	rootCmd.SetOut(&buf)
	rootCmd.SetErr(&buf)
	rootCmd.SetArgs([]string{"node", "capacity", "list", "--json"})
	if err := rootCmd.Execute(); err != nil {
		t.Fatalf("capacity list --json: %v", err)
	}
	var rows []map[string]any
	if err := json.Unmarshal(bytes.TrimSpace(buf.Bytes()), &rows); err != nil {
		t.Fatalf("unmarshal capacity list json: %v\n%s", err, buf.String())
	}
	found := false
	for _, r := range rows {
		if r["NodeID"] == "jsonlist-node" {
			found = true
		}
	}
	if !found {
		t.Errorf("capacity list --json missing jsonlist-node: %s", buf.String())
	}
}

func seedCapacity(t *testing.T, nodeID string, cpu, mem, disk int64) {
	t.Helper()
	db, err := store.Open(certpaths.DBPath())
	if err != nil {
		t.Fatalf("open db: %v", err)
	}
	defer db.Close()
	repo := store.NewCapacityRepo(db)
	c := &store.NodeCapacity{
		NodeID:        nodeID,
		CPUMillicores: cpu,
		MemoryMiB:     mem,
		DiskMiB:       disk,
	}
	if err := repo.Upsert(t.Context(), c); err != nil {
		t.Fatalf("upsert capacity: %v", err)
	}
}
