package records

import (
	"context"
	"errors"
	"testing"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

type updateFLSBoundedPermRepo struct {
	stubUpsertPermRepo
	fieldPerm  *models.FieldPermissions
	fieldErr   error
	objectLoad int
	pointLoad  int
}

func (r *updateFLSBoundedPermRepo) LoadEffectiveFieldPermission(context.Context, *models.UserSession, string, string) (*models.FieldPermissions, error) {
	r.pointLoad++
	return r.fieldPerm, r.fieldErr
}

func (r *updateFLSBoundedPermRepo) ListEffectiveFieldPermissionsForUserObject(context.Context, string, string, string) ([]models.FieldPermissions, error) {
	r.objectLoad++
	if r.fieldErr != nil {
		return nil, r.fieldErr
	}
	if r.fieldPerm == nil {
		return nil, nil
	}
	return []models.FieldPermissions{*r.fieldPerm}, nil
}

func TestFilterEditableFieldsBoundedProjectionUsesRepositoryDeny(t *testing.T) {
	ctx := permissions.WithAccessInputCache(context.Background())
	user := &models.UserSession{ID: "user-1", ProfileID: "profile-1"}
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: "Secret__c", Type: constants.FieldTypeString, Updateable: true},
		},
	}
	permRepo := &updateFLSBoundedPermRepo{
		stubUpsertPermRepo: stubUpsertPermRepo{grantEdit: true},
		fieldPerm: &models.FieldPermissions{
			SobjectType:     "Account",
			Field:           "Secret__c",
			PermissionsRead: true,
			PermissionsEdit: false,
		},
	}
	permSvc := permissions.NewPermissionService(permRepo, nil, nil)
	ps := &PersistenceService{permissions: permSvc}

	effectiveUpdates, hasChanges, err := ps.filterEditableFields(
		ctx,
		"Account",
		schema,
		models.SObject{"Secret__c": "new-secret"},
		models.SObject{constants.FieldID: "rec-1", "Secret__c": "old-secret"},
		user,
	)

	if _, ok := appErrors.AsFLSDenied(err); !ok {
		t.Fatalf("filterEditableFields error = %T %[1]v, want FLSDeniedError", err)
	}
	if hasChanges {
		t.Fatal("hasChanges = true for FLS-denied update; want false")
	}
	if effectiveUpdates != nil {
		t.Fatalf("effectiveUpdates = %#v, want nil for denied write", effectiveUpdates)
	}
	if permRepo.objectLoad != 1 || permRepo.pointLoad != 0 {
		t.Fatalf("permission loads = object %d, point %d; want 1,0", permRepo.objectLoad, permRepo.pointLoad)
	}
}

func TestRecordsWritesPropagateBoundedFLSAuthorityFailure(t *testing.T) {
	authorityErr := errors.New("permission repository unavailable")
	user := &models.UserSession{ID: "user-1", ProfileID: "profile-1"}
	schema := &models.ObjectMetadata{
		APIName: "Account",
		Fields:  []models.FieldMetadata{{APIName: "Name", Createable: true, Updateable: true}},
	}
	permRepo := &updateFLSBoundedPermRepo{
		stubUpsertPermRepo: stubUpsertPermRepo{grantEdit: true, grantCreate: true},
		fieldErr:           authorityErr,
	}
	permSvc := permissions.NewPermissionService(permRepo, nil, nil)
	ps := &PersistenceService{permissions: permSvc}

	updates, changed, err := ps.filterEditableFields(
		permissions.WithAccessInputCache(context.Background()),
		"Account",
		schema,
		models.SObject{"Name": "new"},
		models.SObject{constants.FieldID: "account-1", "Name": "old"},
		user,
	)
	if updates != nil || changed {
		t.Fatalf("update projection = %#v, changed %v; want no partial write", updates, changed)
	}
	if !errors.Is(err, authorityErr) {
		t.Fatalf("update error = %T %[1]v, want permission authority error", err)
	}
	if _, denied := appErrors.AsFLSDenied(err); denied {
		t.Fatalf("update authority error was converted to FLS denial: %v", err)
	}

	created, err := ps.pruneNonCreateableFields(
		permissions.WithAccessInputCache(context.Background()),
		"Account",
		models.SObject{"Name": "new"},
		schema,
		user,
	)
	if created != nil {
		t.Fatalf("create projection = %#v, want no partial write", created)
	}
	if !errors.Is(err, authorityErr) {
		t.Fatalf("create error = %T %[1]v, want permission authority error", err)
	}
	if _, denied := appErrors.AsFLSDenied(err); denied {
		t.Fatalf("create authority error was converted to FLS denial: %v", err)
	}
	if permRepo.objectLoad != 2 || permRepo.pointLoad != 0 {
		t.Fatalf("permission loads = object %d, point %d; want one per request and no point fallback", permRepo.objectLoad, permRepo.pointLoad)
	}
}
