package services

// Pure-Go tests for RenderSpec.

import (
	"errors"
	"testing"
)

func TestRenderSpec_HappyPath(t *testing.T) {
	s := DefaultRenderSpec()
	if err := s.Validate(); err != nil {
		t.Fatalf("default render spec must validate: %v", err)
	}
}

func TestRenderSpec_ShapeMustBeKnown(t *testing.T) {
	cases := []RenderShape{ShapeList, ShapeCards, ShapeCalendar, ShapeTimeline}
	for _, sh := range cases {
		t.Run(string(sh), func(t *testing.T) {
			s := RenderSpec{Shape: sh}
			if err := s.Validate(); err != nil {
				t.Fatalf("shape %q must validate: %v", sh, err)
			}
		})
	}
}

func TestRenderSpec_TimelineConfigValidates(t *testing.T) {
	cases := []struct {
		name string
		cfg  TimelineConfig
		ok   bool
	}{
		{"empty defaults are fine", TimelineConfig{}, true},
		{"known palette", TimelineConfig{Palette: "kind-coded"}, true},
		{"known density", TimelineConfig{Density: "compact"}, true},
		{"known range preset", TimelineConfig{RangePreset: "2y"}, true},
		{"custom range with bounds", TimelineConfig{RangePreset: "custom", RangeFrom: "2026-01-01", RangeTo: "2026-12-31"}, true},
		{"unknown palette rejects", TimelineConfig{Palette: "neon"}, false},
		{"unknown density rejects", TimelineConfig{Density: "tiny"}, false},
		{"unknown range rejects", TimelineConfig{RangePreset: "10y"}, false},
		{"oversized range_from rejects", TimelineConfig{RangeFrom: string(make([]byte, 64))}, false},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			s := RenderSpec{Shape: ShapeTimeline, Timeline: &tc.cfg}
			err := s.Validate()
			if tc.ok && err != nil {
				t.Fatalf("expected ok, got error: %v", err)
			}
			if !tc.ok && !errors.Is(err, ErrInvalidInput) {
				t.Fatalf("expected ErrInvalidInput, got %v", err)
			}
		})
	}
}

func TestRenderSpec_UnknownShapeRejects(t *testing.T) {
	s := RenderSpec{Shape: "kanban"}
	if err := s.Validate(); !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("unknown shape must reject, got %v", err)
	}
}

func TestRenderSpec_ListColumnEnum(t *testing.T) {
	s := RenderSpec{Shape: ShapeList, List: &ListConfig{Columns: []string{"date", "bogus"}}}
	if err := s.Validate(); !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("unknown list column must reject, got %v", err)
	}
}

func TestRenderSpec_KnownListColumnsAccepted(t *testing.T) {
	s := RenderSpec{Shape: ShapeList, List: &ListConfig{Columns: KnownListColumns}}
	if err := s.Validate(); err != nil {
		t.Fatalf("known columns must validate: %v", err)
	}
}

func TestRenderSpec_ListSortEnum(t *testing.T) {
	s := RenderSpec{Shape: ShapeList, List: &ListConfig{Sort: "weird"}}
	if err := s.Validate(); !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("unknown sort must reject, got %v", err)
	}
}

func TestRenderSpec_ListDensityEnum(t *testing.T) {
	s := RenderSpec{Shape: ShapeList, List: &ListConfig{Density: "huge"}}
	if err := s.Validate(); !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("unknown density must reject, got %v", err)
	}
}

func TestRenderSpec_CardsGroupByEnum(t *testing.T) {
	s := RenderSpec{Shape: ShapeCards, Cards: &CardsConfig{GroupBy: "month"}}
	if err := s.Validate(); !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("unknown group_by must reject, got %v", err)
	}
}

func TestRenderSpec_CalendarViewEnum(t *testing.T) {
	s := RenderSpec{Shape: ShapeCalendar, Calendar: &CalendarConfig{DefaultView: "year"}}
	if err := s.Validate(); !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("unknown default_view must reject, got %v", err)
	}
}

func TestRenderSpec_RowActionEnum(t *testing.T) {
	for _, action := range KnownRowActions {
		t.Run(string(action), func(t *testing.T) {
			s := RenderSpec{Shape: ShapeList, List: &ListConfig{RowAction: action}}
			if err := s.Validate(); err != nil {
				t.Fatalf("known row_action %q must validate: %v", action, err)
			}
		})
	}
	s := RenderSpec{Shape: ShapeList, List: &ListConfig{RowAction: "delete"}}
	if err := s.Validate(); !errors.Is(err, ErrInvalidInput) {
		t.Fatalf("unknown row_action must reject, got %v", err)
	}
	// Empty defaults to navigate at the renderer level — schema accepts.
	empty := RenderSpec{Shape: ShapeList, List: &ListConfig{}}
	if err := empty.Validate(); err != nil {
		t.Fatalf("empty row_action must validate (defaults to navigate): %v", err)
	}
}

func TestRenderSpec_RoundTrip(t *testing.T) {
	original := RenderSpec{
		Shape: ShapeList,
		List: &ListConfig{
			Columns: []string{"time", "actor", "title", "project"},
			Sort:    SortDateDesc,
			Density: DensityCompact,
		},
		Cards:    &CardsConfig{GroupBy: CardsGroupByDay, Sort: SortDateAsc},
		Calendar: &CalendarConfig{DefaultView: CalendarMonth},
	}
	b, err := MarshalRenderSpec(original)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}
	parsed, err := UnmarshalRenderSpec(b)
	if err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if parsed.Shape != original.Shape {
		t.Errorf("shape mismatch: %v vs %v", parsed.Shape, original.Shape)
	}
	if parsed.List == nil || parsed.List.Density != DensityCompact {
		t.Errorf("list config not preserved: %+v", parsed.List)
	}
}
