package managedruntime

import (
	"context"
	"database/sql"
	"encoding/base64"
	"fmt"

	"github.com/nexuscrm/metadata/catalogsnapshot"
	"github.com/nexuscrm/permissions"
	"github.com/nexuscrm/persistence/managedlifecycle"
	contractcp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
)

type AssignmentPolicyEligibilityTransaction interface {
	IsTransactionActive(context.Context) bool
	IsPessimisticTx(context.Context) bool
	ExtractTx(context.Context) interface{}
}

type AssignmentPolicyEligibilityAmbientStager interface {
	Stage(context.Context, *sql.Tx, []permissions.AssignmentPolicyEligibilityMutation) error
}

type AssignmentPolicyEligibilityAmbientStagerResolver func(context.Context) (AssignmentPolicyEligibilityAmbientStager, bool)

type assignmentPolicyEligibilityAmbientStagerContextKey struct{}

// WithAssignmentPolicyEligibilityAmbientStager binds the managed lifecycle
// host prepared from the exact immutable mutation view admitted for this
// execution. Both Runtime API requests and durable Runtime Worker attempts use
// this context contract; neither process may select current/latest metadata
// while a transaction is running.
func WithAssignmentPolicyEligibilityAmbientStager(
	ctx context.Context,
	stager AssignmentPolicyEligibilityAmbientStager,
) context.Context {
	if ctx == nil || stager == nil {
		return ctx
	}
	return context.WithValue(ctx, assignmentPolicyEligibilityAmbientStagerContextKey{}, stager)
}

// AssignmentPolicyEligibilityAmbientStagerFromContext resolves only the
// execution-bound lifecycle host. There is deliberately no process-global or
// latest-Catalog fallback.
func AssignmentPolicyEligibilityAmbientStagerFromContext(
	ctx context.Context,
) (AssignmentPolicyEligibilityAmbientStager, bool) {
	if ctx == nil {
		return nil, false
	}
	stager, ok := ctx.Value(assignmentPolicyEligibilityAmbientStagerContextKey{}).(AssignmentPolicyEligibilityAmbientStager)
	return stager, ok && stager != nil
}

type assignmentPolicyEligibilityMutationHandler struct {
	transactions  AssignmentPolicyEligibilityTransaction
	resolveStager AssignmentPolicyEligibilityAmbientStagerResolver
}

func (h *assignmentPolicyEligibilityMutationHandler) ApplyAssignmentPolicyEligibilityMutation(
	ctx context.Context,
	mutation permissions.AssignmentPolicyEligibilityMutation,
) error {
	return h.ApplyAssignmentPolicyEligibilityMutations(ctx, []permissions.AssignmentPolicyEligibilityMutation{mutation})
}

func (h *assignmentPolicyEligibilityMutationHandler) ApplyAssignmentPolicyEligibilityMutations(
	ctx context.Context,
	mutations []permissions.AssignmentPolicyEligibilityMutation,
) error {
	if len(mutations) == 0 {
		return nil
	}
	if h == nil || h.transactions == nil {
		return fmt.Errorf("stage assignment-policy eligibility: transaction manager is required")
	}
	if !h.transactions.IsTransactionActive(ctx) || !h.transactions.IsPessimisticTx(ctx) {
		return fmt.Errorf("stage assignment-policy eligibility: an active pessimistic transaction is required")
	}
	tx, ok := h.transactions.ExtractTx(ctx).(*sql.Tx)
	if !ok || tx == nil {
		return fmt.Errorf("stage assignment-policy eligibility: ambient transaction must be a non-nil *sql.Tx")
	}
	if h.resolveStager == nil {
		return fmt.Errorf("stage assignment-policy eligibility: managed lifecycle stager is required")
	}
	stager, ok := h.resolveStager(ctx)
	if !ok || stager == nil {
		return fmt.Errorf("stage assignment-policy eligibility: admitted Catalog lifecycle binding is required")
	}
	if err := stager.Stage(ctx, tx, mutations); err != nil {
		return fmt.Errorf("stage assignment-policy eligibility: %w", err)
	}
	return nil
}

type assignmentPolicyEligibilityManagedLifecycleStager struct {
	host        *managedlifecycle.ManagedLifecycleHost
	eligibility permissions.AssignmentPolicyEligibilityProjectionProvider
}

