package records

import (
	"context"
	"errors"
	"strings"
	"testing"

	eventFeature "github.com/nexuscrm/events"
	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/formula"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/kernel/pkg/validation"
	metadataFeature "github.com/nexuscrm/metadata"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	sharedEvents "github.com/nexuscrm/shared/pkg/events"
	"github.com/nexuscrm/shared/pkg/models"
)

const (
	sharingRuleOwnerReferenceWriteValidationCapabilityForTest = "platform.security.write.sharing-rule-owner-references"
	sharingRuleShapeWriteValidationCapabilityForTest          = "platform.security.write.sharing-rule-shape"
	sharingRuleCanonicalizationCapabilityForTest              = "platform.security.write.sharing-rule-canonicalization"
)

type sharingRuleEntrypointRepo struct {
	captureRecordRepo
	findOneFn func(ctx context.Context, t string, id string) (models.SObject, error)
}

func (r *sharingRuleEntrypointRepo) FindOne(ctx context.Context, t string, id string) (models.SObject, error) {
	if r.findOneFn != nil {
		return r.findOneFn(ctx, t, id)
	}
	return nil, nil
}

func sharingRuleEntrypointSchema() *models.ObjectMetadata {
	schema := &models.ObjectMetadata{
		ID:           "obj_sharing_rule",
		APIName:      constants.TableSharingRule,
		Label:        "Sharing Rule",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, Createable: false, Updateable: false},
			{APIName: constants.FieldSharingRule_Name, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldSharingRule_ObjectAPIName, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldSharingRule_AccessLevel, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldSharingRule_Criteria, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldSharingRule_SharedFromGroupID, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldSharingRule_ShareWithGroupID, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldSharingRule_SharedFromRoleID, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldSharingRule_ShareWithRoleID, Type: constants.FieldTypeString, Createable: true, Updateable: true},
		},
	}
	schema.AccessPolicy = &models.ObjectAccessPolicy{
		WriteValidation: &models.RecordWriteValidationAccessPolicy{
			Canonicalizers: []models.RecordWriteCanonicalizationPolicy{
				{Phase: string(contract.RecordWriteValidationBeforeCreate), Capability: sharingRuleCanonicalizationCapabilityForTest},
				{Phase: string(contract.RecordWriteValidationBeforeUpdate), Capability: sharingRuleCanonicalizationCapabilityForTest},
				{Phase: string(contract.RecordWriteValidationBeforeRestore), Capability: sharingRuleCanonicalizationCapabilityForTest},
			},
			Validators: []models.RecordWriteValidationPolicy{
				{Phase: string(contract.RecordWriteValidationBeforeCreate), Capability: sharingRuleOwnerReferenceWriteValidationCapabilityForTest},
				{Phase: string(contract.RecordWriteValidationBeforeCreate), Capability: sharingRuleShapeWriteValidationCapabilityForTest},
				{Phase: string(contract.RecordWriteValidationBeforeUpdate), Capability: sharingRuleOwnerReferenceWriteValidationCapabilityForTest},
				{Phase: string(contract.RecordWriteValidationBeforeUpdate), Capability: sharingRuleShapeWriteValidationCapabilityForTest},
			},
		},
	}
	schema.FieldsMap = make(map[string]*models.FieldMetadata, len(schema.Fields))
	for i := range schema.Fields {
		schema.FieldsMap[strings.ToLower(schema.Fields[i].APIName)] = &schema.Fields[i]
	}
	return schema
}

func newSharingRuleEntrypointTestService(schema *models.ObjectMetadata, repo *sharingRuleEntrypointRepo, eventBus ports.EventPublisher) *PersistenceService {
	metaRepo := &stubUpsertMetaRepo{schema: schema}
	metaSvc := metadataFeature.NewMetadataService(metaRepo, nil, nil)
	permSvc := permissions.NewPermissionService(&stubUpsertPermRepo{grantCreate: true, grantEdit: true}, metaSvc, nil)
	ps := &PersistenceService{
		repo:                repo,
		rollup:              noopRollupProcessor{},
		metadata:            metaSvc,
		permissions:         permSvc,
		eventBus:            eventBus,
		validator:           metadataFeature.NewValidationService(formula.NewEngine()),
		schemaValidator:     validation.NewSchemaValidator(),
		txManager:           inlineTxManager{},
		writeCanonicalizers: newRecordWriteCanonicalizers(),
	}
	ps.registerBuiltInSecurityWriteCanonicalizers()
	ps.registerBuiltInSecurityWriteValidators()
	return ps
}

