package connector_webhooks

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

	connectorcapabilities "github.com/pingcap-inc/nexuscrm-connectors/features/connector/capabilities"
	connector_events "github.com/pingcap-inc/nexuscrm-connectors/features/connector/events"
	connector_inbound_routing "github.com/pingcap-inc/nexuscrm-connectors/features/connector/inboundrouting"
	connector_packstate "github.com/pingcap-inc/nexuscrm-connectors/features/connector/packstate"
	"github.com/pingcap-inc/nexuscrm-connectors/features/namedcredential"
)

// RuntimeServingPackIdentity is one immutable connector-pack generation chosen
// by the Integration host's Catalog authority. It contains no provider payload
// or secret value.
type RuntimeServingPackIdentity struct {
	Namespace       string
	InstalledPackID string
	PayloadDigest   string
}

// NewRuntimeServingDispatcher constructs the existing webhook Dispatcher with
// its first pack read constrained to exact, caller-selected generations. The
// generic NewDispatcher remains unchanged for existing in-process compositions.
func NewRuntimeServingDispatcher(
	db *sql.DB,
	cipher *namedcredential.SecretCipher,
	registry *connectorcapabilities.Registry,
	events *connector_events.Service,
	routes connector_inbound_routing.RouteResolver,
	packs []RuntimeServingPackIdentity,
) (*Dispatcher, error) {
	resolvePack, packTransient, err := runtimeServingPackResolvers(db, packs)
	if err != nil {
		return nil, err
	}
	dispatcher := NewDispatcher(db, cipher, registry, events, routes)
	dispatcher.resolvePack = resolvePack
	dispatcher.packTransient = packTransient
	// The generic ingress cache is keyed by namespace/install rather than the
	// Catalog-selected payload digest. Runtime serving must revalidate its exact
	// immutable generation before every downstream secret/handler read, so it
	// deliberately bypasses only the pack snapshot cache instead of serving a
	// mixed snapshot after same-install drift. Exact-install secret/verifier
	// caching remains available after that live authority check succeeds.
	dispatcher.requireLivePackAuthority = true
	return dispatcher, nil
}

func runtimeServingPackResolvers(
	db *sql.DB,
	values []RuntimeServingPackIdentity,
) (
	func(context.Context, string) (*connector_packstate.ConnectorPackRow, bool, error),
	func(context.Context, string) (bool, error),
	error,
) {
	if db == nil {
		return nil, nil, errors.New("connector_webhooks: Runtime serving database is required")
	}
	selected := make(map[string]RuntimeServingPackIdentity, len(values))
	for _, raw := range values {
		value := RuntimeServingPackIdentity{
			Namespace:       strings.ToLower(strings.TrimSpace(raw.Namespace)),
			InstalledPackID: strings.TrimSpace(raw.InstalledPackID),
			PayloadDigest:   strings.TrimSpace(raw.PayloadDigest),
		}
		if value.Namespace == "" || value.InstalledPackID == "" || value.PayloadDigest == "" {
			return nil, nil, errors.New("connector_webhooks: Runtime serving pack identity is incomplete")
		}
		if _, duplicate := selected[value.Namespace]; duplicate {
			return nil, nil, fmt.Errorf("connector_webhooks: Runtime serving namespace %s is ambiguous", value.Namespace)
		}
		selected[value.Namespace] = value
	}
	resolvePack := func(ctx context.Context, namespace string) (*connector_packstate.ConnectorPackRow, bool, error) {
		value, authorized := selected[strings.ToLower(strings.TrimSpace(namespace))]
		if !authorized {
			return nil, false, nil
		}
		row, found, err := connector_packstate.ResolveActiveConnectorPackByInstall(
			ctx,
			db,
			value.Namespace,
			value.InstalledPackID,
		)
		if err != nil {
			return nil, false, err
		}
		if !found || row.PayloadHash != value.PayloadDigest {
			return nil, false, fmt.Errorf(
				"connector_webhooks: selected Runtime serving pack %s (%s) is missing, stale, or not ready",
				value.Namespace,
				value.InstalledPackID,
			)
		}
		return row, true, nil
	}
	packTransient := func(ctx context.Context, namespace string) (bool, error) {
		value, authorized := selected[strings.ToLower(strings.TrimSpace(namespace))]
		if !authorized {
			return false, nil
		}
		return connector_packstate.IsConnectorPackMidApply(ctx, db, value.Namespace)
	}
	return resolvePack, packTransient, nil
}
