package cmd

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync/atomic"
	"testing"

	"github.com/openeuler/Conch/internal/cli/client"
)

func TestPrintSandboxHelpListsSandboxCommands(t *testing.T) {
	var buf bytes.Buffer
	printSandboxHelp(&buf)

	want := `Usage:
  conch sandbox <command> [options]

Commands:
  create      Create a sandbox from a Template Name or the daemon default.
  checkpoint  Checkpoint a sandbox into a resumable template.
  suspend     Suspend a running sandbox.
  resume      Resume a suspended sandbox.
  delete      Delete a sandbox.
  ls          List sandboxes.

Run 'conch sandbox <command> --help' for command-specific usage.
`
	if got := buf.String(); got != want {
		t.Fatalf("sandbox help output mismatch:\n%s", got)
	}
}

func TestRunSandboxCreateOmitsResourceOverridesByDefault(t *testing.T) {
	got := captureSandboxCreateRequest(t)
	for _, key := range []string{"template_name", "template_id", "vmm_name", "vcpu_num", "vcpu_max", "ram_mb"} {
		if value, ok := got[key]; ok {
			t.Fatalf("create request unexpectedly overrides daemon defaults with %s=%v; request = %#v", key, value, got)
		}
	}
}

func TestRunSandboxCreateKeepsExplicitTemplateName(t *testing.T) {
	got := captureSandboxCreateRequest(t, "--template-name", "registry.example/conch/test:latest")
	if got["template_name"] != "registry.example/conch/test:latest" {
		t.Fatalf("template_name = %v; request = %#v", got["template_name"], got)
	}
}

func TestRunSandboxCreateKeepsExplicitTemplateID(t *testing.T) {
	got := captureSandboxCreateRequest(t, "--template-id", "sha256:explicit")
	if got["template_id"] != "sha256:explicit" {
		t.Fatalf("template_id = %v; request = %#v", got["template_id"], got)
	}
}

func TestRunSandboxCreateRejectsNameAndIDTogether(t *testing.T) {
	var requestReceived atomic.Bool
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		requestReceived.Store(true)
		_ = json.NewEncoder(w).Encode(map[string]string{"sandboxID": "sandbox-123"})
	}))
	t.Cleanup(server.Close)
	t.Setenv("CONCH_API_URL", server.URL)

	err := RunSandbox(context.Background(), []string{
		"create", "--template-name", "template:latest", "--template-id", "sha256:explicit", "--sandbox-id", "sandbox-123",
	})
	if err == nil || !strings.Contains(err.Error(), "mutually exclusive") {
		t.Fatalf("RunSandbox() error = %v", err)
	}
	if requestReceived.Load() {
		t.Fatal("mutually exclusive request reached conchd")
	}
}

func TestRunSandboxCreateKeepsExplicitRAM(t *testing.T) {
	got := captureSandboxCreateRequest(t, "--ram-mb", "4096")
	if got["ram_mb"] != float64(4096) {
		t.Fatalf("ram_mb = %v, want 4096; request = %#v", got["ram_mb"], got)
	}
	for _, key := range []string{"vmm_name", "vcpu_num", "vcpu_max"} {
		if value, ok := got[key]; ok {
			t.Fatalf("explicit RAM request unexpectedly overrides daemon default %s=%v; request = %#v", key, value, got)
		}
	}
}

func TestRunSandboxCreateRejectsNegativeRAM(t *testing.T) {
	var requestReceived atomic.Bool
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		requestReceived.Store(true)
		_ = json.NewEncoder(w).Encode(map[string]string{"sandboxID": "sandbox-123"})
	}))
	t.Cleanup(server.Close)
	t.Setenv("CONCH_API_URL", server.URL)

	err := RunSandbox(context.Background(), []string{
		"create", "--template-name", "registry.example/conch/test:latest", "--sandbox-id", "sandbox-123", "--ram-mb", "-1",
	})
	if err == nil || !strings.Contains(err.Error(), "--ram-mb must not be negative") {
		t.Fatalf("RunSandbox() error = %v, want negative RAM validation", err)
	}
	if requestReceived.Load() {
		t.Fatal("negative RAM request reached conchd")
	}
}

func captureSandboxCreateRequest(t *testing.T, resourceArgs ...string) map[string]any {
	t.Helper()
	var got map[string]any
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/api/v1/sandboxes" {
			t.Fatalf("path = %q, want /api/v1/sandboxes", r.URL.Path)
		}
		if err := json.NewDecoder(r.Body).Decode(&got); err != nil {
			t.Fatalf("decode create request: %v", err)
		}
		_ = json.NewEncoder(w).Encode(map[string]string{"sandboxID": "sandbox-123"})
	}))
	t.Cleanup(server.Close)
	t.Setenv("CONCH_API_URL", server.URL)

	args := []string{"create", "--sandbox-id", "sandbox-123"}
	args = append(args, resourceArgs...)
	if err := RunSandbox(context.Background(), args); err != nil {
		t.Fatalf("RunSandbox() error = %v", err)
	}
	return got
}

func TestPrintSandboxList(t *testing.T) {
	var out bytes.Buffer
	records := []client.SandboxRecord{
		{SandboxID: "sandbox-b", TemplateName: "template-b", TemplateID: "sha256:b", CPUCount: 2, MemoryMB: 512, StartedAt: "2026-01-02T03:04:05Z"},
		{SandboxID: "sandbox-a", TemplateName: "template-a", TemplateID: "sha256:a", CPUCount: 1, MemoryMB: 256, StartedAt: "2026-01-01T03:04:05Z"},
	}
	if err := printSandboxList(&out, records); err != nil {
		t.Fatalf("printSandboxList() error = %v", err)
	}
	want := "ID         TEMPLATE_NAME  TEMPLATE_ID  CPU  MEMORY_MB  STARTED_AT\n" +
		"sandbox-a  template-a     sha256:a     1    256        2026-01-01T03:04:05Z\n" +
		"sandbox-b  template-b     sha256:b     2    512        2026-01-02T03:04:05Z\n"
	if got := out.String(); got != want {
		t.Fatalf("sandbox list output = %q, want %q", got, want)
	}
}