func TestInsertSharingRuleBeforeSaveFlowCanClearInvalidOwnerReference(t *testing.T) {
	repo := &sharingRuleEntrypointRepo{
		findOneFn: func(ctx context.Context, t string, id string) (models.SObject, error) {
			if t == constants.TableGroup && id == "group_existing_target" {
				return models.SObject{constants.FieldID: id}, nil
			}
			return nil, nil
		},
	}
	ps := newSharingRuleEntrypointTestService(sharingRuleEntrypointSchema(), repo, mutatingRecordEventBus{
		mutate: func(eventType sharedEvents.EventType, record models.SObject) {
			if eventType == sharedEvents.RecordBeforeCreate {
				delete(record, constants.FieldSharingRule_SharedFromGroupID)
				record[constants.FieldSharingRule_Criteria] = `[{"field":"Name","op":"=","value":"Acme"}]`
			}
		},
	})

	_, err := ps.Insert(context.Background(), constants.TableSharingRule, models.SObject{
		constants.FieldSharingRule_Name:              "qa_owner_group_rule",
		constants.FieldSharingRule_ObjectAPIName:     "Account",
		constants.FieldSharingRule_AccessLevel:       "Read",
		constants.FieldSharingRule_SharedFromGroupID: "group_all_internal_users",
		constants.FieldSharingRule_ShareWithGroupID:  "group_existing_target",
	}, recordEntrypointUser())
	if err != nil {
		t.Fatalf("Insert: %v", err)
	}
	if _, exists := repo.inserted[constants.FieldSharingRule_SharedFromGroupID]; exists {
		t.Fatalf("inserted record still contains %s: %#v", constants.FieldSharingRule_SharedFromGroupID, repo.inserted)
	}
}

func TestInsertSharingRuleBeforeSaveFlowRejectsInvalidFinalShape(t *testing.T) {
	repo := &sharingRuleEntrypointRepo{
		findOneFn: func(ctx context.Context, t string, id string) (models.SObject, error) {
			if t == constants.TableGroup && id == "group_existing_target" {
				return models.SObject{constants.FieldID: id}, nil
			}
			return nil, nil
		},
	}
	ps := newSharingRuleEntrypointTestService(sharingRuleEntrypointSchema(), repo, mutatingRecordEventBus{
		mutate: func(eventType sharedEvents.EventType, record models.SObject) {
			if eventType == sharedEvents.RecordBeforeCreate {
				delete(record, constants.FieldSharingRule_SharedFromGroupID)
			}
		},
	})

	_, err := ps.Insert(context.Background(), constants.TableSharingRule, models.SObject{
		constants.FieldSharingRule_Name:              "qa_owner_group_rule",
		constants.FieldSharingRule_ObjectAPIName:     "Account",
		constants.FieldSharingRule_AccessLevel:       "Read",
		constants.FieldSharingRule_SharedFromGroupID: "group_all_internal_users",
		constants.FieldSharingRule_ShareWithGroupID:  "group_existing_target",
	}, recordEntrypointUser())
	if err == nil {
		t.Fatal("Insert = nil, want final SharingRule shape validation error")
	}
	if !strings.Contains(err.Error(), "owner-based sharing rules must define exactly one SharedFromRoleId or SharedFromGroupId") {
		t.Fatalf("Insert error = %v, want final SharedFrom validation", err)
	}
	if repo.inserted != nil {
		t.Fatalf("Insert persisted invalid final SharingRule shape: %#v", repo.inserted)
	}
}

func TestInsertSharingRuleShapeFailsClosedOnDeclaredCapability(t *testing.T) {
	schema := sharingRuleEntrypointSchema()
	validators := schema.AccessPolicy.WriteValidation.Validators[:0]
	for _, validator := range schema.AccessPolicy.WriteValidation.Validators {
		if validator.Capability == sharingRuleShapeWriteValidationCapabilityForTest {
			continue
		}
		validators = append(validators, validator)
	}
	schema.AccessPolicy.WriteValidation.Validators = validators

	repo := &sharingRuleEntrypointRepo{
		findOneFn: func(ctx context.Context, t string, id string) (models.SObject, error) {
			if t == constants.TableGroup && id == "group_existing_target" {
				return models.SObject{constants.FieldID: id}, nil
			}
			return nil, nil
		},
	}
	ps := newSharingRuleEntrypointTestService(schema, repo, &eventFeature.NoOpEventBus{})

	_, err := ps.Insert(context.Background(), constants.TableSharingRule, models.SObject{
		constants.FieldSharingRule_Name:             "qa_owner_group_rule",
		constants.FieldSharingRule_ObjectAPIName:    "Account",
		constants.FieldSharingRule_AccessLevel:      "Read",
		constants.FieldSharingRule_ShareWithGroupID: "group_existing_target",
	}, recordEntrypointUser())
	if err == nil {
		t.Fatal("Insert = nil, want missing SharingRule shape capability metadata error")
	}
	if !strings.Contains(err.Error(), "missing record write validation metadata") ||
		!strings.Contains(err.Error(), sharingRuleShapeWriteValidationCapabilityForTest) {
		t.Fatalf("Insert error = %v, want missing SharingRule shape capability metadata", err)
	}
	if repo.inserted != nil {
		t.Fatalf("Insert persisted SharingRule after missing shape capability: %#v", repo.inserted)
	}
}

