package runtimeapi

import (
	"context"
	"encoding/json"
	"errors"
	"strconv"
	"testing"
	"time"

	"github.com/nexuscrm/shared/pkg/constants"
	sharedcontract "github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-api-host/runtimegrpc"
	auditsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/auditlogv1"
	metadataadminsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/metadataadminreadv1"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
	"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/metadata"
	"google.golang.org/grpc/status"
)

type metadataAdminReadTestEngine struct {
	objects          []models.ObjectMetadata
	layouts          []*models.PageLayout
	assignments      []*models.LayoutAssignment
	stored           *models.PageLayout
	rules            []*models.ValidationRule
	objectsErr       error
	layoutsErr       error
	assignmentsErr   error
	storedErr        error
	rulesErr         error
	objectCalls      int
	layoutCalls      int
	assignmentCalls  int
	storedCalls      int
	ruleCalls        int
	workspaceObject  string
	assignmentObject string
	storedID         string
	ruleObject       string
}

func (engine *metadataAdminReadTestEngine) ListObjects(context.Context) ([]models.ObjectMetadata, error) {
	engine.objectCalls++
	return engine.objects, engine.objectsErr
}

func (engine *metadataAdminReadTestEngine) ListLayouts(_ context.Context, objectID string) ([]*models.PageLayout, error) {
	engine.layoutCalls++
	engine.workspaceObject = objectID
	return engine.layouts, engine.layoutsErr
}

func (engine *metadataAdminReadTestEngine) ListLayoutAssignments(_ context.Context, objectID string) ([]*models.LayoutAssignment, error) {
	engine.assignmentCalls++
	engine.assignmentObject = objectID
	return engine.assignments, engine.assignmentsErr
}

func (engine *metadataAdminReadTestEngine) GetLayoutForAuthoring(_ context.Context, layoutID string) (*models.PageLayout, error) {
	engine.storedCalls++
	engine.storedID = layoutID
	return engine.stored, engine.storedErr
}

func (engine *metadataAdminReadTestEngine) ListValidationRules(_ context.Context, objectID string) ([]*models.ValidationRule, error) {
	engine.ruleCalls++
	engine.ruleObject = objectID
	return engine.rules, engine.rulesErr
}

type metadataAdminReadTestPermissions struct {
	allowed bool
	calls   int
}

func (engine *metadataAdminReadTestPermissions) EffectivePermissions(
	context.Context,
	*models.UserSession,
	string,
	permissionstatev1.Version,
) (*sharedcontract.EffectivePermissionsResult, error) {
	engine.calls++
	return &sharedcontract.EffectivePermissionsResult{SystemPermissions: map[string]bool{
		string(constants.PermCustomizeApplication): engine.allowed,
	}}, nil
}

