package layoutauthoringv1

import (
	"bytes"
	"encoding/json"
	"fmt"
	"reflect"
	"testing"

	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
)

func storedLayoutWithCreateContext(context *CreateContext) StoredLayout {
	return StoredLayout{
		Document: validDocument(), OrderedListFields: []string{"Name"},
		Lifecycle:       LayoutLifecycleState{ProvenanceKind: "admin"},
		ResourceVersion: "resource-v1", AggregateVersion: "aggregate-v1", WorkspaceVersion: "workspace-v1",
		CreateContext: context, ViewRef: validViewRef(50), Receipts: validReceipts(50),
	}
}

func TestStoredLayoutCreateContextPreservesUnknownEmptyAndRequired(t *testing.T) {
	for _, tc := range []struct {
		name    string
		context *CreateContext
	}{
		{"legacy unknown", nil}, {"known empty", &CreateContext{RequiredFields: []string{}}},
		{"required fields", &CreateContext{RequiredFields: []string{"Name", "OwnerId"}}},
	} {
		t.Run(tc.name, func(t *testing.T) {
			stored := storedLayoutWithCreateContext(tc.context)
			if err := ValidateStoredLayoutResponse(StoredLayoutRequest{LayoutID: "layout-main", ViewRef: &stored.ViewRef}, stored); err != nil {
				t.Fatal(err)
			}
			encoded, err := json.Marshal(stored)
			if err != nil {
				t.Fatal(err)
			}
			if bytes.Contains(encoded, []byte(`"create_context"`)) != (tc.context != nil) {
				t.Fatalf("context presence changed: %s", encoded)
			}
			var decoded StoredLayout
			if err = json.Unmarshal(encoded, &decoded); err != nil {
				t.Fatal(err)
			}
			if !reflect.DeepEqual(decoded.CreateContext, tc.context) {
				t.Fatalf("context roundtrip=%#v; want %#v", decoded.CreateContext, tc.context)
			}
		})
	}
	unknown := storedLayoutWithCreateContext(nil)
	known := storedLayoutWithCreateContext(&CreateContext{RequiredFields: []string{"Name"}})
	if SemanticResponseSize(known) <= SemanticResponseSize(unknown) {
		t.Fatal("read-only context escaped response size accounting")
	}
}

func TestCreateContextValidatesCanonicalBoundedFieldFacts(t *testing.T) {
	for name, fields := range map[string][]string{
		"nil": nil, "empty id": {""}, "invalid id": {"Owner.Name"},
		"not sorted": {"OwnerId", "Name"}, "duplicate": {"Name", "Name"}, "case duplicate": {"Name", "name"},
	} {
		t.Run(name, func(t *testing.T) {
			context := CreateContext{RequiredFields: fields}
			if err := ValidateCreateContext(context); err == nil {
				t.Fatal("invalid context accepted")
			}
			stored := storedLayoutWithCreateContext(&context)
			if err := ValidateStoredLayoutResponse(StoredLayoutRequest{LayoutID: "layout-main"}, stored); err == nil {
				t.Fatal("invalid context escaped stored response validation")
			}
		})
	}
}

func TestCreateContextWholeObjectIsNotLimitedToOneSection(t *testing.T) {
	fields := make([]string, MaxFieldsPerSection+100)
	for i := range fields {
		fields[i] = fmt.Sprintf("Field%04d", i)
	}
	context := &CreateContext{RequiredFields: fields}
	stored := storedLayoutWithCreateContext(context)
	first, second := stored.Document.Sections[0], stored.Document.Sections[0]
	first.Fields = fields[:MaxFieldsPerSection]
	second.SectionID = "second"
	second.Fields = fields[MaxFieldsPerSection:]
	stored.Document.Sections = []LayoutSection{first, second}
	if err := ValidateStoredLayoutResponse(StoredLayoutRequest{LayoutID: "layout-main"}, stored); err != nil {
		t.Fatalf("existing multi-section layout rejected by whole-object context: %v", err)
	}
}

func TestCreateContextUsesExistingSemanticResponseBudget(t *testing.T) {
	// The count ceiling excludes only inputs that cannot possibly fit, even
	// with the shortest legal name. Actual names and structure remain charged.
	if got := SemanticResponseSize("A"); got != minimumCreateRequiredFieldSemanticBytes {
		t.Fatalf("minimum field encoding changed to %d; revisit count admission", got)
	}
	count := MaxSemanticResponseBytes/(common.MaxFieldIdentifierBytes+2) + 1
	fields := make([]string, count)
	for i := range fields {
		fields[i] = fmt.Sprintf("F%063d", i)
	}
	if err := ValidateCreateContext(CreateContext{RequiredFields: fields[:count-2]}); err != nil {
		t.Fatalf("context within existing response budget rejected: %v", err)
	}
	assertResourceExhausted(t, ValidateCreateContext(CreateContext{RequiredFields: fields}), LimitSemanticResponse, MaxSemanticResponseBytes)
}
