package api

import "testing"

// What a car's page shows — which tabs, which rows of the Information tab, which
// columns of the Service history table, and the order each of those is laid out
// in — is stored on the car as key lists, so the validation has to keep them to
// keys the page actually renders. Two keys stay out of their hideable set:
// Information, because a car with no tabs left would be a dead end, and the
// service Date, because a history with the day taken out is not one.

func TestNormalizeHiddenTabs(t *testing.T) {
	got, err := normalizeKeys([]string{" fuel ", "parts", "fuel", ""}, hideableCarTabs, "tab")
	if err != nil {
		t.Fatalf("normalizeKeys: %v", err)
	}
	want := []string{"fuel", "parts"} // trimmed, blanks dropped, deduped
	assertKeys(t, got, want)

	// Clearing the list is how a car goes back to showing everything.
	if empty, err := normalizeKeys(nil, hideableCarTabs, "tab"); err != nil || len(empty) != 0 {
		t.Errorf("normalizeKeys(nil) = %v, %v; want empty and no error", empty, err)
	}

	// Information is the car itself; hiding it would leave a page with no tabs.
	if _, err := normalizeKeys([]string{"info"}, hideableCarTabs, "tab"); err == nil {
		t.Error("normalizeKeys allowed hiding the info tab, want an error")
	}
	// A key from a stale or wrong client is an error, not something to drop
	// quietly while the tab stays visible.
	if _, err := normalizeKeys([]string{"fuel", "nonsense"}, hideableCarTabs, "tab"); err == nil {
		t.Error("normalizeKeys accepted an unknown tab, want an error")
	}

	// The hideable set is the contract the web app's HIDEABLE_TABS mirrors:
	// every tab the car page renders beside Information.
	for _, key := range []string{
		"provider", "services", "technical", "maintenance", "fuel", "charging",
		"documents", "parts", "reminders",
	} {
		if !hideableCarTabs[key] {
			t.Errorf("tab %q should be hideable", key)
		}
	}
	if len(hideableCarTabs) != 9 {
		t.Errorf("hideableCarTabs has %d entries, want the 9 tabs beside Information", len(hideableCarTabs))
	}
}

func TestNormalizeHiddenFields(t *testing.T) {
	got, err := normalizeKeys([]string{"vin", " differentialOil ", "vin"}, hideableCarFields, "field")
	if err != nil {
		t.Fatalf("normalizeKeys: %v", err)
	}
	assertKeys(t, got, []string{"vin", "differentialOil"})

	if _, err := normalizeKeys([]string{"oilSpec", "nonsense"}, hideableCarFields, "field"); err == nil {
		t.Error("normalizeKeys accepted an unknown field, want an error")
	}
	// A tab key is not a field key — the two sets are validated separately.
	if _, err := normalizeKeys([]string{"fuel"}, hideableCarFields, "field"); err == nil {
		t.Error("normalizeKeys accepted a tab key as a field, want an error")
	}

	// Every Information row the web app renders must be hideable; unlike the
	// tabs there is no row the page has to keep.
	for _, key := range []string{
		"oilSpec", "transmissionOil", "differentialOil", "brakeFluid", "coolant",
		"odometer", "serviceInterval", "nextDue", "registrationPlate",
		"registrationCountry", "vin", "fuelType", "buildDate", "firstRegistration",
	} {
		if !hideableCarFields[key] {
			t.Errorf("field %q should be hideable", key)
		}
	}
	if len(hideableCarFields) != 14 {
		t.Errorf("hideableCarFields has %d entries, want the 14 Information rows", len(hideableCarFields))
	}
}

