package metadata

import (
	"context"
	"os"
	"path/filepath"
	"strings"
	"testing"

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

// userSchemaForDecoratorTest constructs a minimal User schema with the
// fields the ServerDefaultedFieldsDecorator targets, plus an unrelated
// field that must NOT receive the DefaultedOnCreate flag.
func userSchemaForDecoratorTest() *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName: constants.TableUser,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldUsername, Type: constants.FieldTypeString, Required: true},
			{APIName: constants.FieldPassword, Type: constants.FieldTypeString, Required: true},
			{APIName: constants.FieldEmail, Type: constants.FieldTypeEmail, Required: true},
			{APIName: constants.FieldFirstName, Type: constants.FieldTypeString, Required: false},
			{APIName: constants.FieldLastName, Type: constants.FieldTypeString, Required: true},
		},
	}
}

func TestServerDefaultedFieldsDecorator_MarksUserPassword(t *testing.T) {
	d := NewServerDefaultedFieldsDecorator(PlatformServerDefaultedFieldDeclarations()...)
	decorated, err := d.DecorateSchema(context.Background(), userSchemaForDecoratorTest())
	if err != nil {
		t.Fatalf("DecorateSchema err = %v", err)
	}

	pwd := models.FindField(decorated, constants.FieldPassword)
	if pwd == nil {
		t.Fatalf("Password missing from decorated schema")
	}
	if !pwd.DefaultedOnCreate {
		t.Fatalf("Password.DefaultedOnCreate = false, want true")
	}
	if !pwd.Required {
		t.Fatalf("Password.Required = false, want true (decorator must not lie about caller obligation)")
	}
}

func TestPlatformServerDefaultedDeclarationsAreScopeFloorBacked(t *testing.T) {
	path := filepath.Join("..", "docs", "design", "scope-floor.md")
	data, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("read scope floor %s: %v", path, err)
	}
	text := string(data)
	for _, decl := range PlatformServerDefaultedFieldDeclarations() {
		ref := decl.ObjectAPIName + "." + decl.FieldAPIName
		if !strings.Contains(text, "`"+ref+"`") {
			t.Fatalf("platform server-defaulted declaration %q is not backed by %s; floor declarations must have architecture-owned evidence", ref, path)
		}
	}
}

func TestServerDefaultedFieldsDecorator_DoesNotInferUserPasswordWithoutDeclaration(t *testing.T) {
	d := NewServerDefaultedFieldsDecorator()
	input := userSchemaForDecoratorTest()
	decorated, err := d.DecorateSchema(context.Background(), input)
	if err != nil {
		t.Fatalf("DecorateSchema err = %v", err)
	}
	if decorated != input {
		t.Fatal("decorator cloned User schema without any server-default declaration")
	}
	pwd := models.FindField(decorated, constants.FieldPassword)
	if pwd == nil {
		t.Fatal("Password missing from decorated schema")
	}
	if pwd.DefaultedOnCreate {
		t.Fatal("Password.DefaultedOnCreate = true without registered declaration")
	}
}

// TestServerDefaultedFieldsDecorator_DoesNotTouchOtherUserFields locks the
// narrow surface: Username, Email, LastName, and every other field on
// User must keep DefaultedOnCreate=false. Username in particular has SF
// v63 contract `required=true, defaultedOnCreate=false` — there is no
// server-side defaulting in Salesforce. The NexusCRM-internal
// "Username = Email" fallback in applyUserDefaults serves BulkInsert /
// system-context callers (where validateRequiredForCreate does not run)
// but must NOT be advertised as a public-API contract. PR #900
// round-6 review.
//
// FirstName create optionality is owned by authoritative schema metadata,
// not by DefaultedOnCreate. The decorator must therefore stay narrow and
// leave FirstName=false here.
func TestServerDefaultedFieldsDecorator_DoesNotTouchOtherUserFields(t *testing.T) {
	d := NewServerDefaultedFieldsDecorator(PlatformServerDefaultedFieldDeclarations()...)
	decorated, err := d.DecorateSchema(context.Background(), userSchemaForDecoratorTest())
	if err != nil {
		t.Fatalf("DecorateSchema err = %v", err)
	}
	for _, apiName := range []string{constants.FieldUsername, constants.FieldEmail, constants.FieldFirstName, constants.FieldLastName} {
		f := models.FindField(decorated, apiName)
		if f == nil {
			t.Fatalf("%s missing from decorated schema", apiName)
		}
		if f.DefaultedOnCreate {
			t.Fatalf("%s.DefaultedOnCreate = true, want false — decorator over-reached", apiName)
		}
	}
}

// TestServerDefaultedFieldsDecorator_NonUserSchemaUntouched verifies the
// fast-path: for any schema other than User, the input pointer is
// returned unchanged. Returning the input is the SchemaDecorator
// contract's documented "no-op" signal (see metadata_service.go:64),
// cheaper than a superfluous clone.
func TestServerDefaultedFieldsDecorator_NonUserSchemaUntouched(t *testing.T) {
	d := NewServerDefaultedFieldsDecorator(PlatformServerDefaultedFieldDeclarations()...)
	input := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Name", Type: constants.FieldTypeString, Required: true},
		},
	}
	decorated, err := d.DecorateSchema(context.Background(), input)
	if err != nil {
		t.Fatalf("DecorateSchema err = %v", err)
	}
	if decorated != input {
		t.Fatalf("non-User schema was cloned; decorator must return input pointer unchanged for no-op fast path")
	}
}

