package main

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"testing"
)

func TestFormatSize(t *testing.T) {
	tests := []struct {
		bytes int64
		want  string
	}{
		{0, "0 B"},
		{512, "512 B"},
		{1024, "1.0 KB"},
		{1536, "1.5 KB"},
		{1048576, "1.0 MB"},
		{1073741824, "1.0 GB"},
		{1099511627776, "1.0 TB"},
		{2684354560, "2.5 GB"},
	}

	for _, tt := range tests {
		got := formatSize(tt.bytes)
		if got != tt.want {
			t.Errorf("formatSize(%d) = %q, want %q", tt.bytes, got, tt.want)
		}
	}
}

func TestPrintJSON(t *testing.T) {
	// Capture stdout.
	old := os.Stdout
	r, w, err := os.Pipe()
	if err != nil {
		t.Fatal(err)
	}
	os.Stdout = w

	data := map[string]string{"status": "ok"}
	if err := printJSON(data); err != nil {
		t.Fatal(err)
	}
	_ = w.Close()
	os.Stdout = old

	var buf bytes.Buffer
	if _, err := buf.ReadFrom(r); err != nil {
		t.Fatal(err)
	}

	var parsed map[string]string
	if err := json.Unmarshal(buf.Bytes(), &parsed); err != nil {
		t.Fatalf("printJSON output is not valid JSON: %v\nOutput: %s", err, buf.String())
	}
	if parsed["status"] != "ok" {
		t.Errorf("expected status=ok, got %q", parsed["status"])
	}

	// Verify indentation.
	if !strings.Contains(buf.String(), "  ") {
		t.Error("expected indented JSON output")
	}
}

func TestPrintTable(t *testing.T) {
	var buf bytes.Buffer
	headers := []string{"NAME", "SIZE", "COUNT"}
	rows := [][]string{
		{"alpha", "1.0 MB", "5"},
		{"beta", "2.5 GB", "12"},
	}
	printTable(&buf, headers, rows)

	output := buf.String()
	lines := strings.Split(strings.TrimSpace(output), "\n")
	if len(lines) != 3 {
		t.Fatalf("expected 3 lines (header + 2 rows), got %d:\n%s", len(lines), output)
	}

	// Header should contain all column names.
	for _, h := range headers {
		if !strings.Contains(lines[0], h) {
			t.Errorf("header missing %q: %s", h, lines[0])
		}
	}

	// Rows should contain the data.
	if !strings.Contains(lines[1], "alpha") {
		t.Errorf("row 1 missing 'alpha': %s", lines[1])
	}
	if !strings.Contains(lines[2], "beta") {
		t.Errorf("row 2 missing 'beta': %s", lines[2])
	}
}

func TestTokenFromEnv(t *testing.T) {
	t.Setenv("MCR_TOKEN", "test-env-token")

	// Build client with empty token flag (should fall back to env).
	c, err := newClient("https://example.com", "", "", "")
	if err != nil {
		t.Fatal(err)
	}
	defer c.close()

	// The newClient does not resolve env; the main() PersistentPreRunE does.
	// So here we test the pattern manually.
	token := ""
	if token == "" {
		token = os.Getenv("MCR_TOKEN")
	}
	if token != "test-env-token" {
		t.Errorf("expected token from env, got %q", token)
	}
}

func TestTokenFlagOverridesEnv(t *testing.T) {
	t.Setenv("MCR_TOKEN", "env-token")

	flagVal := "flag-token"
	token := flagVal
	if token == "" {
		token = os.Getenv("MCR_TOKEN")
	}
	if token != "flag-token" {
		t.Errorf("expected flag token to override env, got %q", token)
	}
}

func TestNewClientREST(t *testing.T) {
	c, err := newClient("https://example.com", "", "mytoken", "")
	if err != nil {
		t.Fatal(err)
	}
	defer c.close()

	if c.useGRPC() {
		t.Error("expected REST client (no gRPC)")
	}
	if c.serverURL != "https://example.com" {
		t.Errorf("unexpected serverURL: %s", c.serverURL)
	}
	if c.token != "mytoken" {
		t.Errorf("unexpected token: %s", c.token)
	}
}

func TestNewClientBadCACert(t *testing.T) {
	_, err := newClient("https://example.com", "", "", "/nonexistent/ca.pem")
	if err == nil {
		t.Fatal("expected error for nonexistent CA cert")
	}
	if !strings.Contains(err.Error(), "CA cert") {
		t.Errorf("unexpected error: %v", err)
	}
}

func TestNewClientInvalidCACert(t *testing.T) {
	tmpFile := t.TempDir() + "/bad.pem"
	if err := os.WriteFile(tmpFile, []byte("not a certificate"), 0o600); err != nil {
		t.Fatal(err)
	}
	_, err := newClient("https://example.com", "", "", tmpFile)
	if err == nil {
		t.Fatal("expected error for invalid CA cert")
	}
	if !strings.Contains(err.Error(), "no valid certificates") {
		t.Errorf("unexpected error: %v", err)
	}
}

func TestRestDoSuccess(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Authorization") != "Bearer test-token" {
			t.Errorf("missing or wrong Authorization header: %s", r.Header.Get("Authorization"))
		}
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"status":"ok"}`))
	}))
	defer srv.Close()

	c := &apiClient{
		serverURL:  srv.URL,
		token:      "test-token",
		httpClient: srv.Client(),
	}

	data, err := c.restDo("GET", "/v1/health", nil)
	if err != nil {
		t.Fatal(err)
	}

	var resp struct {
		Status string `json:"status"`
	}
	if err := json.Unmarshal(data, &resp); err != nil {
		t.Fatal(err)
	}
	if resp.Status != "ok" {
		t.Errorf("expected status=ok, got %q", resp.Status)
	}
}

func TestRestDoError(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusForbidden)
		_, _ = w.Write([]byte(`{"error":"admin role required"}`))
	}))
	defer srv.Close()

	c := &apiClient{
		serverURL:  srv.URL,
		token:      "bad-token",
		httpClient: srv.Client(),
	}

	_, err := c.restDo("GET", "/v1/repositories", nil)
	if err == nil {
		t.Fatal("expected error for 403 response")
	}
	if !strings.Contains(err.Error(), "admin role required") {
		t.Errorf("error should contain server message: %v", err)
	}
	if !strings.Contains(err.Error(), "403") {
		t.Errorf("error should contain status code: %v", err)
	}
}

func TestRestDoPostBody(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Errorf("expected POST, got %s", r.Method)
		}
		if r.Header.Get("Content-Type") != "application/json" {
			t.Errorf("expected application/json content type, got %s", r.Header.Get("Content-Type"))
		}
		var body map[string]any
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			t.Errorf("failed to decode body: %v", err)
		}
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusCreated)
		_, _ = w.Write([]byte(`{"id":"gc-123"}`))
	}))
	defer srv.Close()

	c := &apiClient{
		serverURL:  srv.URL,
		token:      "token",
		httpClient: srv.Client(),
	}

	data, err := c.restDo("POST", "/v1/gc", map[string]string{"test": "value"})
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(string(data), "gc-123") {
		t.Errorf("unexpected response: %s", string(data))
	}
}