// The tabs arrange against a wider set than they hide against: Information
// cannot be switched off, but it can be moved off the front of the bar.
func TestNormalizeTabOrder(t *testing.T) {
	got, err := normalizeKeys([]string{"reminders", "info", "fuel"}, arrangeableCarTabs, "tab")
	if err != nil {
		t.Fatalf("normalizeKeys: %v", err)
	}
	assertKeys(t, got, []string{"reminders", "info", "fuel"})

	// Everything the bar renders has to be arrangeable — the hideable tabs plus
	// Information, and nothing else.
	for key := range hideableCarTabs {
		if !arrangeableCarTabs[key] {
			t.Errorf("tab %q should be arrangeable", key)
		}
	}
	if !arrangeableCarTabs["info"] {
		t.Error("the info tab should be arrangeable even though it cannot be hidden")
	}
	if len(arrangeableCarTabs) != len(hideableCarTabs)+1 {
		t.Errorf("arrangeableCarTabs has %d entries, want the hideable tabs plus Information", len(arrangeableCarTabs))
	}

	// A field key is not a tab key, and an invented tab is still an error.
	if _, err := normalizeKeys([]string{"vin"}, arrangeableCarTabs, "tab"); err == nil {
		t.Error("normalizeKeys accepted a field key as a tab, want an error")
	}
	if _, err := normalizeKeys([]string{"info", "nonsense"}, arrangeableCarTabs, "tab"); err == nil {
		t.Error("normalizeKeys accepted an unknown tab in an arrangement, want an error")
	}
}

// The arrangement of the Information rows shares the field key set — every row
// can be moved — but not the meaning: here the order of the list is the point,
// so it has to survive validation exactly as it was sent.
func TestNormalizeFieldOrder(t *testing.T) {
	got, err := normalizeKeys([]string{"vin", "odometer", "oilSpec"}, hideableCarFields, "field")
	if err != nil {
		t.Fatalf("normalizeKeys: %v", err)
	}
	assertKeys(t, got, []string{"vin", "odometer", "oilSpec"})

	// A key repeated by a client that lost track keeps its first position; a
	// second entry for the same row would put it in two places at once.
	got, err = normalizeKeys([]string{"vin", "odometer", "vin"}, hideableCarFields, "field")
	if err != nil {
		t.Fatalf("normalizeKeys: %v", err)
	}
	assertKeys(t, got, []string{"vin", "odometer"})

	// A partial arrangement is fine — the rows it leaves out follow the arranged
	// ones — but an invented row is still an error.
	if _, err := normalizeKeys([]string{"vin", "nonsense"}, hideableCarFields, "field"); err == nil {
		t.Error("normalizeKeys accepted an unknown field in an arrangement, want an error")
	}
}

// The connected service's headline readings arrange the same way, against the
// key set derived from what that panel renders.
func TestNormalizeMetricOrder(t *testing.T) {
	got, err := normalizeKeys([]string{"evRangeWithAc", "odometer"}, arrangeableCarMetrics, "reading")
	if err != nil {
		t.Fatalf("normalizeKeys: %v", err)
	}
	assertKeys(t, got, []string{"evRangeWithAc", "odometer"})

	// Every reading the panel can show has to be arrangeable, the two that are
	// not measures included.
	for _, key := range []string{
		"odometer", "fuelLevel", "fuelRange", "batteryLevel", "evRange",
		"evRangeWithAc", "chargingStatus", "location",
	} {
		if !arrangeableCarMetrics[key] {
			t.Errorf("reading %q should be arrangeable", key)
		}
	}

	// A field key is not a reading key — a car's two arrangements are separate.
	if _, err := normalizeKeys([]string{"vin"}, arrangeableCarMetrics, "reading"); err == nil {
		t.Error("normalizeKeys accepted a field key as a reading, want an error")
	}
}

func TestNormalizeHiddenServiceColumns(t *testing.T) {
	got, err := normalizeKeys([]string{" parts ", "notes", "parts", ""}, hideableServiceColumns, "service column")
	if err != nil {
		t.Fatalf("normalizeKeys: %v", err)
	}
	assertKeys(t, got, []string{"parts", "notes"}) // trimmed, blanks dropped, deduped

	// The date is what a service record is; a table of them without it would be
	// a list of unattributed work.
	if _, err := normalizeKeys([]string{"date"}, hideableServiceColumns, "service column"); err == nil {
		t.Error("normalizeKeys allowed hiding the date column, want an error")
	}
	// Neither a field key nor an invented one passes: each set is its own.
	if _, err := normalizeKeys([]string{"vin"}, hideableServiceColumns, "service column"); err == nil {
		t.Error("normalizeKeys accepted a field key as a service column, want an error")
	}
	if _, err := normalizeKeys([]string{"parts", "nonsense"}, hideableServiceColumns, "service column"); err == nil {
		t.Error("normalizeKeys accepted an unknown service column, want an error")
	}
	// The parts are one column, not one each: a key per part would put the table
	// back where it started, and these three were columns of their own once.
	for _, key := range []string{"oil", "engineFilter", "cabinFilter"} {
		if hideableServiceColumns[key] {
			t.Errorf("%q should not be a column of its own — the parts share one", key)
		}
	}

	// The hideable set is the contract the web app's HIDEABLE_SERVICE_COLUMNS
	// mirrors: every column that table renders beside the date.
	for _, key := range []string{"km", "nextDate", "nextKm", "parts", "notes", "file"} {
		if !hideableServiceColumns[key] {
			t.Errorf("service column %q should be hideable", key)
		}
	}
	if len(hideableServiceColumns) != 6 {
		t.Errorf("hideableServiceColumns has %d entries, want the 6 columns beside the date", len(hideableServiceColumns))
	}
}