func TestMetadataAdminReadServerProjectsStoredConfigurationAndSorts(t *testing.T) {
	description := "Customer accounts"
	created := time.Date(2026, 8, 25, 4, 5, 6, 7, time.UTC)
	profile := "profile-1"
	field := "Amount"
	engine := &metadataAdminReadTestEngine{
		objects: []models.ObjectMetadata{
			{APIName: "Opportunity"},
			{APIName: "Account", Description: &description, DeleteMode: "soft"},
		},
		layouts: []*models.PageLayout{
			{ID: "layout-b", ObjectAPIName: "Account", LayoutName: "Secondary"},
			{ID: "layout-a", ObjectAPIName: "Account", LayoutName: "Primary", Type: "record", IsDefault: true, CreatedDate: created},
		},
		assignments: []*models.LayoutAssignment{
			{ID: "assignment-b", ObjectAPIName: "Account", LayoutID: "layout-b"},
			{ID: "assignment-a", ObjectAPIName: "Account", LayoutID: "layout-a", ProfileID: &profile, Specificity: 1, CreatedDate: created},
		},
		stored: &models.PageLayout{
			ID: "layout-a", ObjectAPIName: "Account", LayoutName: "Primary",
			Sections: []models.PageSection{{ID: "main"}},
		},
		rules: []*models.ValidationRule{
			{ID: "rule-b", ObjectAPIName: "Account", Name: "RequiresName", Condition: "ISBLANK(Name)", ErrorMessage: "Name is required"},
			{ID: "rule-a", ObjectAPIName: "account", Name: "PositiveAmount", Active: true,
				Condition: "Amount < 0", ErrorMessage: "Amount must be positive", ErrorDisplayField: &field,
				RecordSetBindings: []models.ValidationRuleRecordSetBinding{{
					Name: "hasOpenContact", Mode: "exists", ObjectAPIName: "Contact", FilterLogic: "1",
					Filters: []models.ValidationRuleRecordSetFilter{
						{Field: "AccountId", Operator: "eq", ValueSource: "record", Value: "Id"},
						{Field: "ExternalNumber", Operator: "eq", ValueSource: "literal", Value: json.Number("9007199254740993")},
					},
				}},
			},
		},
	}
	permissions := &metadataAdminReadTestPermissions{allowed: true}
	server, err := NewMetadataAdminReadServer(engine, permissions)
	if err != nil {
		t.Fatal(err)
	}
	ctx := metadataAdminReadTestContext(t)

	indexRequest := metadataadminsemantic.ObjectAdminIndexRequest{}
	wireIndexRequest, err := mapping.ToWireReadObjectAdminIndexRequest(ctx, indexRequest)
	if err != nil {
		t.Fatal(err)
	}
	wireIndex, err := server.ReadObjectAdminIndex(ctx, wireIndexRequest)
	if err != nil {
		t.Fatal(err)
	}
	index, err := mapping.FromWireReadObjectAdminIndexResponse(indexRequest, wireIndex)
	if err != nil {
		t.Fatal(err)
	}
	if len(index.Objects) != 2 || index.Objects[0].ObjectID != "Account" ||
		index.Objects[0].Description == nil || *index.Objects[0].Description != description ||
		index.Objects[1].ObjectID != "Opportunity" {
		t.Fatalf("object index = %#v", index)
	}

	workspaceRequest := metadataadminsemantic.LayoutAdminWorkspaceRequest{ObjectID: "Account"}
	wireWorkspaceRequest, err := mapping.ToWireReadLayoutAdminWorkspaceRequest(ctx, workspaceRequest)
	if err != nil {
		t.Fatal(err)
	}
	wireWorkspace, err := server.ReadLayoutAdminWorkspace(ctx, wireWorkspaceRequest)
	if err != nil {
		t.Fatal(err)
	}
	workspace, err := mapping.FromWireReadLayoutAdminWorkspaceResponse(workspaceRequest, wireWorkspace)
	if err != nil {
		t.Fatal(err)
	}
	if len(workspace.Layouts) != 2 || workspace.Layouts[0].LayoutID != "layout-a" ||
		!workspace.Layouts[0].IsDefault || workspace.Layouts[0].CreatedAt != created.Format(time.RFC3339Nano) ||
		len(workspace.Assignments) != 2 || workspace.Assignments[0].AssignmentID != "assignment-a" ||
		len(workspace.Assignments[0].Dimensions) != 1 ||
		workspace.Assignments[0].Dimensions[0] != (metadataadminsemantic.LayoutAssignmentDimension{Key: "profile", Value: profile}) {
		t.Fatalf("layout workspace = %#v", workspace)
	}

	storedRequest := metadataadminsemantic.StoredLayoutRequest{LayoutID: "layout-a"}
	wireStoredRequest, err := mapping.ToWireReadStoredLayoutRequest(ctx, storedRequest)
	if err != nil {
		t.Fatal(err)
	}
	wireStored, err := server.ReadStoredLayout(ctx, wireStoredRequest)
	if err != nil {
		t.Fatal(err)
	}
	stored, err := mapping.FromWireReadStoredLayoutResponse(storedRequest, wireStored)
	if err != nil {
		t.Fatal(err)
	}
	var document models.PageLayout
	if err := json.Unmarshal(stored.StoredDocument, &document); err != nil ||
		document.ID != "layout-a" || document.ObjectAPIName != "Account" ||
		len(document.Sections) != 1 || document.Sections[0].ID != "main" {
		t.Fatalf("stored layout = %#v, decoded = %#v, error = %v", stored, document, err)
	}
	ruleRequest := metadataadminsemantic.ValidationRuleAdminIndexRequest{ObjectID: "Account"}
	wireRuleRequest, err := mapping.ToWireReadValidationRuleAdminIndexRequest(ctx, ruleRequest)
	if err != nil {
		t.Fatal(err)
	}
	wireRules, err := server.ReadValidationRuleAdminIndex(ctx, wireRuleRequest)
	if err != nil {
		t.Fatal(err)
	}
	rules, err := mapping.FromWireReadValidationRuleAdminIndexResponse(ruleRequest, wireRules)
	if err != nil {
		t.Fatal(err)
	}
	if len(rules.Rules) != 2 || rules.Rules[0].RuleID != "rule-a" || rules.Rules[0].ObjectID != "Account" ||
		rules.Rules[0].ErrorDisplayField == nil || *rules.Rules[0].ErrorDisplayField != field ||
		len(rules.Rules[0].RecordSetBindings) != 1 || len(rules.Rules[0].RecordSetBindings[0].Filters) != 2 ||
		string(rules.Rules[0].RecordSetBindings[0].Filters[0].Value) != `"Id"` ||
		string(rules.Rules[0].RecordSetBindings[0].Filters[1].Value) != `9007199254740993` ||
		rules.Rules[1].RuleID != "rule-b" {
		t.Fatalf("validation-rule index = %#v", rules)
	}
	if permissions.calls != 4 || engine.objectCalls != 1 || engine.layoutCalls != 1 ||
		engine.assignmentCalls != 1 || engine.storedCalls != 1 ||
		engine.ruleCalls != 1 || engine.workspaceObject != "Account" || engine.assignmentObject != "Account" ||
		engine.storedID != "layout-a" || engine.ruleObject != "Account" {
		t.Fatalf("calls permissions/objects/layouts/assignments/stored/rules = %d/%d/%d/%d/%d/%d", permissions.calls,
			engine.objectCalls, engine.layoutCalls, engine.assignmentCalls, engine.storedCalls, engine.ruleCalls)
	}
}

