package packs

import (
	"encoding/json"
	"testing"
)

// TestParseUserPageLayoutDeterministic guards against silent drift in the
// embedded user_page_layout.json: two parses must produce identical layouts
// down to section field order. The Apply step trusts this determinism for
// idempotency (re-running converges on the same Config JSON).
func TestParseUserPageLayoutDeterministic(t *testing.T) {
	a, err := parseUserPageLayout()
	if err != nil {
		t.Fatalf("first parse: %v", err)
	}
	b, err := parseUserPageLayout()
	if err != nil {
		t.Fatalf("second parse: %v", err)
	}

	aJSON, err := json.Marshal(a)
	if err != nil {
		t.Fatalf("marshal a: %v", err)
	}
	bJSON, err := json.Marshal(b)
	if err != nil {
		t.Fatalf("marshal b: %v", err)
	}
	if string(aJSON) != string(bJSON) {
		t.Fatalf("non-deterministic parse:\n  a = %s\n  b = %s", aJSON, bJSON)
	}
}

// TestUserPageLayoutCanonicalIdentity asserts that parseUserPageLayout pins
// the canonical Id, ObjectAPIName, and Source regardless of what the JSON
// carries. The Apply path's SQL upsert keys off these values; if the JSON
// drifts, parse must re-stamp them.
func TestUserPageLayoutCanonicalIdentity(t *testing.T) {
	layout, err := parseUserPageLayout()
	if err != nil {
		t.Fatalf("parse: %v", err)
	}

	if layout.ID != curatedUserLayoutID {
		t.Errorf("Id = %q, want %q", layout.ID, curatedUserLayoutID)
	}
	if layout.ObjectAPIName != "User" {
		t.Errorf("ObjectAPIName = %q, want %q", layout.ObjectAPIName, "User")
	}
	if layout.Source != curatedLayoutSource {
		t.Errorf("Source = %q, want %q (must NOT be auto_generated, or ResolveLayout will overwrite it)",
			layout.Source, curatedLayoutSource)
	}
	if layout.GenerationVersion != 0 {
		t.Errorf("GenerationVersion = %d, want 0", layout.GenerationVersion)
	}
}

// TestUserPageLayoutShape locks the SF v63.0-aligned section shape so an
// accidental edit to user_page_layout.json that drops a required section
// or balloons the field count fails loudly in CI.
func TestUserPageLayoutShape(t *testing.T) {
	layout, err := parseUserPageLayout()
	if err != nil {
		t.Fatalf("parse: %v", err)
	}

	wantSections := []struct {
		id     string
		minLen int
	}{
		{"curated_User_section_identity", 7},
		{"curated_User_section_access", 4},
		{"curated_User_section_work", 4},
		{"curated_User_section_address", 4},
		{"curated_User_section_locale", 5},
		{"curated_User_section_system", 4},
	}
	if len(layout.Sections) != len(wantSections) {
		t.Fatalf("section count = %d, want %d", len(layout.Sections), len(wantSections))
	}
	totalFields := 0
	for i, want := range wantSections {
		got := layout.Sections[i]
		if got.ID != want.id {
			t.Errorf("section[%d].Id = %q, want %q", i, got.ID, want.id)
		}
		if len(got.Fields) < want.minLen {
			t.Errorf("section[%d] (%s): %d fields, want >= %d",
				i, got.ID, len(got.Fields), want.minLen)
		}
		totalFields += len(got.Fields)
	}
	// Per issue #656: cap field count well below the 165 the auto-generator
	// produces. SF v63.0 admin form has 48; this projection sits at ~33.
	if totalFields > 50 {
		t.Errorf("total field count = %d, want <= 50 (issue #656 prune)", totalFields)
	}
}