func TestUpdateSharingRuleRejectsMissingOwnerReference(t *testing.T) {
	repo := &sharingRuleEntrypointRepo{
		captureRecordRepo: captureRecordRepo{
			old: models.SObject{
				constants.FieldID:                           "0JR000000000001",
				constants.FieldSharingRule_Name:             "existing_rule",
				constants.FieldSharingRule_ObjectAPIName:    "Account",
				constants.FieldSharingRule_AccessLevel:      "Read",
				constants.FieldSharingRule_ShareWithGroupID: "group_existing_target",
			},
		},
		findOneFn: func(ctx context.Context, t string, id string) (models.SObject, error) {
			if t == constants.TableGroup && id == "group_existing_target" {
				return models.SObject{constants.FieldID: id}, nil
			}
			return nil, nil
		},
	}
	ps := newSharingRuleEntrypointTestService(sharingRuleEntrypointSchema(), repo, &eventFeature.NoOpEventBus{})

	err := ps.Update(context.Background(), constants.TableSharingRule, "0JR000000000001", models.SObject{
		constants.FieldSharingRule_SharedFromGroupID: "group_all_internal_users",
	}, recordEntrypointUser())
	if err == nil {
		t.Fatal("Update = nil, want INVALID_CROSS_REFERENCE_KEY")
	}

	// The neutral *DomainWireError must survive the full Update lifecycle
	// (event-bus wrapping etc.) and still be unwrappable; the boundary
	// dialect maps WireInvalidReference to the v63 INVALID_CROSS_REFERENCE_KEY
	// at HTTP 400 (covered by the dialect's wire_domain_mapping golden).
	var domainErr *appErrors.DomainWireError
	if !errors.As(err, &domainErr) {
		t.Fatalf("error type = %T (%v), want *appErrors.DomainWireError", err, err)
	}
	if domainErr.Kind != appErrors.WireInvalidReference {
		t.Fatalf("kind = %q, want %q", domainErr.Kind, appErrors.WireInvalidReference)
	}
	if repo.updated != nil {
		t.Fatalf("unexpected repo.Update call with %#v", repo.updated)
	}
}

func TestUpdateSharingRuleBeforeSaveFlowClearsOwnerReferenceInSQLDiff(t *testing.T) {
	repo := &sharingRuleEntrypointRepo{
		captureRecordRepo: captureRecordRepo{
			old: models.SObject{
				constants.FieldID:                            "0JR000000000001",
				constants.FieldSharingRule_Name:              "existing_rule",
				constants.FieldSharingRule_ObjectAPIName:     "Account",
				constants.FieldSharingRule_AccessLevel:       "Read",
				constants.FieldSharingRule_SharedFromGroupID: "group_all_internal_users",
				constants.FieldSharingRule_ShareWithGroupID:  "group_existing_target",
			},
		},
		findOneFn: func(ctx context.Context, t string, id string) (models.SObject, error) {
			if t == constants.TableGroup && id == "group_existing_target" {
				return models.SObject{constants.FieldID: id}, nil
			}
			return nil, nil
		},
	}
	ps := newSharingRuleEntrypointTestService(sharingRuleEntrypointSchema(), repo, mutatingRecordEventBus{
		mutate: func(eventType sharedEvents.EventType, record models.SObject) {
			if eventType == sharedEvents.RecordBeforeUpdate {
				delete(record, constants.FieldSharingRule_SharedFromGroupID)
				record[constants.FieldSharingRule_Criteria] = `[{"field":"Name","op":"=","value":"Acme"}]`
			}
		},
	})

	err := ps.Update(context.Background(), constants.TableSharingRule, "0JR000000000001", models.SObject{
		constants.FieldSharingRule_AccessLevel: "Edit",
	}, recordEntrypointUser())
	if err != nil {
		t.Fatalf("Update: %v", err)
	}
	if repo.updated == nil {
		t.Fatal("Update did not reach repo.Update")
	}
	if got, ok := repo.updated[constants.FieldSharingRule_SharedFromGroupID]; !ok || got != nil {
		t.Fatalf("updated %s = %#v (present=%v), want explicit nil clear; updates=%#v", constants.FieldSharingRule_SharedFromGroupID, got, ok, repo.updated)
	}
	if got := repo.updated[constants.FieldSharingRule_Criteria]; got != `[{"field":"Name","op":"=","value":"Acme"}]` {
		t.Fatalf("updated %s = %#v, want criteria repair; updates=%#v", constants.FieldSharingRule_Criteria, got, repo.updated)
	}
}

