package output

import (
	"bytes"
	"strings"
	"testing"
)

func TestPrint(t *testing.T) {
	env := SuccessEnvelope(map[string]interface{}{"status": "ok"}, nil)
	if err := Print(env, "json"); err != nil {
		t.Fatalf("Print error: %v", err)
	}
}

func TestPrintDefaultFormat(t *testing.T) {
	env := SuccessEnvelope(map[string]interface{}{"status": "ok"}, nil)
	if err := Print(env, ""); err != nil {
		t.Fatalf("Print default format error: %v", err)
	}
}

func TestPrintToJSON(t *testing.T) {
	var buf bytes.Buffer
	env := SuccessEnvelope(map[string]interface{}{"status": "ok"}, nil)
	if err := PrintTo(&buf, env, "json"); err != nil {
		t.Fatalf("PrintTo json: %v", err)
	}
	if !strings.Contains(buf.String(), `"ok"`) {
		t.Fatalf("expected ok in JSON output, got: %s", buf.String())
	}
	if !strings.Contains(buf.String(), `"status"`) {
		t.Fatalf("expected status in JSON output, got: %s", buf.String())
	}
}

func TestPrintToYAML(t *testing.T) {
	var buf bytes.Buffer
	env := SuccessEnvelope(map[string]interface{}{"status": "ok"}, nil)
	if err := PrintTo(&buf, env, "yaml"); err != nil {
		t.Fatalf("PrintTo yaml: %v", err)
	}
	if !strings.Contains(buf.String(), "ok") {
		t.Fatalf("expected ok in YAML output, got: %s", buf.String())
	}
}

func TestPrintToTableSlice(t *testing.T) {
	var buf bytes.Buffer
	env := SuccessEnvelope([]interface{}{
		map[string]interface{}{"id": float64(1), "name": "test"},
		map[string]interface{}{"id": float64(2), "name": "test2"},
	}, nil)
	if err := PrintTo(&buf, env, "table"); err != nil {
		t.Fatalf("PrintTo table slice: %v", err)
	}
	out := buf.String()
	if !strings.Contains(out, "id") || !strings.Contains(out, "name") {
		t.Fatalf("expected table headers, got: %s", out)
	}
	if !strings.Contains(out, "1") || !strings.Contains(out, "test") {
		t.Fatalf("expected table data, got: %s", out)
	}
}

func TestPrintToTableEmptySlice(t *testing.T) {
	var buf bytes.Buffer
	env := SuccessEnvelope([]interface{}{}, nil)
	if err := PrintTo(&buf, env, "table"); err != nil {
		t.Fatalf("PrintTo table empty: %v", err)
	}
	if !strings.Contains(buf.String(), "No results") {
		t.Fatalf("expected 'No results', got: %s", buf.String())
	}
}

func TestPrintToTableSliceNonMap(t *testing.T) {
	var buf bytes.Buffer
	env := SuccessEnvelope([]interface{}{"string1", "string2"}, nil)
	if err := PrintTo(&buf, env, "table"); err != nil {
		t.Fatalf("PrintTo table non-map slice: %v", err)
	}
	// Should fallback to JSON
	if !strings.Contains(buf.String(), "[") {
		t.Fatalf("expected JSON array fallback, got: %s", buf.String())
	}
}

func TestPrintToTableMap(t *testing.T) {
	var buf bytes.Buffer
	env := SuccessEnvelope(map[string]interface{}{
		"key1": "val1",
		"key2": "val2",
	}, nil)
	if err := PrintTo(&buf, env, "table"); err != nil {
		t.Fatalf("PrintTo table map: %v", err)
	}
	out := buf.String()
	if !strings.Contains(out, "KEY") || !strings.Contains(out, "VALUE") {
		t.Fatalf("expected KEY/VALUE headers, got: %s", out)
	}
}

func TestPrintToTableMapComplex(t *testing.T) {
	var buf bytes.Buffer
	env := SuccessEnvelope(map[string]interface{}{
		"nested": map[string]interface{}{"a": "b"},
	}, nil)
	if err := PrintTo(&buf, env, "table"); err != nil {
		t.Fatalf("PrintTo table complex map: %v", err)
	}
	// Should fallback to JSON because of nested map
	if !strings.Contains(buf.String(), "{") {
		t.Fatalf("expected JSON fallback for complex map, got: %s", buf.String())
	}
}

