package permissions

import (
	"context"

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

type fieldAccessOperation uint8

const (
	fieldAccessRead fieldAccessOperation = iota
	fieldAccessEdit
	fieldAccessCreate
)

// fieldAccessEvaluation is the single interpreter for field authorization.
// Point checks supply one repository input; projections supply the same inputs
// in a batch. An absent row is different from an explicit false/false row.
// Metadata restrictions and derived policies precede stored grants in both paths.
// This evaluation is local to one object, actor and record; it is not a cache
// of authority across requests or a second metadata registry.
type fieldAccessEvaluation struct {
	service         *PermissionService
	object          string
	schema          *models.ObjectMetadata
	record          models.Record
	user            *models.UserSession
	loadPermission  func(context.Context, string) (*models.FieldPermissions, error)
	super           *bool
	objectDecisions map[string]bool
}

func (ps *PermissionService) newFieldAccessEvaluation(ctx context.Context, object string, schema *models.ObjectMetadata, record models.Record, user *models.UserSession) *fieldAccessEvaluation {
	return &fieldAccessEvaluation{
		service: ps, object: object, schema: schema, record: record, user: user,
		loadPermission: func(ctx context.Context, field string) (*models.FieldPermissions, error) {
			return ps.loadEffectiveFieldPermission(ctx, user, object, field)
		},
	}
}

func (e *fieldAccessEvaluation) objectAllowed(ctx context.Context, operation string) bool {
	if value, found := e.objectDecisions[operation]; found {
		return value
	}
	value := e.service.checkObjectPermissionWithSchemaState(ctx, e.object, e.schema, operation, e.user, newAccessPolicyEvalState())
	if e.objectDecisions == nil {
		e.objectDecisions = make(map[string]bool)
	}
	e.objectDecisions[operation] = value
	return value
}

func (e *fieldAccessEvaluation) resolve(ctx context.Context, name string, field *models.FieldMetadata, operation fieldAccessOperation) (bool, error) {
	if ctx != nil {
		if err := ctx.Err(); err != nil {
			return false, err
		}
	}
	if e.user == nil {
		return false, nil
	}
	if operation == fieldAccessRead && FieldReadBlockedByMetadata(field) {
		return false, nil
	}
	if operation == fieldAccessEdit && isFieldSystemReadOnlyByName(name) {
		return false, nil
	}
	if e.super == nil {
		granted := e.service.HasSuperUserCapability(ctx, e.user)
		e.super = &granted
	}
	if *e.super {
		return true, nil
	}
	if e.schema != nil && e.schema.UsesDerivedAccessPolicy() {
		if operation == fieldAccessCreate {
			// The caller separately proves object Create and intrinsic createability.
			// Derived objects do not own self FLS rows, including on insert.
			return true, nil
		}
		policyOperation := "fieldRead"
		record := models.Record(nil)
		if operation == fieldAccessEdit {
			policyOperation = "fieldEdit"
			record = e.record
		}
		return e.service.evaluateObjectAccessPolicy(ctx, e.schema, record, policyOperation, e.user, newAccessPolicyEvalState())
	}
	permission, err := e.loadPermission(ctx, name)
	if err != nil {
		return false, err
	}
	if permission != nil {
		if operation == fieldAccessRead {
			return permission.PermissionsRead, nil
		}
		return permission.PermissionsEdit, nil
	}
	if ctx != nil {
		if err := ctx.Err(); err != nil {
			return false, err
		}
	}
	switch operation {
	case fieldAccessRead:
		if constants.ScopedInternalAccessIgnoresImplicitFieldDenials(ctx, e.object, constants.PermRead) {
			return true, nil
		}
		return e.objectAllowed(ctx, constants.PermRead), nil
	case fieldAccessEdit:
		return e.objectAllowed(ctx, constants.PermEdit), nil
	case fieldAccessCreate:
		// Absence keeps the established insert fallback: object Create was checked
		// by the caller; object Edit is not required to insert a field value.
		return true, nil
	default:
		return false, nil
	}
}