func TestServerDefaultedFieldsDecorator_MarksSiblingMaxTarget(t *testing.T) {
	parentField := "Parent__c"
	d := NewServerDefaultedFieldsDecorator()
	input := &models.ObjectMetadata{
		APIName: "Line__c",
		Fields: []models.FieldMetadata{
			{APIName: "Parent__c", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent__c"}, Createable: true, Updateable: true},
			{APIName: "SortOrder__c", Type: models.FieldType("integer"), Required: true, Createable: true, Updateable: true, SiblingMaxParentField: &parentField},
		},
	}

	decorated, err := d.DecorateSchema(context.Background(), input)
	if err != nil {
		t.Fatalf("DecorateSchema err = %v", err)
	}
	if decorated == input {
		t.Fatal("sibling-max schema was not cloned")
	}

	sort := models.FindField(decorated, "SortOrder__c")
	if sort == nil {
		t.Fatal("SortOrder__c missing from decorated schema")
	}
	if !sort.DefaultedOnCreate {
		t.Fatal("SortOrder__c.DefaultedOnCreate = false, want true")
	}
	if !sort.Required || !sort.Createable || !sort.Updateable {
		t.Fatalf("decorator changed sibling-max field shape: required=%v createable=%v updateable=%v", sort.Required, sort.Createable, sort.Updateable)
	}

	inputSort := models.FindField(input, "SortOrder__c")
	if inputSort == nil {
		t.Fatal("SortOrder__c missing from input schema")
	}
	if inputSort.DefaultedOnCreate {
		t.Fatal("input SortOrder__c.DefaultedOnCreate = true; decorator mutated the shared input")
	}
}

func TestServerDefaultedFieldsDecorator_IgnoresBlankSiblingMaxParentField(t *testing.T) {
	blankParentField := "   "
	d := NewServerDefaultedFieldsDecorator()
	input := &models.ObjectMetadata{
		APIName: "Line__c",
		Fields: []models.FieldMetadata{
			{APIName: "SortOrder__c", Type: models.FieldType("integer"), Required: true, Createable: true, Updateable: true, SiblingMaxParentField: &blankParentField},
		},
	}

	decorated, err := d.DecorateSchema(context.Background(), input)
	if err != nil {
		t.Fatalf("DecorateSchema err = %v", err)
	}
	if decorated != input {
		t.Fatal("blank sibling-max parent field cloned schema; want no-op")
	}
	sort := models.FindField(decorated, "SortOrder__c")
	if sort == nil {
		t.Fatal("SortOrder__c missing from decorated schema")
	}
	if sort.DefaultedOnCreate {
		t.Fatal("blank sibling-max parent field marked DefaultedOnCreate")
	}
}

// TestServerDefaultedFieldsDecorator_IsIdempotent guards against multi-
// pass scenarios (cache refreshes, snapshot rebuilds): running the
// decorator on its own output must be a no-op once the flag is set.
// Without this guard, repeated invocation would keep cloning the
// schema for no reason.
func TestServerDefaultedFieldsDecorator_IsIdempotent(t *testing.T) {
	d := NewServerDefaultedFieldsDecorator(PlatformServerDefaultedFieldDeclarations()...)
	first, err := d.DecorateSchema(context.Background(), userSchemaForDecoratorTest())
	if err != nil {
		t.Fatalf("first DecorateSchema err = %v", err)
	}
	second, err := d.DecorateSchema(context.Background(), first)
	if err != nil {
		t.Fatalf("second DecorateSchema err = %v", err)
	}
	if second != first {
		t.Fatalf("decorator re-cloned an already-decorated schema; idempotency broken")
	}
}

// TestServerDefaultedFieldsDecorator_NilInput pins the contract for a
// nil schema input. Mirrors CompoundVirtualFieldDecorator (which also
// returns (nil, nil) on nil input rather than panicking).
func TestServerDefaultedFieldsDecorator_NilInput(t *testing.T) {
	d := NewServerDefaultedFieldsDecorator()
	out, err := d.DecorateSchema(context.Background(), nil)
	if err != nil {
		t.Fatalf("DecorateSchema(nil) err = %v", err)
	}
	if out != nil {
		t.Fatalf("DecorateSchema(nil) returned non-nil schema = %v", out)
	}
}

// TestServerDefaultedFieldsDecorator_DoesNotMutateInput guards the
// SchemaDecorator MUST-NOT-MUTATE-INPUT contract (metadata_service.go:58).
// The input pointer is shared with concurrent readers once published to
// the cache; mutating it would race.
func TestServerDefaultedFieldsDecorator_DoesNotMutateInput(t *testing.T) {
	d := NewServerDefaultedFieldsDecorator(PlatformServerDefaultedFieldDeclarations()...)
	input := userSchemaForDecoratorTest()
	_, err := d.DecorateSchema(context.Background(), input)
	if err != nil {
		t.Fatalf("DecorateSchema err = %v", err)
	}
	pwd := models.FindField(input, constants.FieldPassword)
	if pwd == nil {
		t.Fatalf("input Password missing — test setup broken")
	}
	if pwd.DefaultedOnCreate {
		t.Fatalf("input Password.DefaultedOnCreate = true; decorator mutated the shared input")
	}
}