func TestPrintToTableError(t *testing.T) {
	var buf bytes.Buffer
	env := ErrorEnvelope(500, "server error", "try again")
	if err := PrintTo(&buf, env, "table"); err != nil {
		t.Fatalf("PrintTo table error: %v", err)
	}
	out := buf.String()
	if !strings.Contains(out, "Error [500]: server error") {
		t.Fatalf("expected error message with code, got: %s", out)
	}
	if !strings.Contains(out, "try again") {
		t.Fatalf("expected suggestion, got: %s", out)
	}
}

func TestPrintToTableErrorWithoutCode(t *testing.T) {
	var buf bytes.Buffer
	env := ErrorEnvelope(nil, "plain error", "")
	if err := PrintTo(&buf, env, "table"); err != nil {
		t.Fatalf("PrintTo table error without code: %v", err)
	}
	out := buf.String()
	if !strings.Contains(out, "Error: plain error") {
		t.Fatalf("expected plain error line, got: %s", out)
	}
	if strings.Contains(out, "Error [") {
		t.Fatalf("did not expect a code prefix, got: %s", out)
	}
}

func TestCollectKeysSortsRemaining(t *testing.T) {
	m := map[string]interface{}{
		"zebra": 1, "id": 1, "alpha": 1, "mango": 1, "name": 1,
	}
	keys := collectKeys(m)
	// Priority keys first (id, name), then the rest in sorted order.
	want := []string{"id", "name", "alpha", "mango", "zebra"}
	if len(keys) != len(want) {
		t.Fatalf("keys = %v, want %v", keys, want)
	}
	for i := range want {
		if keys[i] != want[i] {
			t.Fatalf("keys = %v, want %v", keys, want)
		}
	}
}

func TestPrintToTableMapDeterministic(t *testing.T) {
	m := map[string]interface{}{
		"zebra": "z", "id": float64(1), "alpha": "a", "name": "n", "mango": "m",
	}
	render := func() string {
		var buf bytes.Buffer
		if err := PrintTo(&buf, SuccessEnvelope(m, nil), "table"); err != nil {
			t.Fatalf("render: %v", err)
		}
		return buf.String()
	}
	first := render()
	for i := 0; i < 25; i++ {
		if got := render(); got != first {
			t.Fatalf("table output is not deterministic:\n%q\nvs\n%q", first, got)
		}
	}
}

func TestPrintToTableSliceDeterministic(t *testing.T) {
	items := []interface{}{
		map[string]interface{}{"id": float64(1), "zebra": "z", "alpha": "a", "name": "n"},
		map[string]interface{}{"id": float64(2), "zebra": "z2", "alpha": "a2", "name": "n2"},
	}
	render := func() string {
		var buf bytes.Buffer
		if err := PrintTo(&buf, SuccessEnvelope(items, nil), "table"); err != nil {
			t.Fatalf("render: %v", err)
		}
		return buf.String()
	}
	first := render()
	for i := 0; i < 25; i++ {
		if got := render(); got != first {
			t.Fatalf("slice table header order is not deterministic:\n%q\nvs\n%q", first, got)
		}
	}
}

func TestPrintToTableErrorNoSuggestion(t *testing.T) {
	var buf bytes.Buffer
	env := ErrorEnvelope(500, "server error", "")
	if err := PrintTo(&buf, env, "table"); err != nil {
		t.Fatalf("PrintTo table error no suggestion: %v", err)
	}
	out := buf.String()
	if strings.Contains(out, "Suggestion:") {
		t.Fatalf("should not have suggestion line, got: %s", out)
	}
}

func TestPrintToTableNilData(t *testing.T) {
	var buf bytes.Buffer
	env := SuccessEnvelope(nil, nil)
	if err := PrintTo(&buf, env, "table"); err != nil {
		t.Fatalf("PrintTo table nil data: %v", err)
	}
	if !strings.Contains(buf.String(), "No data") {
		t.Fatalf("expected 'No data', got: %s", buf.String())
	}
}

