package catalogsnapshot

import (
	"bytes"
	"fmt"
	"os"
	"reflect"
	"testing"

	"github.com/nexuscrm/shared/pkg/models"
	layoutauthoringv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv1"
)

// These immutable neutral fixtures were emitted by the unextended compiler.
// Re-encoding them must retain their original content-addressed identity.
func TestLayoutCreateContextPreservesLegacyArtifacts(t *testing.T) {
	read := func(name string) []byte {
		t.Helper()
		raw, err := os.ReadFile("testdata/" + name)
		if err != nil {
			t.Fatal(err)
		}
		return raw
	}
	catalogBytes := read("layout-authoring-catalog-v20.json")
	viewBytes := read("layout-authoring-view-v27.json")
	snapshot := mustOpenSnapshot(t, makeArtifact(ArtifactCatalogSnapshot, catalogBytes))
	view := mustOpenView(t, makeArtifact(ArtifactRuntimeMetadataView, viewBytes))
	for _, test := range []struct {
		name     string
		original []byte
		document any
	}{
		{"catalog", catalogBytes, snapshot.doc}, {"view", viewBytes, view.doc},
	} {
		t.Run(test.name, func(t *testing.T) {
			encoded, err := marshalCanonical(test.document)
			if err != nil || !bytes.Equal(encoded, test.original) {
				t.Fatalf("legacy bytes changed: %v", err)
			}
		})
	}
	stored, ok := view.LayoutAuthoringStoredLayout("layout-work-item")
	if !ok || stored.CreateContext != nil {
		t.Fatalf("legacy absence changed: %#v", stored.CreateContext)
	}
	if err := view.VerifySource(snapshot); err != nil {
		t.Fatal(err)
	}
}

