package automation

import (
	"context"
	"errors"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/kernel/pkg/recordlocks"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/queryspec"
	"github.com/nexuscrm/shared/pkg/referenceidentity"
)

// RuntimeApprovalAuthority is the request-bound semantic source used by the
// independent Runtime Approval composition. ProcessDefinition and ProcessNode
// rows remain storage identity anchors, but their executable meaning and every
// ProcessAction come from the immutable Catalog view carried by this authority.
//
// The query-shaped seam is intentionally private to the existing Approval
// engine adapter. It is not an RPC and does not expose the engine's internal
// interfaces over the wire.
type RuntimeApprovalAuthority interface {
	referenceidentity.Authority
	QueryProcessDefinitions(context.Context, string, string, string, int) (*models.QueryResult, error)
	QueryProcessNodes(context.Context, string, string, string, int) (*models.QueryResult, error)
	QueryProcessActions(context.Context, string, string, string, int) (*models.QueryResult, error)
	GetProcessDefinition(context.Context, string) (approvalRuntimeRecord, error)
	CatalogDefinitionID(context.Context, string) (string, error)
}

type runtimeApprovalAuthorityContextKey struct{}

// WithRuntimeApprovalAuthority binds exactly one immutable Catalog authority
// to an Approval request. A Runtime Approval service never falls back to live
// semantic tables when this binding is absent.
func WithRuntimeApprovalAuthority(ctx context.Context, authority RuntimeApprovalAuthority) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	ctx = referenceidentity.WithAuthority(ctx, authority)
	return context.WithValue(ctx, runtimeApprovalAuthorityContextKey{}, authority)
}

func runtimeApprovalAuthorityFromContext(ctx context.Context) (RuntimeApprovalAuthority, bool) {
	if ctx == nil {
		return nil, false
	}
	authority, ok := ctx.Value(runtimeApprovalAuthorityContextKey{}).(RuntimeApprovalAuthority)
	return authority, ok && authority != nil
}

var errRuntimeApprovalAuthorityMissing = errors.New("runtime Approval Catalog authority is missing")

// RuntimeApprovalQueryRouter preserves the existing Approval engine while
// replacing its three metadata-table read paths with one request-bound Catalog
// authority. All runtime-state and business-record queries still delegate to
// the existing neutral QueryService.
type RuntimeApprovalQueryRouter struct {
	base ports.QueryService
}

func NewRuntimeApprovalQueryRouter(base ports.QueryService) (*RuntimeApprovalQueryRouter, error) {
	if base == nil {
		return nil, errors.New("runtime Approval query router requires a base query service")
	}
	return &RuntimeApprovalQueryRouter{base: base}, nil
}

func (r *RuntimeApprovalQueryRouter) QueryWithFilter(
	ctx context.Context,
	objectName string,
	filterExpr string,
	user *models.UserSession,
	sortField string,
	sortDir string,
	limit int,
) (*models.QueryResult, error) {
	switch objectName {
	case constants.TableProcessDefinition, constants.TableProcessNode, constants.TableProcessAction:
		authority, ok := runtimeApprovalAuthorityFromContext(ctx)
		if !ok {
			return nil, errRuntimeApprovalAuthorityMissing
		}
		switch objectName {
		case constants.TableProcessDefinition:
			return authority.QueryProcessDefinitions(ctx, filterExpr, sortField, sortDir, limit)
		case constants.TableProcessNode:
			return authority.QueryProcessNodes(ctx, filterExpr, sortField, sortDir, limit)
		default:
			return authority.QueryProcessActions(ctx, filterExpr, sortField, sortDir, limit)
		}
	default:
		return r.base.QueryWithFilter(ctx, objectName, filterExpr, user, sortField, sortDir, limit)
	}
}

func (r *RuntimeApprovalQueryRouter) QueryWithSpec(
	ctx context.Context,
	req queryspec.QuerySpec,
	user *models.UserSession,
) (*models.QueryResult, error) {
	switch req.ObjectAPIName {
	case constants.TableProcessDefinition, constants.TableProcessNode, constants.TableProcessAction:
		return nil, errors.New("runtime Approval metadata queries must use the closed authority adapter")
	default:
		return r.base.QueryWithSpec(ctx, req, user)
	}
}

func (r *RuntimeApprovalQueryRouter) RunAnalytics(ctx context.Context, query models.AnalyticsQuery, user *models.UserSession) (interface{}, error) {
	return r.base.RunAnalytics(ctx, query, user)
}

