package permissions

import (
	"context"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/fieldcapabilities"
	"github.com/nexuscrm/shared/pkg/models"
)

// FieldAccessResolver computes effective field capability by combining
// intrinsic capability (from _System_Field.Createable/Updateable) with runtime permissions.
//
// This is the ONE place where permissions are applied to field capabilities.
// All consumers call the resolver; none re-derive editability logic.
type FieldAccessResolver struct {
	permSvc *PermissionService
}

// NewFieldAccessResolver creates a resolver backed by the given permission service.
func NewFieldAccessResolver(permSvc *PermissionService) *FieldAccessResolver {
	return &FieldAccessResolver{permSvc: permSvc}
}

// IsIntrinsicallyCreateable returns true if the field is intrinsically
// createable (not a formula, autoNumber, system read-only, etc.). No
// permissions check. Callers that need to distinguish "silently drop"
// (intrinsic non-createable) from "hard-fail" (user-FLS denial) should
// gate on this before calling EffectiveCreateable.
func (r *FieldAccessResolver) IsIntrinsicallyCreateable(
	objectAPIName string, f *models.FieldMetadata,
) bool {
	c, _ := fieldcapabilities.ComputeIntrinsic(objectAPIName, f)
	return c
}

// EffectiveCreateable returns true if the field can be set by this user when creating a record.
// = intrinsic Createable (via ComputeIntrinsic) AND object Create permission AND field Edit permission.
func (r *FieldAccessResolver) EffectiveCreateable(
	ctx context.Context, f *models.FieldMetadata,
	objectAPIName string, user *models.UserSession,
) bool {
	// Intrinsic check via ComputeIntrinsic — authoritative source, works even
	// when stored f.Createable hasn't been backfilled (Go zero value = false).
	if !r.IsIntrinsicallyCreateable(objectAPIName, f) {
		return false
	}
	if !r.permSvc.CheckObjectPermissionWithUser(ctx, objectAPIName, constants.PermCreate, user) {
		return false
	}
	return r.checkFieldEditWithCreateFallback(ctx, objectAPIName, f.APIName, user)
}

// checkFieldEditWithCreateFallback checks field-level edit permission.
// Unlike CheckFieldEditabilityWithUser, the fallback is true (object Create already verified)
// rather than object PermEdit. This prevents a user with Create-but-not-Edit from being denied.
func (r *FieldAccessResolver) checkFieldEditWithCreateFallback(
	ctx context.Context, objectAPIName, fieldAPIName string, user *models.UserSession,
) bool {
	if user == nil {
		return false
	}
	if user.IsSystemAdmin || constants.IsSuperUser(user.ProfileID) {
		return true
	}
	if r.permSvc != nil && r.permSvc.metadata != nil {
		if schema := r.permSvc.metadata.GetSchema(ctx, objectAPIName); schema != nil && schema.UsesDerivedAccessPolicy() {
			// Derived/composite objects do not own self FieldPermissions rows.
			// Once objectCreate passes, create-time field gating comes only from
			// intrinsic field capability plus the metadata-authored access policy.
			return true
		}
	}
	// Check explicit field-level permission
	perm, err := r.permSvc.loadEffectiveFieldPermission(ctx, user, objectAPIName, fieldAPIName)
	if err != nil {
		return false
	}
	if perm != nil {
		return perm.PermissionsEdit // explicit FLS: respect edit flag
	}
	// No explicit FLS row — object Create permission already verified by caller
	return true
}

// EffectiveUpdateable returns true if the field can be modified by this user when editing a record.
// = intrinsic Updateable (via ComputeIntrinsic) AND object Edit permission AND field Edit permission.
func (r *FieldAccessResolver) EffectiveUpdateable(
	ctx context.Context, f *models.FieldMetadata,
	objectAPIName string, user *models.UserSession,
) bool {
	_, u := fieldcapabilities.ComputeIntrinsic(objectAPIName, f)
	if !u {
		return false
	}
	if !r.permSvc.CheckObjectPermissionWithUser(ctx, objectAPIName, constants.PermEdit, user) {
		return false
	}
	return r.permSvc.CheckFieldEditabilityWithUser(ctx, objectAPIName, f.APIName, user)
}
