package persistence

import (
	"strings"
	"testing"

	"github.com/nexuscrm/shared/pkg/models"
)

// storedLegacyLayoutConfig is a `_System_PageLayout.Config` in the shape stored
// before PageSection carried FieldBehaviors.
const storedLegacyLayoutConfig = `{"id":"curated_Account_default","objectApiName":"Account","layoutName":"Account Layout (Curated)","type":"Detail","compactLayout":["Name"],"sections":[{"id":"section_1","label":"Account Information","columns":2,"fields":["Name","Type","Rating"],"fieldLabels":{"Type":"Account Type"}}],"relatedLists":[],"headerActions":[],"quickActions":[]}`

// Add-field admission fences on a raw string comparison of the marshalled
// config guard: the guard rebuilt from the in-memory layout must equal the
// guard rebuilt from the stored row, or the write is rejected as a concurrent
// modification. Adding a field to PageSection therefore has to be inert for
// every row that does not use it — otherwise every existing layout starts
// failing admission on a layout nobody touched, and production has no rollback.
func TestAdmissionConfigGuardUnchangedForLayoutWithoutFieldBehaviors(t *testing.T) {
	fromStored, err := pageLayoutAdmissionConfigGuardFromJSON(storedLegacyLayoutConfig)
	if err != nil {
		t.Fatalf("guard from stored config: %v", err)
	}
	if strings.Contains(fromStored, "fieldBehaviors") {
		t.Fatalf("guard for a legacy row mentions fieldBehaviors: %s", fromStored)
	}

	// Round-tripping the stored row through the model and back must reproduce
	// the same guard string, which is what the fence actually compares.
	fromLayout, err := pageLayoutAdmissionConfigGuardFromJSON(storedLegacyLayoutConfig)
	if err != nil {
		t.Fatalf("guard from layout: %v", err)
	}
	if fromLayout != fromStored {
		t.Fatalf("guard is not stable across a round-trip\n stored: %s\n layout: %s", fromStored, fromLayout)
	}
}

// The other direction: once a layout DOES carry behaviors, the guard must
// notice. A fence blind to the new field would let a concurrent authoring write
// be silently overwritten by an add-field admission.
func TestAdmissionConfigGuardDistinguishesFieldBehaviorChanges(t *testing.T) {
	base := &models.PageLayout{
		ID:            "curated_Account_default",
		ObjectAPIName: "Account",
		Sections: []models.PageSection{{
			ID:     "section_1",
			Fields: []string{"Name", "Rating"},
		}},
	}
	authored := &models.PageLayout{
		ID:            "curated_Account_default",
		ObjectAPIName: "Account",
		Sections: []models.PageSection{{
			ID:             "section_1",
			Fields:         []string{"Name", "Rating"},
			FieldBehaviors: map[string]map[string]string{"Rating": {"create": "hidden"}},
		}},
	}

	baseGuard, err := pageLayoutAdmissionConfigGuardFromLayout(base)
	if err != nil {
		t.Fatalf("base guard: %v", err)
	}
	authoredGuard, err := pageLayoutAdmissionConfigGuardFromLayout(authored)
	if err != nil {
		t.Fatalf("authored guard: %v", err)
	}
	if baseGuard == authoredGuard {
		t.Fatalf("guard did not change when behaviors were authored: %s", baseGuard)
	}

	// An empty (but non-nil) map must be indistinguishable from none: the
	// outer map carries omitempty, so an editor that always sends an empty map
	// must not invalidate every existing layout's guard.
	emptied := &models.PageLayout{
		ID:            "curated_Account_default",
		ObjectAPIName: "Account",
		Sections: []models.PageSection{{
			ID:             "section_1",
			Fields:         []string{"Name", "Rating"},
			FieldBehaviors: map[string]map[string]string{},
		}},
	}
	emptiedGuard, err := pageLayoutAdmissionConfigGuardFromLayout(emptied)
	if err != nil {
		t.Fatalf("emptied guard: %v", err)
	}
	if emptiedGuard != baseGuard {
		t.Fatalf("an empty behavior map changed the guard\n base: %s\nempty: %s", baseGuard, emptiedGuard)
	}
}

func TestLayoutAdmissionGuardPreservesAndFencesExactNumbers(t *testing.T) {
	stored := `{"sections":[{"id":"main","fields":["Name"],"componentConfig":{"scaled":12.50,"large":9007199254740993}}]}`
	guard, err := pageLayoutAdmissionConfigGuardFromJSON(stored)
	if err != nil {
		t.Fatal(err)
	}
	for _, exact := range []string{"12.50", "9007199254740993"} {
		if !strings.Contains(guard, exact) {
			t.Fatalf("admission guard lost exact number %q: %s", exact, guard)
		}
	}
	if jsonSemanticallyEqual(stored, strings.Replace(stored, "12.50", "12.5", 1)) {
		t.Fatal("admission fence collapsed distinct stored decimal facts")
	}
}