func (r *RuntimeApprovalQueryRouter) SearchSingleObject(ctx context.Context, objectName, term string, searchFields, returnFields []string, limit, offset int, orderBy, orderDir, filterExpr string, user *models.UserSession) ([]approvalRuntimeRecord, error) {
	return r.base.SearchSingleObject(ctx, objectName, term, searchFields, returnFields, limit, offset, orderBy, orderDir, filterExpr, user)
}

func (r *RuntimeApprovalQueryRouter) SearchSuggestions(ctx context.Context, objectName, term string, searchFields, returnFields []string, limit int, user *models.UserSession) ([]approvalRuntimeRecord, error) {
	return r.base.SearchSuggestions(ctx, objectName, term, searchFields, returnFields, limit, user)
}

func (r *RuntimeApprovalQueryRouter) GlobalSearch(ctx context.Context, term string, user *models.UserSession) ([]models.SearchResult, error) {
	return r.base.GlobalSearch(ctx, term, user)
}

func (r *RuntimeApprovalQueryRouter) Calculate(ctx context.Context, objectName string, record approvalRuntimeRecord, user *models.UserSession) (approvalRuntimeRecord, error) {
	return r.base.Calculate(ctx, objectName, record, user)
}

func (r *RuntimeApprovalQueryRouter) ExecuteRawSQL(ctx context.Context, statement string, params []interface{}, user *models.UserSession) ([]approvalRuntimeRecord, error) {
	return r.base.ExecuteRawSQL(ctx, statement, params, user)
}

// RuntimeApprovalPersistenceRouter prevents the few direct GetRaw definition
// reads from bypassing the same Catalog authority. All state writes and record
// reads remain owned by the existing persistence engine and transaction.
type RuntimeApprovalPersistenceRouter struct {
	base PersistenceProvider
}

func NewRuntimeApprovalPersistenceRouter(base PersistenceProvider) (*RuntimeApprovalPersistenceRouter, error) {
	if base == nil {
		return nil, errors.New("runtime Approval persistence router requires a base persistence service")
	}
	return &RuntimeApprovalPersistenceRouter{base: base}, nil
}

