//go:build runtime_api_integration

package runtimeapi

import (
	"context"
	"encoding/json"
	"errors"
	"net"
	"os"
	"testing"
	"time"

	"github.com/nexuscrm/backend/internal/runtimeassembly"
	database "github.com/nexuscrm/db"
	"github.com/nexuscrm/kernel/pkg/schemacatalog"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	authoring "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv1"
	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv2"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials/insecure"
	grpcmetadata "google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
	"google.golang.org/grpc/test/bufconn"
)

// The authority fixture starts after authentication. Schema, transaction-current
// permissions, mutation engine, host policy, server classifier, protobuf and
// gRPC transport are real; no engine or database outcome is stubbed.
func TestLayoutConflictReasonsCrossRealTiDBAndGRPC(t *testing.T) {
	if os.Getenv("TIDB_HOST") == "" {
		t.Skip("runtime_api_integration requires the declared TiDB environment")
	}
	ctx, cancel := context.WithTimeout(t.Context(), 90*time.Second)
	defer cancel()
	db, cleanup := openRuntimeAPIScratchDB(t)
	defer cleanup()
	wanted := map[string]bool{}
	for _, name := range []string{"_System_Object", "_System_Field", "_System_PageLayout", "_System_LayoutAssignment", "_System_PageLayoutHistory", "_System_RecordType", "_System_PermissionStateVersion", "User", "UserRole", "Profile", "PermissionSet", "PermissionSetAssignment", "_System_Permission", "_System_PermissionSetPermission", "PermissionSetGroupComponent", "PermissionSetGroup", "PermissionSetGroupAssignment", "_System_MutingPermissionSetPermission", "MutingPermissionSet", "_System_CustomPermission", "ProfileCustomPermission", "PermissionSetCustomPermission"} {
		wanted[name] = true
	}
	definitions, err := schemacatalog.ManagedTableDefinitions()
	if err != nil {
		t.Fatal(err)
	}
	schema := persistence.NewSchemaRepository(db, nil)
	for _, definition := range definitions {
		if wanted[definition.TableName] {
			if err := schema.CreatePhysicalTable(ctx, definition); err != nil {
				t.Fatal(err)
			}
			delete(wanted, definition.TableName)
		}
	}
	if len(wanted) != 0 {
		t.Fatalf("canonical table closure missing: %v", wanted)
	}
	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 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)`)
	exec(`INSERT INTO _System_Object (Id,ApiName,TableType,Label,SharingModel,ListFields) VALUES ('object','WorkItem','standard_object','Work Item','Private','[]')`)
	exec(`INSERT INTO _System_Field (Id,ObjectId,ApiName,Label,Type) VALUES ('id-field','object','Id','ID','string')`)
	for _, id := range []string{"default-layout", "assigned-layout"} {
		layout := models.PageLayout{ID: id, ObjectAPIName: "WorkItem", LayoutName: id, Type: "Detail", Source: "admin_created", IsDefault: id == "default-layout", Sections: []models.PageSection{}, RelatedLists: []models.RelatedListConfig{}}
		raw, err := json.Marshal(layout)
		if err != nil {
			t.Fatal(err)
		}
		exec(`INSERT INTO _System_PageLayout (Id,ObjectApiName,LayoutName,Config,Source,IsDefault,IsCustomized) VALUES (?,'WorkItem',?,?,'admin_created',?,1)`, id, id, raw, layout.IsDefault)
	}
	exec(`INSERT INTO _System_LayoutAssignment (Id,ObjectApiName,LayoutId,ProfileId,Specificity) VALUES ('assignment','WorkItem','assigned-layout','profile-admin',1)`)
	exec(`INSERT INTO _System_LayoutAssignment (Id,ObjectApiName,LayoutId,Specificity) VALUES ('default-assignment','WorkItem','default-layout',0)`)
	stateRepo, err := persistence.NewPermissionStateRepository(db)
	if err != nil {
		t.Fatal(err)
	}
	state, err := stateRepo.EnsureInitialized(ctx)
	if err != nil {
		t.Fatal(err)
	}
	connection := database.NewConnectionFromDB(db)
	permissionService := permissions.NewPermissionService(persistence.NewPermissionRepository(db), nil, persistence.NewUserRepository(db))
	runtime, err := runtimeassembly.NewLayoutAuthoringRuntime(ctx, connection, permissionService)
	if err != nil {
		t.Fatal(err)
	}
	permissionProjection, err := runtimeassembly.NewEffectivePermissionProjection(connection, permissionService)
	if err != nil {
		t.Fatal(err)
	}
	server, err := NewLayoutAuthoringV2Server(runtime.Engine, permissionProjection)
	if err != nil {
		t.Fatal(err)
	}
	policy, err := runtimegrpc.NewPolicyWithOptions(runtimegrpc.Config{Authority: func(context.Context, string) (runtimegrpc.VerifiedCaller, error) {
		return runtimegrpc.VerifiedCaller(testWorkloadID), nil
	}, AdmissionLimit: 1, AttemptCap: time.Second}, runtimegrpc.WithPlatformCapabilityMaxAdmissionLease(semantic.CapabilityName, time.Second), runtimegrpc.WithResourceClassAdmissionLimit(runtimegrpc.ResourceClassHeavyWork, 1))
	if err != nil {
		t.Fatal(err)
	}
	trusted := TrustedCall{orgID: testOrgID, appInstallationID: testInstallationID, callerWorkloadID: testWorkloadID, principalID: "actor", securityEpoch: 1, capabilities: []string{semantic.CapabilityName}, permissionFloor: state.Version, user: models.UserSession{ID: "actor", ProfileID: "profile-admin", ProfileName: "Admin"}}
	listener := bufconn.Listen(1024 * 1024)
	grpcServer := grpc.NewServer(grpc.ChainUnaryInterceptor(policy.UnaryServerInterceptor(), func(ctx context.Context, request any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
		return handler(context.WithValue(ctx, trustedCallContextKey{}, trusted), request)
	}))
	runtimev1.RegisterLayoutAuthoringV2ServiceServer(grpcServer, server)
	go func() { _ = grpcServer.Serve(listener) }()
	defer grpcServer.Stop()
	clientConnection, err := grpc.NewClient("passthrough:///layout-test", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }))
	if err != nil {
		t.Fatal(err)
	}
	defer clientConnection.Close()
	client := runtimev1.NewLayoutAuthoringV2ServiceClient(clientConnection)
	before, err := runtime.Engine.ReadWorkspace(ctx, "WorkItem")
	if err != nil {
		t.Fatal(err)
	}
	exec(`UPDATE _System_Object SET ListFields='["Id"]' WHERE Id='object'`)
	current, err := runtime.Engine.ReadWorkspace(ctx, "WorkItem")
	if err != nil {
		t.Fatal(err)
	}
	for _, test := range []struct {
		name, layout, revision string
		reason                 common.ConflictReason
	}{
		{"stale revision", "assigned-layout", before.Versions.WorkspaceVersion, common.ConflictRevisionMismatch},
		{"default deletion", "default-layout", current.Versions.WorkspaceVersion, common.ConflictDefaultRequired},
		{"referenced deletion", "assigned-layout", current.Versions.WorkspaceVersion, common.ConflictReferenced},
	} {
		t.Run(test.name, func(t *testing.T) {
			callCtx, callCancel := context.WithTimeout(ctx, time.Second)
			defer callCancel()
			callCtx = grpcmetadata.NewOutgoingContext(callCtx, grpcmetadata.Pairs(envelope.ProtocolMajorMetadataKey, "1", envelope.ProtocolMinorMetadataKey, "0", envelope.CapabilityMetadataKey, semantic.CapabilityName))
			request := semantic.Request{ObjectID: "WorkItem", ExpectedLayoutRevision: test.revision, Operation: semantic.Operation{Delete: &semantic.DeleteLayout{LayoutID: test.layout}}}
			if test.reason == common.ConflictRevisionMismatch {
				name := "Rejected save"
				request.Operation = semantic.Operation{Update: &semantic.UpdateLayout{LayoutID: test.layout, Changes: authoring.LayoutDocumentChanges{
					Name:                 authoring.Change[string]{Mode: authoring.ChangeSet, Value: &name},
					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},
				}, ListFields: authoring.FieldIDListChange{Mode: authoring.ChangePreserve}}}
			}
			wireRequest, err := mapping.ToWireLayoutAuthoringV2MutateRequest(callCtx, request)
			if err != nil {
				t.Fatal(err)
			}
			_, wireErr := client.Mutate(callCtx, wireRequest)
			decoded := mapping.FromPlatformStatusError(wireErr, semantic.CapabilityName)
			var failure *common.Failure
			if status.Code(wireErr) != codes.Aborted || !errors.As(decoded, &failure) || failure.Category != common.FailureConflict || failure.ConflictReason != test.reason {
				t.Fatalf("gRPC conflict = %v, decoded %#v", wireErr, decoded)
			}
		})
	}
	after, err := runtime.Engine.ReadWorkspace(ctx, "WorkItem")
	if err != nil {
		t.Fatal(err)
	}
	if after.Versions.WorkspaceVersion != current.Versions.WorkspaceVersion {
		t.Fatal("rejected mutations changed the layout aggregate")
	}
	var historyCount int
	if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM _System_PageLayoutHistory`).Scan(&historyCount); err != nil {
		t.Fatal(err)
	}
	if historyCount != 0 {
		t.Fatalf("rejected mutations wrote %d history rows", historyCount)
	}
}
