package serverruntime

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

	"github.com/nexuscrm/permissions"
	contractcp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
	controlplanepacks "github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

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

// assignmentPolicyEligibilityAmbientStager hides controlplane host,
// attestation, target, and evidence types from the L1 product boundary.
type assignmentPolicyEligibilityAmbientStager interface {
	Stage(context.Context, *sql.Tx, []permissions.AssignmentPolicyEligibilityMutation) error
}

type assignmentPolicyEligibilityMutationHandler struct {
	transactions assignmentPolicyEligibilityTransaction
	stager       assignmentPolicyEligibilityAmbientStager
}

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.stager == nil {
		return fmt.Errorf("stage assignment-policy eligibility: managed lifecycle stager is required")
	}
	if err := h.stager.Stage(ctx, tx, mutations); err != nil {
		return fmt.Errorf("stage assignment-policy eligibility: %w", err)
	}
	return nil
}

type assignmentPolicyEligibilityManagedLifecycleStager struct {
	host        *controlplanepacks.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")
	}
	desiredKeys := assignmentPolicyEligibilityDesiredKeys(mutations)
	request, err := controlplanepacks.NewManagedLifecyclePlannedTransactionRequest(desiredKeys)
	if err != nil {
		return err
	}
	transaction, err := controlplanepacks.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
}

func newAssignmentPolicyEligibilityMutationHandler(
	ctx context.Context,
	db *sql.DB,
	transactions assignmentPolicyEligibilityTransaction,
	registrationSet controlplanepacks.ComponentKindRegistrationSet,
	eligibility permissions.AssignmentPolicyEligibilityProjectionProvider,
) (permissions.AssignmentPolicyEligibilityMutationBatchHandler, error) {
	var binding *contractcp.ComponentKindManagedLifecycleRegistration
	for _, candidate := range registrationSet.ManagedLifecycles() {
		if candidate.Registration().Contract.Kind != permissions.PermissionSetAssignmentPolicyComponentKind {
			continue
		}
		if binding != nil {
			return nil, fmt.Errorf("assignment-policy managed lifecycle is registered more than once")
		}
		frozen := candidate
		binding = &frozen
	}
	if binding == nil {
		return nil, nil
	}
	if db == nil {
		return nil, fmt.Errorf("assignment-policy managed lifecycle requires a database")
	}
	if transactions == nil {
		return nil, fmt.Errorf("assignment-policy managed lifecycle requires a transaction manager")
	}
	if eligibility == nil {
		return nil, fmt.Errorf("assignment-policy managed lifecycle requires an eligibility projection provider")
	}
	host, err := controlplanepacks.NewManagedLifecycleHost(
		db,
		binding.Registration(),
		binding.ManagedLifecycle(),
		registrationSet.ManagedLifecycleEffectAdapters(),
	)
	if err != nil {
		return nil, fmt.Errorf("compose assignment-policy managed lifecycle host: %w", err)
	}
	if err := host.PrepareAmbientTransactionStages(ctx); err != nil {
		return nil, fmt.Errorf("prepare assignment-policy ambient stages: %w", err)
	}
	return &assignmentPolicyEligibilityMutationHandler{
		transactions: transactions,
		stager:       assignmentPolicyEligibilityManagedLifecycleStager{host: host, eligibility: eligibility},
	}, nil
}