func TestPreviewBeforeUpdateSharingRuleRejectsInvalidFinalShape(t *testing.T) {
	repo := &sharingRuleEntrypointRepo{
		captureRecordRepo: captureRecordRepo{
			old: models.SObject{
				constants.FieldID:                            "0JR000000000001",
				constants.FieldSharingRule_Name:              "existing_rule",
				constants.FieldSharingRule_ObjectAPIName:     "Account",
				constants.FieldSharingRule_AccessLevel:       "Read",
				constants.FieldSharingRule_SharedFromGroupID: "group_all_internal_users",
				constants.FieldSharingRule_ShareWithGroupID:  "group_existing_target",
			},
		},
		findOneFn: func(ctx context.Context, t string, id string) (models.SObject, error) {
			if t == constants.TableGroup && id == "group_existing_target" {
				return models.SObject{constants.FieldID: id}, nil
			}
			return nil, nil
		},
	}
	ps := newSharingRuleEntrypointTestService(sharingRuleEntrypointSchema(), repo, mutatingRecordEventBus{
		mutate: func(eventType sharedEvents.EventType, record models.SObject) {
			if eventType == sharedEvents.RecordBeforeUpdate {
				delete(record, constants.FieldSharingRule_SharedFromGroupID)
			}
		},
	})

	prepared, err := ps.PreviewBeforeUpdate(context.Background(), constants.TableSharingRule, "0JR000000000001", models.SObject{
		constants.FieldSharingRule_AccessLevel: "Edit",
	}, nil, recordEntrypointUser())
	if err == nil {
		t.Fatal("PreviewBeforeUpdate = nil error, want final SharingRule shape validation error")
	}
	if !strings.Contains(err.Error(), "owner-based sharing rules must define exactly one SharedFromRoleId or SharedFromGroupId") {
		t.Fatalf("PreviewBeforeUpdate error = %v, want final SharedFrom validation", err)
	}
	if prepared != nil {
		t.Fatalf("PreviewBeforeUpdate prepared = %#v, want nil on invalid final shape", prepared)
	}
}

func TestPreviewBeforeUpdateSharingRuleClearRepairMaterializesSQLDiff(t *testing.T) {
	repo := &sharingRuleEntrypointRepo{
		captureRecordRepo: captureRecordRepo{
			old: models.SObject{
				constants.FieldID:                            "0JR000000000001",
				constants.FieldSharingRule_Name:              "existing_rule",
				constants.FieldSharingRule_ObjectAPIName:     "Account",
				constants.FieldSharingRule_AccessLevel:       "Read",
				constants.FieldSharingRule_SharedFromGroupID: "group_all_internal_users",
				constants.FieldSharingRule_ShareWithGroupID:  "group_existing_target",
			},
		},
		findOneFn: func(ctx context.Context, t string, id string) (models.SObject, error) {
			if t == constants.TableGroup && id == "group_existing_target" {
				return models.SObject{constants.FieldID: id}, nil
			}
			return nil, nil
		},
	}
	ps := newSharingRuleEntrypointTestService(sharingRuleEntrypointSchema(), repo, mutatingRecordEventBus{
		mutate: func(eventType sharedEvents.EventType, record models.SObject) {
			if eventType == sharedEvents.RecordBeforeUpdate {
				delete(record, constants.FieldSharingRule_SharedFromGroupID)
				record[constants.FieldSharingRule_Criteria] = `[{"field":"Name","op":"=","value":"Acme"}]`
			}
		},
	})

	prepared, err := ps.PreviewBeforeUpdate(context.Background(), constants.TableSharingRule, "0JR000000000001", models.SObject{
		constants.FieldSharingRule_AccessLevel: "Edit",
	}, nil, recordEntrypointUser())
	if err != nil {
		t.Fatalf("PreviewBeforeUpdate: %v", err)
	}
	if prepared == nil {
		t.Fatal("PreviewBeforeUpdate returned nil prepared update")
	}
	if got, ok := prepared.EffectiveUpdates[constants.FieldSharingRule_SharedFromGroupID]; !ok || got != nil {
		t.Fatalf("prepared update %s = %#v (present=%v), want explicit nil clear; prepared=%#v", constants.FieldSharingRule_SharedFromGroupID, got, ok, prepared.EffectiveUpdates)
	}
	if got := prepared.EffectiveUpdates[constants.FieldSharingRule_Criteria]; got != `[{"field":"Name","op":"=","value":"Acme"}]` {
		t.Fatalf("prepared update %s = %#v, want criteria repair; prepared=%#v", constants.FieldSharingRule_Criteria, got, prepared.EffectiveUpdates)
	}
}