func TestLayoutCreateContextTracksSchemaWithoutChangingWriteVersions(t *testing.T) {
	input := layoutAuthoringCompileInput()
	oldSnapshot := mustOpenSnapshot(t, mustCompile(t, input))
	project := func(snapshot *CatalogSnapshot) Artifact {
		t.Helper()
		artifact, err := ProjectRuntimeMetadataView(snapshot, ProjectionKey{AppInstallationID: testInstallA, CapabilityID: layoutauthoringv1.CapabilityName})
		if err != nil {
			t.Fatal(err)
		}
		return artifact
	}
	oldArtifact := project(oldSnapshot)
	oldView := mustOpenView(t, oldArtifact)
	before, _ := oldView.LayoutAuthoringStoredLayout("layout-work-item")
	if before.CreateContext == nil || before.CreateContext.RequiredFields == nil || len(before.CreateContext.RequiredFields) != 0 {
		t.Fatalf("expected known-empty input requirements: %#v", before.CreateContext)
	}
	for _, object := range input.Objects {
		if object.Metadata.APIName == "WorkItem" {
			for i := range object.Metadata.Fields {
				if object.Metadata.Fields[i].APIName == "Subject" {
					object.Metadata.Fields[i].Required = true
					object.Metadata.Fields[i].Createable = true
				}
			}
		}
	}
	input = withRevision(input, testRevision+1)
	for i := range input.LayoutAuthoringAggregates {
		input.LayoutAuthoringAggregates[i].CatalogRevision = input.Lineage.CatalogRevision
	}
	newSnapshot := mustOpenSnapshot(t, mustCompile(t, input))
	newArtifact := project(newSnapshot)
	view := mustOpenView(t, newArtifact)
	after, _ := view.LayoutAuthoringStoredLayout("layout-work-item")
	if after.CreateContext == nil || !reflect.DeepEqual(after.CreateContext.RequiredFields, []string{"Subject"}) {
		t.Fatalf("context = %#v", after.CreateContext)
	}
	if before.ResourceVersion != after.ResourceVersion || before.AggregateVersion != after.AggregateVersion || before.WorkspaceVersion != after.WorkspaceVersion {
		t.Fatal("read-only context changed Layout write preconditions")
	}
	if oldArtifact.Digest() == newArtifact.Digest() || oldSnapshot.Digest() == newSnapshot.Digest() {
		t.Fatal("source rules did not change immutable identity")
	}
	aggregate := layoutAuthoringAggregateForTest(&input, "WorkItem")
	versions, err := ComputeLayoutAuthoringVersions(aggregate.ObjectAPIName, aggregate.OrderedListFields, aggregate.Layouts, aggregate.Assignments)
	if err != nil || versions.WorkspaceVersion != after.WorkspaceVersion || versions.LayoutAggregateVersions[after.Document.LayoutID] != after.AggregateVersion {
		t.Fatalf("locked write versions drifted: %v", err)
	}
	store, err := NewDirectoryStore(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	for _, artifact := range []Artifact{oldArtifact, newArtifact} {
		if err := store.Put(t.Context(), artifact); err != nil {
			t.Fatal(err)
		}
	}
	reloaded, err := store.Get(t.Context(), ArtifactRuntimeMetadataView, oldArtifact.Digest())
	if err != nil {
		t.Fatal(err)
	}
	oldStored, _ := mustOpenView(t, reloaded).LayoutAuthoringStoredLayout("layout-work-item")
	if oldStored.CreateContext == nil || len(oldStored.CreateContext.RequiredFields) != 0 {
		t.Fatal("old digest acquired newer requirements")
	}
	after.CreateContext.RequiredFields[0] = "ChangedByCaller"
	again, _ := view.LayoutAuthoringStoredLayout("layout-work-item")
	if !reflect.DeepEqual(again.CreateContext.RequiredFields, []string{"Subject"}) {
		t.Fatal("caller mutated immutable context")
	}
	effective, err := BuildEffectiveMetadata(input)
	if err != nil {
		t.Fatal(err)
	}
	compiled, err := CompileEffectiveMetadata(effective)
	if err != nil || compiled.Digest() != newSnapshot.Digest() {
		t.Fatalf("effective metadata lost create context: %v", err)
	}
}

func TestLayoutCreateContextDescribesTheWholeObject(t *testing.T) {
	input := layoutAuthoringCompileInput()
	for _, object := range input.Objects {
		if object.Metadata.APIName == "WorkItem" {
			for i := 0; i < 1001; i++ {
				name := fmt.Sprintf("Required%04d", i)
				object.Metadata.Fields = append(object.Metadata.Fields, models.FieldMetadata{
					ID: "field-" + name, APIName: name, Label: name, PhysicalName: name,
					Type: "string", Required: true, Createable: true,
				})
			}
		}
	}
	view := projectLayoutAuthoringViewForTest(t, input)
	stored, ok := view.LayoutAuthoringStoredLayout("layout-work-item")
	if !ok || stored.CreateContext == nil || len(stored.CreateContext.RequiredFields) != 1001 {
		t.Fatal("whole-object requirements were capped by a single layout section's size")
	}
}

func TestLayoutCreateContextRejectsMissingForgedAndDowngradedFacts(t *testing.T) {
	for _, test := range []string{"missing", "forged", "downgraded"} {
		t.Run(test, func(t *testing.T) {
			snapshot := mustOpenSnapshot(t, mustCompile(t, layoutAuthoringCompileInput()))
			switch test {
			case "missing":
				snapshot.doc.LayoutAuthoring.Workspaces[0].CreateContext = nil
			case "forged":
				snapshot.doc.LayoutAuthoring.Workspaces[0].CreateContext.RequiredFields = []string{"Unrequired"}
			case "downgraded":
				snapshot.doc.Schema = CatalogSnapshotSchemaV20
			}
			raw, err := marshalCanonical(snapshot.doc)
			if err != nil {
				t.Fatal(err)
			}
			if _, err := OpenCatalogSnapshot(makeArtifact(ArtifactCatalogSnapshot, raw)); err == nil {
				t.Fatal("invalid source context accepted")
			}
		})
	}
	for _, test := range []string{"missing", "forged", "downgraded"} {
		t.Run("view/"+test, func(t *testing.T) {
			snapshot := mustOpenSnapshot(t, mustCompile(t, layoutAuthoringCompileInput()))
			artifact, err := ProjectRuntimeMetadataView(snapshot, ProjectionKey{AppInstallationID: testInstallA, CapabilityID: layoutauthoringv1.CapabilityName})
			if err != nil {
				t.Fatal(err)
			}
			view := mustOpenView(t, artifact)
			switch test {
			case "missing":
				view.doc.LayoutAuthoring.Workspaces[0].CreateContext = nil
			case "forged":
				view.doc.LayoutAuthoring.Workspaces[0].CreateContext.RequiredFields = []string{"Unrequired"}
			case "downgraded":
				view.doc.Schema = RuntimeMetadataViewSchemaV27
			}
			raw, err := marshalCanonical(view.doc)
			if err != nil {
				t.Fatal(err)
			}
			opened, err := OpenRuntimeMetadataView(makeArtifact(ArtifactRuntimeMetadataView, raw))
			if err == nil && (test != "forged" || opened.VerifySource(snapshot) == nil) {
				t.Fatal("invalid view context accepted")
			}
		})
	}
}
