package automation

import (
	"context"
	"strings"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/formula"
	"github.com/nexuscrm/kernel/pkg/logging"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/approvalir"
)

// Domain errors for approval operations (typed)
var (
	ErrNoActiveApprovalProcess = apperrors.NewNotFoundError("approval process", "")
	ErrRecordAlreadyPending    = apperrors.NewConflictError("approval", "status", "pending")
	ErrWorkItemNotFound        = apperrors.NewNotFoundError("work item", "")
	ErrNotAuthorized           = apperrors.NewPermissionError("approve/reject", "approval work item")
	ErrNotSubmitter            = apperrors.NewPermissionError("recall", "approval — only the submitter can recall")
	ErrRecallNotAllowed        = apperrors.NewPermissionError("recall", "approval process does not allow recall")
	ErrSubmitPermissionDenied  = apperrors.NewPermissionError("submit", "approval record")
	ErrInstanceNotPending      = apperrors.NewConflictError("approval instance", "status", "not pending")
	ErrEntryCriteriaNotMet     = apperrors.NewValidationError("entryCriteria", "record does not meet the entry criteria for this approval process")
	ErrDelegationNotAllowed    = apperrors.NewPermissionError("reassign", "delegation not allowed for this approval step")
)

// ApprovalService handles approval workflow management
type ApprovalService struct {
	persistence               PersistenceProvider
	query                     ports.QueryService
	permissions               *permissions.PermissionService
	flowExecutor              approvalFlowExecutor
	interviews                *FlowInterviewService
	actions                   *ActionService
	schema                    SchemaLookup
	logger                    *logging.Logger
	formulaEngine             *formula.Engine
	replayOutbox              ApprovalActionReplayStore
	submitLineage             approvalSubmitLineageStore
	approvalDefinitionDecoder approvalir.DefinitionDecoder
	approvalQueryOnlyOwners   map[string]struct{}
	actionLog                 ApprovalActionLogSink
}

type approvalFlowExecutor interface {
	approvalNotificationFlowVersion(ctx context.Context, flowAPIName string, user *models.UserSession) (*models.FlowVersion, error)
	validateApprovalNotificationFlowMetadata(flowAPIName string, meta *models.FlowMetadata) error
	executeApprovalNotificationFlowVersion(ctx context.Context, flowAPIName string, flowVersion *models.FlowVersion, inputs map[string]interface{}, user *models.UserSession) (*models.FlowExecutionResult, error)
}

type ApprovalServiceOption func(*ApprovalService)

func WithApprovalActionReplayStore(store ApprovalActionReplayStore) ApprovalServiceOption {
	return func(s *ApprovalService) {
		s.replayOutbox = store
	}
}

// WithApprovalActionLogSink wires the terminal-outcome recorder for approval
// action requests (NX#4150 Class C). Leave it unset and no rows are written,
// and every approval path behaves exactly as before. Pass
// NewDBApprovalActionLogStore(db) in production, with a handle that is NOT
// enlisted in the approval transaction - the row must outlive the rollback it
// describes.
func WithApprovalActionLogSink(sink ApprovalActionLogSink) ApprovalServiceOption {
	return func(s *ApprovalService) {
		s.actionLog = sink
	}
}

// WithApprovalSubmitLineageStore wires the per-causal-chain submit dedup
// store. Submit records every (chain, record, process) fact through it inside
// the submit transaction; the SubmitForApproval actionCall probe reads it.
func WithApprovalSubmitLineageStore(store approvalSubmitLineageStore) ApprovalServiceOption {
	return func(s *ApprovalService) {
		s.submitLineage = store
	}
}

// WithApprovalDefinitionDecoder carries the product-owned approval grammar
// decoder into the service. The decoder remains dormant until the neutral
// approval-runtime cutover consumes it.
func WithApprovalDefinitionDecoder(decoder approvalir.DefinitionDecoder) ApprovalServiceOption {
	return func(s *ApprovalService) {
		s.approvalDefinitionDecoder = decoder
	}
}

// WithApprovalQueryOnlyOwnerPacks carries the product-owned pack identities
// whose ProcessNode topology is proven before the host begins serving. Other
// owners retain the compatibility materializer until they have an equivalent
// proof; L0 does not infer product ownership from object names.
func WithApprovalQueryOnlyOwnerPacks(ownerPacks []string) ApprovalServiceOption {
	owners := make(map[string]struct{}, len(ownerPacks))
	for _, rawOwner := range ownerPacks {
		owner := strings.TrimSpace(rawOwner)
		if owner == "" || owner != rawOwner {
			continue
		}
		owners[owner] = struct{}{}
	}
	return func(s *ApprovalService) {
		s.approvalQueryOnlyOwners = owners
	}
}

// NewApprovalService creates a new ApprovalService.
// `schema` is optional; when nil, lookup-field target resolution falls back
// to a restricted heuristic (only standard `<Name>Id` fields resolve).
func NewApprovalService(
	persistence PersistenceProvider,
	query ports.QueryService,
	permissions *permissions.PermissionService,
	flowExecutor *FlowExecutor,
	interviews *FlowInterviewService,
	actions *ActionService,
	schema SchemaLookup,
	logger *logging.Logger,
	opts ...ApprovalServiceOption,
) *ApprovalService {
	if logger == nil {
		logger = logging.Default
	}
	svc := &ApprovalService{
		persistence:   persistence,
		query:         query,
		permissions:   permissions,
		flowExecutor:  flowExecutor,
		interviews:    interviews,
		actions:       actions,
		schema:        schema,
		logger:        logger,
		formulaEngine: formula.NewEngine(),
	}
	for _, opt := range opts {
		if opt != nil {
			opt(svc)
		}
	}
	return svc
}

func (s *ApprovalService) hasSuperUserCapability(ctx context.Context, user *models.UserSession) bool {
	return s != nil && s.permissions != nil && s.permissions.HasSuperUserCapability(ctx, user)
}

// Approve approves a pending work item.
// 1. Finds the ProcessInstanceWorkitem
// 2. Updates the ProcessInstance status to Approved
// 3. Deletes the work item (marking it as processed)
func (s *ApprovalService) Approve(ctx context.Context, workItemID, comments string, user *models.UserSession) (models.SObject, error) {
	return s.processAction(ctx, workItemID, constants.ApprovalStatusApproved, comments, user)
}

// Reject rejects a pending work item.
// 1. Finds the ProcessInstanceWorkitem
// 2. Updates the ProcessInstance status to Rejected
// 3. Deletes the work item (marking it as processed)
func (s *ApprovalService) Reject(ctx context.Context, workItemID, comments string, user *models.UserSession) (models.SObject, error) {
	return s.processAction(ctx, workItemID, constants.ApprovalStatusRejected, comments, user)
}