// The parts a service can change hide against their own set — one key per
// boolean on the service_records collection, and no order beside it, because
// they are a checkbox list inside a single column.
func TestNormalizeHiddenServiceParts(t *testing.T) {
	got, err := normalizeKeys([]string{" oil ", "cabinFilter", "oil", ""}, hideableServiceParts, "service part")
	if err != nil {
		t.Fatalf("normalizeKeys: %v", err)
	}
	assertKeys(t, got, []string{"oil", "cabinFilter"}) // trimmed, blanks dropped, deduped

	// Every part is hideable, unlike the tabs and the columns: a service that
	// changed nothing is a real service, so there is no part the form must keep.
	for _, key := range []string{"oil", "engineFilter", "cabinFilter"} {
		if !hideableServiceParts[key] {
			t.Errorf("service part %q should be hideable", key)
		}
	}
	if len(hideableServiceParts) != 3 {
		t.Errorf("hideableServiceParts has %d entries, want the 3 booleans on a service record", len(hideableServiceParts))
	}

	// "parts" is the column those three share; it is not one of them. Sending
	// the column key here would hide nothing and quietly succeed if the two sets
	// were not kept apart.
	if _, err := normalizeKeys([]string{"parts"}, hideableServiceParts, "service part"); err == nil {
		t.Error("normalizeKeys accepted the parts column as a part, want an error")
	}
	if _, err := normalizeKeys([]string{"notes"}, hideableServiceParts, "service part"); err == nil {
		t.Error("normalizeKeys accepted a column key as a service part, want an error")
	}
	if _, err := normalizeKeys([]string{"oil", "nonsense"}, hideableServiceParts, "service part"); err == nil {
		t.Error("normalizeKeys accepted an unknown service part, want an error")
	}

	// And no part is a column, which is what keeps the table from widening as
	// parts are added.
	for key := range hideableServiceParts {
		if arrangeableServiceColumns[key] {
			t.Errorf("service part %q must not also be a column", key)
		}
	}
}

// The columns arrange against a wider set than they hide against, the way the
// tabs do: the date cannot be switched off, but it can be moved off the left.
func TestNormalizeServiceColumnOrder(t *testing.T) {
	got, err := normalizeKeys([]string{"notes", "date", "parts"}, arrangeableServiceColumns, "service column")
	if err != nil {
		t.Fatalf("normalizeKeys: %v", err)
	}
	assertKeys(t, got, []string{"notes", "date", "parts"})

	for key := range hideableServiceColumns {
		if !arrangeableServiceColumns[key] {
			t.Errorf("service column %q should be arrangeable", key)
		}
	}
	if !arrangeableServiceColumns["date"] {
		t.Error("the date column should be arrangeable even though it cannot be hidden")
	}
	if len(arrangeableServiceColumns) != len(hideableServiceColumns)+1 {
		t.Errorf("arrangeableServiceColumns has %d entries, want the hideable columns plus the date", len(arrangeableServiceColumns))
	}

	// A partial arrangement is fine — the columns it leaves out follow the
	// arranged ones — but an invented column is still an error.
	if _, err := normalizeKeys([]string{"date", "nonsense"}, arrangeableServiceColumns, "service column"); err == nil {
		t.Error("normalizeKeys accepted an unknown column in an arrangement, want an error")
	}
}

func assertKeys(t *testing.T, got, want []string) {
	t.Helper()
	if len(got) != len(want) {
		t.Fatalf("keys = %v, want %v", got, want)
	}
	for i := range got {
		if got[i] != want[i] {
			t.Fatalf("keys = %v, want %v", got, want)
		}
	}
}