func (s assignmentPolicyEligibilityManagedLifecycleStager) Stage(
	ctx context.Context,
	tx *sql.Tx,
	mutations []permissions.AssignmentPolicyEligibilityMutation,
) error {
	if s.host == nil {
		return fmt.Errorf("assignment-policy managed lifecycle host is required")
	}
	request, err := managedlifecycle.NewManagedLifecyclePlannedTransactionRequest(
		AssignmentPolicyEligibilityDesiredKeys(mutations),
	)
	if err != nil {
		return err
	}
	transaction, err := managedlifecycle.AttestManagedLifecycleCallerTransaction(
		tx,
		contractcp.ComponentManagedTransactionPessimisticReadCommitted,
	)
	if err != nil {
		return err
	}
	_, err = s.host.StagePlannedInTransaction(
		ctx,
		transaction,
		request,
		permissions.NewPermissionSetAssignmentPolicyEligibilityTransitionsProvider(mutations, s.eligibility),
	)
	return err
}

func AssignmentPolicyEligibilityDesiredKeys(mutations []permissions.AssignmentPolicyEligibilityMutation) []string {
	seen := make(map[string]struct{}, len(mutations))
	keys := make([]string, 0, len(mutations))
	for _, mutation := range mutations {
		key := "source/" + base64.RawURLEncoding.EncodeToString([]byte(mutation.SourceIdentity))
		if _, duplicate := seen[key]; duplicate {
			continue
		}
		seen[key] = struct{}{}
		keys = append(keys, key)
	}
	return keys
}

// NewIndependentAssignmentPolicyEligibilityAmbientStager prepares one immutable
// Catalog-revision-bound lifecycle binding. No policy target is invented at
// process startup: an actual installed policy owns preparation of the global
// serialization target, while a lifecycle-only app remains a valid zero-target
// runtime.
func NewIndependentAssignmentPolicyEligibilityAmbientStager(
	ctx context.Context,
	db *sql.DB,
	view *catalogsnapshot.RuntimeMetadataView,
) (AssignmentPolicyEligibilityAmbientStager, error) {
	if view == nil {
		return nil, fmt.Errorf("assignment-policy Catalog runtime view is required")
	}
	eligibility, err := permissions.NewCatalogAssignmentPolicyEligibilityProjectionProvider(view.Objects())
	if err != nil {
		return nil, err
	}
	if eligibility == nil {
		return nil, nil
	}
	registration, lifecycle, err := permissions.NewPermissionSetAssignmentPolicyManagedLifecycleRegistrationWithEligibility(eligibility)
	if err != nil {
		return nil, err
	}
	binding, err := registration.WithManagedLifecycle(lifecycle)
	if err != nil {
		return nil, err
	}
	host, err := managedlifecycle.NewManagedLifecycleHost(
		db,
		binding.Registration(),
		binding.ManagedLifecycle(),
		[]managedlifecycle.ManagedLifecycleEffectAdapter{NewPermissionStateManagedLifecycleEffectAdapter()},
	)
	if err != nil {
		return nil, fmt.Errorf("compose assignment-policy managed lifecycle host: %w", err)
	}
	if err := host.AttachAmbientTransactionStages(ctx, nil); err != nil {
		return nil, fmt.Errorf("attach assignment-policy ambient lifecycle state: %w", err)
	}
	return assignmentPolicyEligibilityManagedLifecycleStager{host: host, eligibility: eligibility}, nil
}

// NewIndependentAssignmentPolicyEligibilityMutationHandler installs one
// process-global context resolver. Each admitted request supplies the stager
// prepared with the exact immutable mutation view captured by that request.
func NewIndependentAssignmentPolicyEligibilityMutationHandler(
	transactions AssignmentPolicyEligibilityTransaction,
	resolveStager AssignmentPolicyEligibilityAmbientStagerResolver,
) (permissions.AssignmentPolicyEligibilityMutationBatchHandler, error) {
	if transactions == nil {
		return nil, fmt.Errorf("assignment-policy managed lifecycle requires a transaction manager")
	}
	if resolveStager == nil {
		return nil, fmt.Errorf("assignment-policy admitted lifecycle resolver is required")
	}
	return &assignmentPolicyEligibilityMutationHandler{
		transactions:  transactions,
		resolveStager: resolveStager,
	}, nil
}
