package automation

import (
	"context"
	"strings"

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

// RuntimeApprovalAction identifies the closed set of work-item mutations
// exposed by the neutral Runtime Approval adapter. It is an engine-private
// execution profile, not a wire contract or public product dialect.
type RuntimeApprovalAction string

const (
	RuntimeApprovalActionApprove  RuntimeApprovalAction = "approve"
	RuntimeApprovalActionReject   RuntimeApprovalAction = "reject"
	RuntimeApprovalActionReassign RuntimeApprovalAction = "reassign"
	RuntimeApprovalActionRecall   RuntimeApprovalAction = "recall"
)

// FenceRuntimeApprovalAction locks the current ProcessInstance first and the
// selected work item second, then revalidates state and authorization from
// those current rows. Ordinary queries in a pessimistic transaction are still
// snapshot reads; this explicit current-read fence prevents concurrent action
// requests with distinct idempotency keys from both consuming the same work
// item or acting after reassignment/recall.
func (s *ApprovalService) FenceRuntimeApprovalAction(
	ctx context.Context,
	workItemID string,
	binding ApprovalWorkItemBinding,
	operation RuntimeApprovalAction,
	user *models.UserSession,
) error {
	if s == nil || s.persistence == nil || user == nil || strings.TrimSpace(user.ID) == "" ||
		strings.TrimSpace(workItemID) == "" || strings.TrimSpace(binding.ProcessInstanceID) == "" ||
		strings.TrimSpace(binding.ProcessDefinitionID) == "" || strings.TrimSpace(binding.TargetRecordID) == "" {
		return apperrors.NewInternalError("Runtime Approval action fence is incomplete", nil)
	}
	ctx = constants.WithSecurityContext(ctx, constants.SystemContext, "ApprovalService: fence Runtime approval action")

	instance, err := s.persistence.GetLock(ctx, constants.TableProcessInstance, binding.ProcessInstanceID)
	if err != nil {
		return apperrors.NewInternalError("failed to lock Runtime Approval ProcessInstance", err)
	}
	if instance == nil {
		return apperrors.NewInternalError("Runtime Approval ProcessInstance is missing", nil)
	}
	if approvalStringValue(instance[constants.FieldID]) != binding.ProcessInstanceID ||
		approvalStringValue(instance["ProcessDefinitionId"]) != binding.ProcessDefinitionID ||
		approvalStringValue(instance["TargetObjectId"]) != binding.TargetRecordID {
		return apperrors.NewInternalError("Runtime Approval ProcessInstance binding changed", nil)
	}
	if approvalStringValue(instance["Status"]) != constants.ApprovalStatusPending {
		return ErrInstanceNotPending
	}

	workItem, err := s.persistence.GetLock(ctx, constants.TableProcessInstanceWorkitem, workItemID)
	if err != nil {
		return apperrors.NewInternalError("failed to lock Runtime Approval work item", err)
	}
	if workItem == nil || approvalStringValue(workItem[constants.FieldID]) != workItemID {
		return ErrWorkItemNotFound
	}
	if approvalStringValue(workItem["ProcessInstanceId"]) != binding.ProcessInstanceID {
		return apperrors.NewInternalError("Runtime Approval work item binding changed", nil)
	}

	switch operation {
	case RuntimeApprovalActionApprove, RuntimeApprovalActionReject, RuntimeApprovalActionReassign:
		if approvalStringValue(workItem["ActorId"]) != user.ID && !s.hasSuperUserCapability(ctx, user) {
			return ErrNotAuthorized
		}
	case RuntimeApprovalActionRecall:
		// Callers with the platform super-user capability may recall on behalf of
		// the submitter — the same override as the service-layer check in
		// Recall (approval_recall.go Step 4).
		if approvalStringValue(instance["SubmittedById"]) != user.ID && !s.hasSuperUserCapability(ctx, user) {
			return ErrNotSubmitter
		}
	default:
		return apperrors.NewInternalError("Runtime Approval action fence received an unsupported operation", nil)
	}
	return nil
}