func (r *RuntimeApprovalPersistenceRouter) Insert(ctx context.Context, table string, record approvalRuntimeRecord, user *models.UserSession) (approvalRuntimeRecord, error) {
	return r.base.Insert(ctx, table, record, user)
}
func (r *RuntimeApprovalPersistenceRouter) PreviewBeforeInsert(ctx context.Context, table string, record approvalRuntimeRecord, user *models.UserSession) (approvalRuntimeRecord, error) {
	return r.base.PreviewBeforeInsert(ctx, table, record, user)
}
func (r *RuntimeApprovalPersistenceRouter) PrelockUpdateParents(ctx context.Context, table, id string, updates approvalRuntimeRecord, user *models.UserSession) error {
	return r.base.PrelockUpdateParents(ctx, table, id, updates, user)
}
func (r *RuntimeApprovalPersistenceRouter) PrelockPreparedUpdateParents(ctx context.Context, table, id string, prepared *models.PreparedRecordUpdate, extra approvalRuntimeRecord, user *models.UserSession) error {
	prelocker, ok := r.base.(preparedUpdateParentPrelocker)
	if !ok {
		return appErrors.NewInternalError("runtime Approval persistence is missing prepared parent prelocking", nil)
	}
	return prelocker.PrelockPreparedUpdateParents(ctx, table, id, prepared, extra, user)
}
func (r *RuntimeApprovalPersistenceRouter) PlanPreparedUpdateParentKeys(ctx context.Context, table, id string, prepared *models.PreparedRecordUpdate, extra approvalRuntimeRecord, user *models.UserSession) ([]recordlocks.ParentKey, error) {
	planner, ok := r.base.(preparedUpdateParentPlanner)
	if !ok {
		return nil, appErrors.NewInternalError("runtime Approval persistence is missing prepared parent planning", nil)
	}
	return planner.PlanPreparedUpdateParentKeys(ctx, table, id, prepared, extra, user)
}
func (r *RuntimeApprovalPersistenceRouter) Update(ctx context.Context, table, id string, updates approvalRuntimeRecord, user *models.UserSession) error {
	return r.base.Update(ctx, table, id, updates, user)
}
func (r *RuntimeApprovalPersistenceRouter) PreviewBeforeUpdate(ctx context.Context, table, id string, updates, stamps approvalRuntimeRecord, user *models.UserSession) (*models.PreparedRecordUpdate, error) {
	return r.base.PreviewBeforeUpdate(ctx, table, id, updates, stamps, user)
}
func (r *RuntimeApprovalPersistenceRouter) CommitPreparedUpdate(ctx context.Context, table, id string, prepared *models.PreparedRecordUpdate, extra approvalRuntimeRecord, user *models.UserSession) error {
	return r.base.CommitPreparedUpdate(ctx, table, id, prepared, extra, user)
}
func (r *RuntimeApprovalPersistenceRouter) Delete(ctx context.Context, table, id string, user *models.UserSession) error {
	return r.base.Delete(ctx, table, id, user)
}
func (r *RuntimeApprovalPersistenceRouter) GetRaw(ctx context.Context, objectName, id string) (approvalRuntimeRecord, error) {
	if objectName == constants.TableProcessDefinition {
		authority, ok := runtimeApprovalAuthorityFromContext(ctx)
		if !ok {
			return nil, errRuntimeApprovalAuthorityMissing
		}
		return authority.GetProcessDefinition(ctx, id)
	}
	if objectName == constants.TableProcessNode || objectName == constants.TableProcessAction {
		return nil, errors.New("runtime Approval metadata GetRaw must use the closed authority adapter")
	}
	return r.base.GetRaw(ctx, objectName, id)
}
func (r *RuntimeApprovalPersistenceRouter) ReadSelectedSystemRecord(ctx context.Context, objectName, id string, fields []string) (approvalRuntimeRecord, error) {
	if objectName == constants.TableProcessDefinition || objectName == constants.TableProcessNode || objectName == constants.TableProcessAction {
		return nil, errors.New("runtime Approval metadata selected reads must use the closed authority adapter")
	}
	reader, ok := r.base.(selectedSystemRecordReader)
	if !ok {
		return nil, appErrors.NewInternalError("runtime Approval persistence is missing selected system-record reads", nil)
	}
	return reader.ReadSelectedSystemRecord(ctx, objectName, id, fields)
}
func (r *RuntimeApprovalPersistenceRouter) GetLock(ctx context.Context, table, id string) (approvalRuntimeRecord, error) {
	return r.base.GetLock(ctx, table, id)
}
func (r *RuntimeApprovalPersistenceRouter) GetRecordType(ctx context.Context, id string) (string, error) {
	return r.base.GetRecordType(ctx, id)
}
func (r *RuntimeApprovalPersistenceRouter) RunInTransaction(ctx context.Context, fn func(context.Context) error) error {
	return r.base.RunInTransaction(ctx, fn)
}
func (r *RuntimeApprovalPersistenceRouter) IsTransactionActive(ctx context.Context) bool {
	return r.base.IsTransactionActive(ctx)
}
func (r *RuntimeApprovalPersistenceRouter) RunInPessimisticTransaction(ctx context.Context, fn func(context.Context) error) error {
	return r.base.RunInPessimisticTransaction(ctx, fn)
}
func (r *RuntimeApprovalPersistenceRouter) RunWithParentLockCoordinator(ctx context.Context, fn func(context.Context) error) error {
	runner, ok := r.base.(parentLockCoordinatorRunner)
	if !ok {
		return appErrors.NewInternalError("runtime Approval persistence is missing the parent lock coordinator", nil)
	}
	return runner.RunWithParentLockCoordinator(ctx, fn)
}
func (r *RuntimeApprovalPersistenceRouter) LockParentRows(ctx context.Context, keys []recordlocks.ParentKey) ([]recordlocks.ParentKey, error) {
	locker, ok := r.base.(parentRowLocker)
	if !ok {
		return nil, appErrors.NewInternalError("runtime Approval persistence is missing coordinated row locking", nil)
	}
	// The records engine owns reference identity and lock disposition. This
	// adapter only preserves the Approval service's persistence interface.
	return locker.LockParentRows(ctx, keys)
}
func (r *RuntimeApprovalPersistenceRouter) DetachTx(ctx context.Context) context.Context {
	return r.base.DetachTx(ctx)
}
func (r *RuntimeApprovalPersistenceRouter) RecomputeActiveFormulaProjection(ctx context.Context, user *models.UserSession, objectAPIName, recordID string, fields []string) (approvalRuntimeRecord, error) {
	return r.base.RecomputeActiveFormulaProjection(ctx, user, objectAPIName, recordID, fields)
}