func TestMetadataAdminReadServerDeniesEveryAdminReadBeforeEngineRead(t *testing.T) {
	ctx := metadataAdminReadTestContext(t)
	indexRequest, err := mapping.ToWireReadObjectAdminIndexRequest(
		ctx,
		metadataadminsemantic.ObjectAdminIndexRequest{},
	)
	if err != nil {
		t.Fatal(err)
	}
	workspaceRequest, err := mapping.ToWireReadLayoutAdminWorkspaceRequest(
		ctx,
		metadataadminsemantic.LayoutAdminWorkspaceRequest{ObjectID: "Account"},
	)
	if err != nil {
		t.Fatal(err)
	}
	storedRequest, err := mapping.ToWireReadStoredLayoutRequest(
		ctx,
		metadataadminsemantic.StoredLayoutRequest{LayoutID: "layout-a"},
	)
	if err != nil {
		t.Fatal(err)
	}
	ruleRequest, err := mapping.ToWireReadValidationRuleAdminIndexRequest(
		ctx,
		metadataadminsemantic.ValidationRuleAdminIndexRequest{ObjectID: "Account"},
	)
	if err != nil {
		t.Fatal(err)
	}
	tests := []struct {
		name string
		call func(*MetadataAdminReadServer) error
	}{
		{name: "object index", call: func(server *MetadataAdminReadServer) error {
			_, callErr := server.ReadObjectAdminIndex(ctx, indexRequest)
			return callErr
		}},
		{name: "layout workspace", call: func(server *MetadataAdminReadServer) error {
			_, callErr := server.ReadLayoutAdminWorkspace(ctx, workspaceRequest)
			return callErr
		}},
		{name: "stored layout", call: func(server *MetadataAdminReadServer) error {
			_, callErr := server.ReadStoredLayout(ctx, storedRequest)
			return callErr
		}},
		{name: "validation-rule index", call: func(server *MetadataAdminReadServer) error {
			_, callErr := server.ReadValidationRuleAdminIndex(ctx, ruleRequest)
			return callErr
		}},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			engine := &metadataAdminReadTestEngine{}
			server, err := NewMetadataAdminReadServer(engine, &metadataAdminReadTestPermissions{})
			if err != nil {
				t.Fatal(err)
			}
			if err := test.call(server); status.Code(err) != codes.PermissionDenied {
				t.Fatalf("status = %v, want PermissionDenied", err)
			}
			if engine.objectCalls != 0 || engine.layoutCalls != 0 ||
				engine.assignmentCalls != 0 || engine.storedCalls != 0 || engine.ruleCalls != 0 {
				t.Fatalf(
					"engine calls objects/layouts/assignments/stored/rules = %d/%d/%d/%d/%d, want all zero",
					engine.objectCalls, engine.layoutCalls, engine.assignmentCalls, engine.storedCalls, engine.ruleCalls,
				)
			}
		})
	}
}

func TestMetadataAdminReadServerRejectsCapabilityConfusionBeforeAuthorizationOrRead(t *testing.T) {
	parent := auditLogTestContext(t)
	trusted, ok := trustedCallFromContext(parent)
	if !ok {
		t.Fatal("missing trusted delegated call")
	}
	trusted.capabilities = []string{
		auditsemantic.CapabilityName,
		metadataadminsemantic.CapabilityName,
	}
	parent = context.WithValue(parent, trustedCallContextKey{}, trusted)
	ctx := metadataAdminReadHostContext(
		t,
		parent,
		runtimev1.AuditLogReadService_ReadAuditLog_FullMethodName,
		auditsemantic.CapabilityName,
	)
	request, err := mapping.ToWireReadObjectAdminIndexRequest(
		ctx,
		metadataadminsemantic.ObjectAdminIndexRequest{},
	)
	if err != nil {
		t.Fatal(err)
	}
	engine := &metadataAdminReadTestEngine{}
	permissions := &metadataAdminReadTestPermissions{allowed: true}
	server, err := NewMetadataAdminReadServer(engine, permissions)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := server.ReadObjectAdminIndex(ctx, request); status.Code(err) != codes.Unavailable {
		t.Fatalf("capability-confused status = %v, want Unavailable", err)
	}
	if permissions.calls != 0 || engine.objectCalls != 0 {
		t.Fatalf("permission/object calls = %d/%d, want 0/0", permissions.calls, engine.objectCalls)
	}
}

