package runtimeassembly

import (
	"context"
	"reflect"
	"testing"

	layoutdocument "github.com/nexuscrm/backend/internal/layoutauthoring"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/shared/pkg/models"
	authoring "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv2"
)

type layoutAssignmentCaptureRepository struct {
	ports.MetadataRepository
	assignment *models.LayoutAssignment
}

func (repo *layoutAssignmentCaptureRepository) CreateAssignment(_ context.Context, assignment *models.LayoutAssignment) error {
	cloned := *assignment
	repo.assignment = &cloned
	return nil
}

func TestLayoutAuthoringNoOpStopsBeforePersistence(t *testing.T) {
	document := semantic.LayoutDocument{
		LayoutID: "layout-account", ObjectID: "Account", Name: "Account", Kind: "Detail",
		CompactFields: []string{"Name"}, Tabs: []string{}, Sections: []authoring.LayoutSection{},
		RelatedLists: []authoring.RelatedList{}, RecordPanels: []authoring.RecordPanel{},
		HeaderActions: authoring.ActionPlacement{Actions: []authoring.LayoutAction{}},
		QuickActions:  authoring.ActionPlacement{Actions: []authoring.LayoutAction{}},
		RecordStates:  []authoring.RecordStateRule{}, PrimaryHeaderActions: []string{},
	}
	layout, err := layoutdocument.PageLayoutFromDocument(document)
	if err != nil {
		t.Fatal(err)
	}
	before := metadata.LayoutAuthoringAggregateSnapshot{
		ObjectAPIName: "Account", OrderedListFields: []string{"Name"}, Layouts: []*models.PageLayout{layout},
	}
	request := semantic.Request{ObjectID: "Account", Operation: semantic.Operation{Update: &semantic.UpdateLayout{
		LayoutID: layout.ID, Changes: preserveLayoutDocumentChanges(),
		ListFields: semantic.FieldIDListChange{Mode: authoring.ChangePreserve},
	}}}
	// An empty MetadataService would fail on every write. A successful result
	// therefore proves an unchanged editor save exits before persistence,
	// history, ownership transfer, and schema validation.
	engine := &LayoutAuthoringEngine{metadata: &metadata.MetadataService{}}
	changed, layoutID, _, _, _, err := engine.apply(t.Context(), "user-admin", request, before)
	if err != nil {
		t.Fatal(err)
	}
	if changed || layoutID == nil || *layoutID != layout.ID {
		t.Fatalf("no-op result changed=%v layoutID=%v", changed, layoutID)
	}
	projected, err := layoutdocument.DocumentFromPageLayout(*layout)
	if err != nil || !reflect.DeepEqual(projected, document) {
		t.Fatalf("fixture drifted: document=%#v err=%v", projected, err)
	}
}

func TestLayoutAuthoringAssignmentUpdateReturnsExistingIdentity(t *testing.T) {
	profileID := "profile-admin"
	requestedProfileID := "PROFILE-ADMIN"
	roleID, requestedRoleID := "role-a", "ROLE-A"
	recordTypeID, requestedRecordTypeID := "record-type-a", "RECORD-TYPE-A"
	before := metadata.LayoutAuthoringAggregateSnapshot{
		ObjectAPIName: "Account",
		Layouts: []*models.PageLayout{
			{ID: "layout-old", ObjectAPIName: "Account"},
			{ID: "layout-new", ObjectAPIName: "Account"},
		},
		Assignments: []*models.LayoutAssignment{{
			ID: "assignment-existing", ObjectAPIName: "Account", LayoutID: "layout-old", ProfileID: &profileID, RoleID: &roleID, RecordTypeID: &recordTypeID,
		}},
	}
	repo := &layoutAssignmentCaptureRepository{}
	engine := &LayoutAuthoringEngine{metadata: metadata.NewMetadataService(repo, nil, nil)}
	request := semantic.Request{ObjectID: "Account", Operation: semantic.Operation{
		AssignmentUpsert: &semantic.UpsertLayoutAssignment{
			LayoutID: "layout-new", Selectors: semantic.AssignmentSelectors{ProfileID: &requestedProfileID, RoleID: &requestedRoleID, RecordTypeID: &requestedRecordTypeID},
		},
	}}

	changed, layoutID, assignmentID, _, _, err := engine.apply(t.Context(), "user-admin", request, before)
	if err != nil {
		t.Fatal(err)
	}
	if !changed || layoutID == nil || *layoutID != "layout-new" ||
		assignmentID == nil || *assignmentID != "assignment-existing" {
		t.Fatalf("assignment update result = changed %v, layout %v, assignment %v", changed, layoutID, assignmentID)
	}
	if repo.assignment == nil || repo.assignment.ID != "assignment-existing" || repo.assignment.LayoutID != "layout-new" {
		t.Fatalf("persisted assignment = %#v", repo.assignment)
	}
}

func preserveLayoutDocumentChanges() semantic.LayoutDocumentChanges {
	return semantic.LayoutDocumentChanges{
		Name: authoring.Change[string]{Mode: authoring.ChangePreserve}, Kind: authoring.Change[string]{Mode: authoring.ChangePreserve},
		LayoutVersion: authoring.Change[uint32]{Mode: authoring.ChangePreserve}, CompactFields: authoring.Change[[]string]{Mode: authoring.ChangePreserve},
		Tabs: authoring.Change[[]string]{Mode: authoring.ChangePreserve}, Sections: authoring.Change[[]authoring.LayoutSection]{Mode: authoring.ChangePreserve},
		RelatedLists: authoring.Change[[]authoring.RelatedList]{Mode: authoring.ChangePreserve}, RecordPanels: authoring.Change[[]authoring.RecordPanel]{Mode: authoring.ChangePreserve},
		HeaderActions: authoring.Change[authoring.ActionPlacement]{Mode: authoring.ChangePreserve}, QuickActions: authoring.Change[authoring.ActionPlacement]{Mode: authoring.ChangePreserve},
		RecordStates: authoring.Change[[]authoring.RecordStateRule]{Mode: authoring.ChangePreserve}, PrimaryHeaderActions: authoring.Change[[]string]{Mode: authoring.ChangePreserve},
	}
}
