package runtimeassembly

import (
	"context"
	"encoding/json"
	historysemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/queryv1"
	"reflect"
	"strings"
	"testing"
	"time"

	layoutdocument "github.com/nexuscrm/backend/internal/layoutauthoring"
	database "github.com/nexuscrm/db"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/models"
	v1 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv1"
	v2 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv2"
)

// This is the original stored-document failure through the real transaction,
// permission owner, schema resolver, Layout history, and database rollback.
func TestHistoryLayoutMutationLifecycleOnTiDB(t *testing.T) {
	ctx, cancel := context.WithTimeout(t.Context(), 60*time.Second)
	defer cancel()
	db, _ := newLayoutRuntimeTestDB(t, ctx, "_System_Object", "_System_Field", "_System_PageLayout", "_System_LayoutAssignment", "_System_PageLayoutHistory", "_System_PermissionStateVersion", "User", "UserRole", "Profile", "PermissionSet", "PermissionSetAssignment", "_System_Permission", "_System_PermissionSetPermission", "PermissionSetGroupComponent", "PermissionSetGroup", "PermissionSetGroupAssignment", "_System_MutingPermissionSetPermission", "MutingPermissionSet", "_System_CustomPermission", "ProfileCustomPermission", "PermissionSetCustomPermission")
	exec := func(statement string, args ...any) {
		t.Helper()
		if _, err := db.ExecContext(ctx, statement, args...); err != nil {
			t.Fatal(err)
		}
	}
	exec(`INSERT INTO Profile (Id,Name,IsActive) VALUES ('profile-admin','Admin',1)`)
	exec("INSERT INTO `User` (Id,Username,Email,Password,ProfileId,LastName,IsActive,IsDeleted) VALUES ('actor','actor','actor@example.test','test-disabled','profile-admin','Actor',1,0)")
	exec(`INSERT INTO _System_Object (Id,ApiName,TableType,Label,SharingModel) VALUES ('issue-incarnation','Issue','standard_object','Issue','Private')`)
	exec(`INSERT INTO PermissionSet (Id,Name,Label,ProfileId,IsOwnedByProfile,IsDeleted) VALUES ('grant','Admin','Admin','profile-admin',1,0)`)
	exec(`INSERT INTO _System_Permission (Id,ApiName,Label,Category,IsActive) VALUES ('customize','CustomizeApplication','Customize','system',1)`)
	exec(`INSERT INTO _System_PermissionSetPermission (Id,PermissionSetId,PermissionApiName,IsEnabled) VALUES ('grant-customize','grant','CustomizeApplication',1)`)
	stateRepo, err := persistence.NewPermissionStateRepository(db)
	if err != nil {
		t.Fatal(err)
	}
	state, err := stateRepo.EnsureInitialized(ctx)
	if err != nil {
		t.Fatal(err)
	}
	tm := persistence.NewTransactionManager(database.NewConnectionFromDB(db))
	authority, err := persistence.NewPermissionStateAuthority(tm)
	if err != nil {
		t.Fatal(err)
	}
	repo := persistence.NewMetadataRepository(db)
	ms := metadata.NewMetadataService(repo, nil, nil)
	ms.SetTransactionManager(tm)
	perm := permissions.NewPermissionService(persistence.NewPermissionRepository(db), nil, persistence.NewUserRepository(db))
	policy, err := permissions.NewTransactionCurrentPermissionAuthoringPolicy(perm, authority)
	if err != nil {
		t.Fatal(err)
	}
	engine := &LayoutAuthoringEngine{metadata: ms, permissions: perm, policy: policy, transactions: tm}
	actor := models.UserSession{ID: "actor", ProfileID: "profile-admin", ProfileName: "Admin"}
	projection := v2.HistoryRelatedListProjection{ObjectID: "Changes", Columns: map[string]historysemantic.HistoryColumn{
		"Key": historysemantic.HistoryColumnID, "IssueRef": historysemantic.HistoryColumnParentID, "Attribute": historysemantic.HistoryColumnField,
		"Before": historysemantic.HistoryColumnOldValue, "After": historysemantic.HistoryColumnNewValue, "ValueType": historysemantic.HistoryColumnDataType,
		"At": historysemantic.HistoryColumnCreatedDate, "Actor": historysemantic.HistoryColumnCreatedByID,
	}}
	doc := v2.LayoutDocument{LayoutID: "layout-original", ObjectID: "Issue", Name: "Original", Kind: "Detail",
		CompactFields: []string{}, Tabs: []string{}, Sections: []v1.LayoutSection{},
		RelatedLists: []v1.RelatedList{{RelatedListID: "changes", Label: "Changes", ObjectID: "Changes", LookupFieldID: "IssueRef", FieldIDs: []string{"Attribute", "Before", "After", "At", "Actor"}, PrefillMappings: []v1.PrefillMapping{}}},
		RecordPanels: []v1.RecordPanel{}, HeaderActions: v1.ActionPlacement{Actions: []v1.LayoutAction{}}, QuickActions: v1.ActionPlacement{Actions: []v1.LayoutAction{}}, RecordStates: []v1.RecordStateRule{}, PrimaryHeaderActions: []string{},
	}
	layout, err := layoutdocument.PageLayoutFromDocument(doc)
	if err != nil {
		t.Fatal(err)
	}
	raw, err := json.Marshal(layout)
	if err != nil {
		t.Fatal(err)
	}
	exec(`INSERT INTO _System_PageLayout (Id,ObjectApiName,LayoutName,Config,Source,IsCustomized) VALUES ('layout-original','Issue','Original',?,'admin_created',1)`, raw)
	revision := func() string {
		t.Helper()
		snapshot, err := engine.ReadWorkspace(ctx, "Issue")
		if err != nil {
			t.Fatal(err)
		}
		return snapshot.Versions.WorkspaceVersion
	}
	nameChange := func(name string) v2.Operation {
		changes := preserveLayoutDocumentChanges()
		changes.Name = v1.Change[string]{Mode: v1.ChangeSet, Value: &name}
		return v2.Operation{Update: &v2.UpdateLayout{LayoutID: doc.LayoutID, Changes: changes, ListFields: v2.FieldIDListChange{Mode: v1.ChangePreserve}}}
	}
	request := v2.Request{ObjectID: "Issue", ExpectedLayoutRevision: revision(), Operation: nameChange("Renamed")}
	unchanged := request.ExpectedLayoutRevision
	if _, err := engine.Mutate(ctx, actor, state.Version, request); err == nil || !strings.Contains(err.Error(), "related_lists[0].object_id") {
		t.Fatalf("expected undeclared virtual object failure, got %v", err)
	}
	if revision() != unchanged {
		t.Fatal("rejected save changed the workspace")
	}
	request.HistoryRelatedLists = []v2.HistoryRelatedListProjection{projection}
	response, err := engine.Mutate(ctx, actor, state.Version, request)
	if err != nil || !response.Changed {
		t.Fatalf("name-only save: %#v %v", response, err)
	}
	saved, _, err := engine.ReadStoredLayout(ctx, "Issue", doc.LayoutID)
	if err != nil {
		t.Fatal(err)
	}
	savedDoc, err := layoutdocument.DocumentFromPageLayout(*saved)
	if err != nil {
		t.Fatal(err)
	}
	want := doc
	want.Name = "Renamed"
	if !reflect.DeepEqual(savedDoc, want) {
		t.Fatalf("name-only save changed layout content: %#v", savedDoc)
	}
	if _, err := engine.Mutate(ctx, actor, state.Version, request); err == nil {
		t.Fatal("stale workspace revision accepted")
	}
	mutate := func(operation v2.Operation, wantSuccess bool) {
		t.Helper()
		request.ExpectedLayoutRevision = revision()
		request.Operation = operation
		before := request.ExpectedLayoutRevision
		response, err := engine.Mutate(ctx, actor, state.Version, request)
		if wantSuccess {
			if err != nil || !response.Changed {
				t.Fatalf("mutation: %#v %v", response, err)
			}
		} else {
			if err == nil {
				t.Fatal("invalid mutation accepted")
			}
			if revision() != before {
				t.Fatal("failed mutation did not roll back")
			}
		}
	}
	mutate(v2.Operation{Clone: &v2.CloneLayout{SourceLayoutID: doc.LayoutID, TargetLayoutID: "layout-clone", TargetName: "Clone"}}, true)
	created := doc
	created.LayoutID, created.Name = "layout-created", "Created"
	mutate(v2.Operation{Create: &v2.CreateLayout{Document: created, ListFields: v2.FieldIDListChange{Mode: v1.ChangePreserve}}}, true)
	mutate(nameChange("Changed again"), true)
	mutate(v2.Operation{Restore: &v2.RestoreLayout{LayoutID: doc.LayoutID, HistoryVersion: 1}}, true)
	saved, _, err = engine.ReadStoredLayout(ctx, "Issue", doc.LayoutID)
	if err != nil {
		t.Fatal(err)
	}
	savedDoc, err = layoutdocument.DocumentFromPageLayout(*saved)
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(savedDoc, want) {
		t.Fatalf("restore lost the history list: %#v", savedDoc)
	}
	badLists := append([]v1.RelatedList{}, doc.RelatedLists...)
	badLists[0].FieldIDs = []string{"Secret"}
	changes := preserveLayoutDocumentChanges()
	changes.RelatedLists = v1.Change[[]v1.RelatedList]{Mode: v1.ChangeSet, Value: &badLists}
	mutate(v2.Operation{Update: &v2.UpdateLayout{LayoutID: doc.LayoutID, Changes: changes, ListFields: v2.FieldIDListChange{Mode: v1.ChangePreserve}}}, false)
	exec(`INSERT INTO _System_Object (Id,ApiName,TableType,Label,SharingModel) VALUES ('physical-changes','Changes','standard_object','Physical Changes','Private')`)
	mutate(nameChange("Must use physical schema"), false)
	exec("UPDATE `User` SET IsActive=0 WHERE Id='actor'")
	mutate(nameChange("Unauthorized"), false)
}