func TestPrintToDefaultFormat(t *testing.T) {
	var buf bytes.Buffer
	env := SuccessEnvelope("test", nil)
	if err := PrintTo(&buf, env, ""); err != nil {
		t.Fatalf("PrintTo default format: %v", err)
	}
	// Default should be JSON
	if !strings.Contains(buf.String(), `"ok"`) {
		t.Fatalf("expected JSON output for default format, got: %s", buf.String())
	}
}

func TestPrintToUnknownFormat(t *testing.T) {
	var buf bytes.Buffer
	env := SuccessEnvelope("test", nil)
	if err := PrintTo(&buf, env, "xml"); err != nil {
		t.Fatalf("PrintTo unknown format: %v", err)
	}
	// Unknown format should fallback to JSON
	if !strings.Contains(buf.String(), `"ok"`) {
		t.Fatalf("expected JSON fallback for unknown format, got: %s", buf.String())
	}
}

func TestHasComplexValues(t *testing.T) {
	tests := []struct {
		name string
		m    map[string]interface{}
		want bool
	}{
		{"flat", map[string]interface{}{"a": "1", "b": "2"}, false},
		{"nested map", map[string]interface{}{"a": map[string]interface{}{"x": "y"}}, true},
		{"nested slice", map[string]interface{}{"a": []interface{}{1, 2}}, true},
		{"empty", map[string]interface{}{}, false},
		{"nil", nil, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := hasComplexValues(tt.m); got != tt.want {
				t.Fatalf("hasComplexValues = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCollectKeys(t *testing.T) {
	m := map[string]interface{}{
		"number":     float64(3),
		"title":      "test",
		"id":         float64(1),
		"status":     "open",
		"custom_key": "val",
	}
	keys := collectKeys(m)
	// Priority keys should come first
	if len(keys) != 5 {
		t.Fatalf("expected 5 keys, got %d", len(keys))
	}
	if keys[0] != "number" {
		t.Fatalf("first key should be 'number', got %q", keys[0])
	}
	if keys[1] != "id" {
		t.Fatalf("second key should be 'id', got %q", keys[1])
	}
	if keys[2] != "title" {
		t.Fatalf("third key should be 'title', got %q", keys[2])
	}
	if keys[3] != "status" {
		t.Fatalf("fourth key should be 'status', got %q", keys[3])
	}
}

func TestFormatValue(t *testing.T) {
	tests := []struct {
		name string
		v    interface{}
		want string
	}{
		{"nil", nil, ""},
		{"string", "hello", "hello"},
		{"int", 42, "42"},
		{"float", 3.14, "3.14"},
		{"bool", true, "true"},
		{"slice", []interface{}{1, 2, 3}, "[1,2,3]"},
		{"map", map[string]interface{}{"a": "b"}, `{"a":"b"}`},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := formatValue(tt.v)
			if got != tt.want {
				t.Fatalf("formatValue = %q, want %q", got, tt.want)
			}
		})
	}
}

func TestPrintTableUnwrapsResourceWrappedList(t *testing.T) {
	env := SuccessEnvelope(map[string]interface{}{
		"total_count": 2,
		"tags": []interface{}{
			map[string]interface{}{"name": "v1.0.0", "id": 1},
			map[string]interface{}{"name": "v1.1.0", "id": 2},
		},
	}, nil)
	var buf bytes.Buffer
	if err := PrintTo(&buf, env, "table"); err != nil {
		t.Fatalf("PrintTo returned error: %v", err)
	}
	out := buf.String()
	if !strings.Contains(out, "total_count: 2") {
		t.Fatalf("missing summary line: %s", out)
	}
	if !strings.Contains(out, "v1.0.0") || !strings.Contains(out, "v1.1.0") {
		t.Fatalf("missing table rows: %s", out)
	}
	if strings.Contains(out, "\"ok\"") {
		t.Fatalf("should not fall back to JSON: %s", out)
	}
}

func TestPrintTableKeepsJSONForNestedObjects(t *testing.T) {
	env := SuccessEnvelope(map[string]interface{}{
		"commit": map[string]interface{}{"sha": "abc"},
		"files":  []interface{}{},
	}, nil)
	var buf bytes.Buffer
	if err := PrintTo(&buf, env, "table"); err != nil {
		t.Fatalf("PrintTo returned error: %v", err)
	}
	if !strings.Contains(buf.String(), "\"ok\"") {
		t.Fatalf("nested object map should fall back to JSON: %s", buf.String())
	}
}
