package serverruntime

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

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

// ComponentKindRegistrationProvider constructs one neutral L1-owned component
// registration. Product composition evaluates each provider exactly once.
type ComponentKindRegistrationProvider func() (contractcp.ComponentKindRegistration, error)

// ComponentKindManagedLifecycleRegistrationProvider constructs one neutral
// L1-owned component registration and its additive managed lifecycle. Product
// composition evaluates each provider exactly once and binds both values into
// the same immutable registration snapshot.
type ComponentKindManagedLifecycleRegistrationProvider func() (contractcp.ComponentKindRegistration, contractcp.ComponentManagedLifecycleRegistration, error)

// ComponentMutationStoreOpener opens the canonical mutation store for one
// registered component kind on the caller's ambient transaction. The caller
// owns the transaction boundary; the returned store cannot commit or roll it
// back.
type ComponentMutationStoreOpener func(context.Context, contractcp.ComponentKind) (contractcp.ComponentMutationStore, error)

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

// ComponentKindRegistrationSet returns the immutable registration snapshot
// owned by the composed product distribution. Raw, uncomposed hooks return an
// empty set and therefore cannot activate product-owned component routes.
func (h ProductHooks) ComponentKindRegistrationSet() packs.ComponentKindRegistrationSet {
	return h.Capabilities.componentKindRegistrationSet
}

func composeProductComponentKindRegistrationSet(products []ProductHooks) (packs.ComponentKindRegistrationSet, error) {
	var registrations []contractcp.ComponentKindRegistration
	var managedLifecycles []contractcp.ComponentKindManagedLifecycleRegistration
	for productIndex, product := range products {
		for providerIndex, provider := range product.Capabilities.ComponentKindRegistrationProviders {
			if provider == nil {
				return packs.ComponentKindRegistrationSet{}, fmt.Errorf("product[%d] component kind registration provider[%d] is nil", productIndex, providerIndex)
			}
			registration, err := provider()
			if err != nil {
				return packs.ComponentKindRegistrationSet{}, fmt.Errorf("product[%d] component kind registration provider[%d]: %w", productIndex, providerIndex, err)
			}
			registrations = append(registrations, registration)
		}
		for providerIndex, provider := range product.Capabilities.ComponentKindManagedLifecycleRegistrationProviders {
			if provider == nil {
				return packs.ComponentKindRegistrationSet{}, fmt.Errorf("product[%d] component kind managed lifecycle registration provider[%d] is nil", productIndex, providerIndex)
			}
			registration, lifecycle, err := provider()
			if err != nil {
				return packs.ComponentKindRegistrationSet{}, fmt.Errorf("product[%d] component kind managed lifecycle registration provider[%d]: %w", productIndex, providerIndex, err)
			}
			binding, err := registration.WithManagedLifecycle(lifecycle)
			if err != nil {
				return packs.ComponentKindRegistrationSet{}, fmt.Errorf("product[%d] component kind managed lifecycle registration provider[%d]: %w", productIndex, providerIndex, err)
			}
			managedLifecycles = append(managedLifecycles, binding)
		}
	}
	set, err := packs.NewComponentKindRegistrationSetWithManagedLifecycles(registrations, managedLifecycles)
	if err != nil {
		return packs.ComponentKindRegistrationSet{}, fmt.Errorf("compose product component kind registrations: %w", err)
	}
	set, err = set.WithManagedLifecycleRuntime(
		[]packs.ManagedLifecycleEffectAdapter{
			newPermissionStateManagedLifecycleEffectAdapter(),
		},
		true,
	)
	if err != nil {
		return packs.ComponentKindRegistrationSet{}, fmt.Errorf("compose product component kind registrations: %w", err)
	}
	return set, nil
}

func newAmbientTransactionComponentMutationStoreOpener(
	registrationSet packs.ComponentKindRegistrationSet,
	transactions ambientComponentStoreTransaction,
	db *sql.DB,
) ComponentMutationStoreOpener {
	writeSets := make(map[contractcp.ComponentKind][]contractcp.ComponentWriteSurface)
	for _, registration := range registrationSet.Registrations() {
		writeSets[registration.Contract.Kind] = append([]contractcp.ComponentWriteSurface(nil), registration.Contract.WriteSet...)
	}
	return func(ctx context.Context, kind contractcp.ComponentKind) (contractcp.ComponentMutationStore, error) {
		writeSet, registered := writeSets[kind]
		if !registered {
			return nil, fmt.Errorf("component kind %q is not registered by the product distribution", kind)
		}
		if ctx == nil {
			return nil, fmt.Errorf("open component mutation store for %q: context is required", kind)
		}
		if transactions == nil {
			return nil, fmt.Errorf("open component mutation store for %q: transaction manager is required", kind)
		}
		if !transactions.IsTransactionActive(ctx) || !transactions.IsPessimisticTx(ctx) {
			return nil, fmt.Errorf("open component mutation store for %q: an active pessimistic transaction is required", kind)
		}
		tx, ok := transactions.ExtractTx(ctx).(*sql.Tx)
		if !ok || tx == nil {
			return nil, fmt.Errorf("open component mutation store for %q: ambient transaction must be a non-nil *sql.Tx", kind)
		}
		store, err := packs.NewTransactionComponentMutationStore(tx, db, writeSet)
		if err != nil {
			return nil, fmt.Errorf("open component mutation store for %q: %w", kind, err)
		}
		return store, nil
	}
}