func TestMetadataAdminReadServerReportsWorkspaceFailureInsteadOfEmptySuccess(t *testing.T) {
	engine := &metadataAdminReadTestEngine{layoutsErr: errors.New("database password must not escape")}
	server, err := NewMetadataAdminReadServer(engine, &metadataAdminReadTestPermissions{allowed: true})
	if err != nil {
		t.Fatal(err)
	}
	ctx := metadataAdminReadTestContext(t)
	request, err := mapping.ToWireReadLayoutAdminWorkspaceRequest(ctx, metadataadminsemantic.LayoutAdminWorkspaceRequest{ObjectID: "Account"})
	if err != nil {
		t.Fatal(err)
	}
	if _, err := server.ReadLayoutAdminWorkspace(ctx, request); status.Code(err) != codes.Internal {
		t.Fatalf("status = %v, want Internal", err)
	}
	if engine.assignmentCalls != 0 {
		t.Fatalf("assignment calls = %d after layout failure, want zero", engine.assignmentCalls)
	}
}

func TestMetadataAdminReadServerReportsMissingStoredLayout(t *testing.T) {
	server, err := NewMetadataAdminReadServer(&metadataAdminReadTestEngine{}, &metadataAdminReadTestPermissions{allowed: true})
	if err != nil {
		t.Fatal(err)
	}
	ctx := metadataAdminReadTestContext(t)
	request, err := mapping.ToWireReadStoredLayoutRequest(ctx, metadataadminsemantic.StoredLayoutRequest{LayoutID: "missing"})
	if err != nil {
		t.Fatal(err)
	}
	if _, err := server.ReadStoredLayout(ctx, request); status.Code(err) != codes.NotFound {
		t.Fatalf("status = %v, want NotFound", err)
	}
}

func TestMetadataAdminReadServerRequiresEngineAndPermissions(t *testing.T) {
	if server, err := NewMetadataAdminReadServer(nil, &metadataAdminReadTestPermissions{}); err == nil || server != nil {
		t.Fatalf("nil engine = %#v, %v", server, err)
	}
	if server, err := NewMetadataAdminReadServer(&metadataAdminReadTestEngine{}, nil); err == nil || server != nil {
		t.Fatalf("nil permissions = %#v, %v", server, err)
	}
}

func metadataAdminReadTestContext(t *testing.T) context.Context {
	t.Helper()
	parent := auditLogTestContext(t)
	trusted, ok := trustedCallFromContext(parent)
	if !ok {
		t.Fatal("missing trusted delegated call")
	}
	trusted.capabilities = []string{metadataadminsemantic.CapabilityName}
	parent = context.WithValue(parent, trustedCallContextKey{}, trusted)
	return metadataAdminReadHostContext(
		t,
		parent,
		runtimev1.MetadataAdminReadService_ReadObjectAdminIndex_FullMethodName,
		metadataadminsemantic.CapabilityName,
	)
}

func metadataAdminReadHostContext(
	t *testing.T,
	parent context.Context,
	fullMethod string,
	capability string,
) context.Context {
	t.Helper()
	ctx := metadata.NewIncomingContext(parent, metadata.Pairs(
		envelope.ProtocolMajorMetadataKey, strconv.FormatUint(uint64(metadataadminsemantic.ProtocolMajor), 10),
		envelope.ProtocolMinorMetadataKey, strconv.FormatUint(uint64(metadataadminsemantic.ProtocolMinor), 10),
		envelope.CapabilityMetadataKey, capability,
	))
	ctx, cancel := context.WithTimeout(ctx, time.Second)
	t.Cleanup(cancel)
	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(
		capability,
		time.Second,
	), runtimegrpc.WithResourceClassAdmissionLimit(
		runtimegrpc.ResourceClassHeavyWork,
		1,
	))
	if err != nil {
		t.Fatal(err)
	}
	var admitted context.Context
	info := &grpc.UnaryServerInfo{FullMethod: fullMethod}
	_, err = policy.UnaryServerInterceptor()(ctx, nil, info, func(ctx context.Context, _ any) (any, error) {
		admitted = ctx
		return struct{}{}, nil
	})
	if err != nil {
		t.Fatal(err)
	}
	if admitted == nil {
		t.Fatal("host policy did not admit test context")
	}
	return admitted
}
