package packs

import (
	"fmt"
	"slices"
	"sort"
	"strings"

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

// ComponentKindRegistrationSet is a validated immutable value owned by one
// distribution. A configured registration reserves its kind for external
// dispatch; SourceBackedPack fails closed until that route is installed.
type ComponentKindRegistrationSet struct {
	entries                      []componentKindRegistrationEntry
	managedLifecycleEffects      []ManagedLifecycleEffectAdapter
	rejectLegacyManagedProviders bool
}

type componentKindRegistrationEntry struct {
	registration cp.ComponentKindRegistration
	lifecycle    *cp.ComponentManagedLifecycleRegistration
}

// NewComponentKindRegistrationSet validates one distribution's neutral L1
// registrations and snapshots all mutable fields.
func NewComponentKindRegistrationSet(registrations []cp.ComponentKindRegistration) (ComponentKindRegistrationSet, error) {
	return newComponentKindRegistrationSet(registrations, nil)
}

// NewComponentKindRegistrationSetWithManagedLifecycles atomically binds the
// additive lifecycle registrations into the same immutable kind snapshot used
// for build, validation, and install routing. A lifecycle binding selects the
// additive provider while leaving the caller's legacy-compatible base value
// unchanged.
func NewComponentKindRegistrationSetWithManagedLifecycles(registrations []cp.ComponentKindRegistration, lifecycles []cp.ComponentKindManagedLifecycleRegistration) (ComponentKindRegistrationSet, error) {
	return newComponentKindRegistrationSet(registrations, lifecycles)
}

func newComponentKindRegistrationSet(registrations []cp.ComponentKindRegistration, lifecycles []cp.ComponentKindManagedLifecycleRegistration) (ComponentKindRegistrationSet, error) {
	entries := make([]componentKindRegistrationEntry, 0, len(registrations)+len(lifecycles))
	for _, registration := range registrations {
		entries = append(entries, componentKindRegistrationEntry{registration: cloneComponentKindRegistration(registration)})
	}
	for index, binding := range lifecycles {
		registration := cloneComponentKindRegistration(binding.Registration())
		lifecycle := binding.ManagedLifecycle()
		registration.ManagedProvider = nil
		if lifecycle.Kind() != registration.Contract.Kind {
			return ComponentKindRegistrationSet{}, fmt.Errorf("managed lifecycle binding %d kind %q does not match registration kind %q", index, lifecycle.Kind(), registration.Contract.Kind)
		}
		entries = append(entries, componentKindRegistrationEntry{registration: registration, lifecycle: &lifecycle})
	}

	seenKinds := make(map[string]cp.ComponentKind, len(entries))
	tableOwners := map[string]cp.ComponentKind{}
	for index := range entries {
		registration := entries[index].registration
		kind := string(registration.Contract.Kind)
		kindKey := strings.ToLower(kind)
		if kind == "" || kind != strings.TrimSpace(kind) || kind != kindKey {
			return ComponentKindRegistrationSet{}, fmt.Errorf("registration %d kind %q is not canonical", index, kind)
		}
		if previous, exists := seenKinds[kindKey]; exists {
			return ComponentKindRegistrationSet{}, fmt.Errorf("component kind %q is registered more than once (first %q)", kind, previous)
		}
		seenKinds[kindKey] = registration.Contract.Kind
		if registration.Capability.Kind != registration.Contract.Kind || !registration.Capability.Installable {
			return ComponentKindRegistrationSet{}, fmt.Errorf("component kind %s does not declare an installable matching capability", kind)
		}
		if registration.BuildHandler == nil {
			return ComponentKindRegistrationSet{}, fmt.Errorf("component kind %s build handler is required", kind)
		}
		if registration.ManagedProvider != nil {
			if err := validateManagedComponentRegistration(registration); err != nil {
				return ComponentKindRegistrationSet{}, fmt.Errorf("component kind %s: %w", kind, err)
			}
			if err := validateComponentRegistrationDescriptor(registration); err != nil {
				return ComponentKindRegistrationSet{}, fmt.Errorf("component kind %s: %w", kind, err)
			}
		} else if entries[index].lifecycle != nil {
			if err := validateManagedLifecycleBaseRegistration(registration, *entries[index].lifecycle); err != nil {
				return ComponentKindRegistrationSet{}, fmt.Errorf("component kind %s: %w", kind, err)
			}
		}
		for _, surface := range registration.Contract.WriteSet {
			tableKey := strings.ToLower(surface.Table)
			if previous, exists := tableOwners[tableKey]; exists && previous != registration.Contract.Kind {
				return ComponentKindRegistrationSet{}, fmt.Errorf("component kinds %s and %s both claim write table %q", previous, registration.Contract.Kind, surface.Table)
			}
			tableOwners[tableKey] = registration.Contract.Kind
		}
		entries[index].registration = registration
	}
	sort.Slice(entries, func(i, j int) bool {
		return entries[i].registration.Contract.Kind < entries[j].registration.Contract.Kind
	})
	return ComponentKindRegistrationSet{entries: entries}, nil
}

// WithComponentKindRegistrations reserves the registered kinds on one source
// pack construction. Validation happens immediately and any error is returned
// by NewSourceBackedPack before component build.
func WithComponentKindRegistrations(registrations []cp.ComponentKindRegistration) SourceBackedPackOption {
	set, err := NewComponentKindRegistrationSet(registrations)
	return withComponentKindRegistrationSet(set, err)
}

// WithComponentKindManagedLifecycles configures base and lifecycle-backed
// registrations through one immutable source-pack option.
func WithComponentKindManagedLifecycles(registrations []cp.ComponentKindRegistration, lifecycles []cp.ComponentKindManagedLifecycleRegistration) SourceBackedPackOption {
	set, err := NewComponentKindRegistrationSetWithManagedLifecycles(registrations, lifecycles)
	return withComponentKindRegistrationSet(set, err)
}

// WithComponentKindRegistrationSet carries an already validated distribution
// snapshot through source-pack construction without dropping lifecycle
// bindings into a legacy-only registration slice.
func WithComponentKindRegistrationSet(source ComponentKindRegistrationSet) SourceBackedPackOption {
	set, err := source.Clone()
	return withComponentKindRegistrationSet(set, err)
}

// WithComponentKindRegistrationsFromSourcePack carries the already-owned set
// through a rebuild without exposing a second mutable registration read path.
func WithComponentKindRegistrationsFromSourcePack(source *SourceBackedPack) SourceBackedPackOption {
	if source == nil {
		return withComponentKindRegistrationSet(ComponentKindRegistrationSet{}, fmt.Errorf("source pack is required"))
	}
	if !source.componentRegistrationSetConfigured {
		return func(pack *SourceBackedPack) {
			pack.requireSourceBackedPackConstruction()
		}
	}
	return withComponentKindRegistrationSet(source.componentRegistrationSet.Clone())
}

func withComponentKindRegistrationSet(set ComponentKindRegistrationSet, err error) SourceBackedPackOption {
	return func(pack *SourceBackedPack) {
		pack.requireSourceBackedPackConstruction()
		if pack.componentRegistrationSetConfigured {
			pack.componentRegistrationSetErr = fmt.Errorf("component registration set is configured more than once")
			return
		}
		pack.componentRegistrationSetConfigured = true
		pack.componentRegistrationSet = set
		pack.componentRegistrationSetErr = err
	}
}

// Registrations returns a deep copy in canonical kind order.
func (s ComponentKindRegistrationSet) Registrations() []cp.ComponentKindRegistration {
	out := make([]cp.ComponentKindRegistration, len(s.entries))
	for index, entry := range s.entries {
		out[index] = cloneComponentKindRegistration(entry.registration)
	}
	return out
}

func (s ComponentKindRegistrationSet) plainRegistrations() []cp.ComponentKindRegistration {
	out := make([]cp.ComponentKindRegistration, 0, len(s.entries))
	for _, entry := range s.entries {
		if entry.lifecycle == nil {
			out = append(out, cloneComponentKindRegistration(entry.registration))
		}
	}
	return out
}

// ManagedLifecycles returns defensive copies of all additive lifecycle
// bindings in canonical kind order.
func (s ComponentKindRegistrationSet) ManagedLifecycles() []cp.ComponentKindManagedLifecycleRegistration {
	out := make([]cp.ComponentKindManagedLifecycleRegistration, 0, len(s.entries))
	for _, entry := range s.entries {
		if entry.lifecycle == nil {
			continue
		}
		binding, err := cloneComponentKindRegistration(entry.registration).WithManagedLifecycle(*entry.lifecycle)
		if err != nil {
			panic("validated managed lifecycle snapshot became invalid: " + err.Error())
		}
		out = append(out, binding)
	}
	return out
}

// Clone returns an independent immutable snapshot with the same legacy,
// presentation, and lifecycle bindings.
func (s ComponentKindRegistrationSet) Clone() (ComponentKindRegistrationSet, error) {
	clone, err := NewComponentKindRegistrationSetWithManagedLifecycles(s.plainRegistrations(), s.ManagedLifecycles())
	if err != nil {
		return ComponentKindRegistrationSet{}, err
	}
	clone.managedLifecycleEffects = append([]ManagedLifecycleEffectAdapter(nil), s.managedLifecycleEffects...)
	clone.rejectLegacyManagedProviders = s.rejectLegacyManagedProviders
	return clone, nil
}

// WithManagedLifecycleRuntime returns a new immutable registration snapshot
// with distribution-owned effect adapters and the optional production legacy
// gate. It does not change the layout of RunnerOptions or ApplyDeps.
func (s ComponentKindRegistrationSet) WithManagedLifecycleRuntime(effects []ManagedLifecycleEffectAdapter, rejectLegacyProviders bool) (ComponentKindRegistrationSet, error) {
	if err := ValidateManagedLifecycleEffectAdapters(effects); err != nil {
		return ComponentKindRegistrationSet{}, err
	}
	clone, err := s.Clone()
	if err != nil {
		return ComponentKindRegistrationSet{}, err
	}
	clone.managedLifecycleEffects = append([]ManagedLifecycleEffectAdapter(nil), effects...)
	sort.Slice(clone.managedLifecycleEffects, func(i, j int) bool {
		return clone.managedLifecycleEffects[i].Class < clone.managedLifecycleEffects[j].Class
	})
	clone.rejectLegacyManagedProviders = rejectLegacyProviders
	if err := clone.ValidateManagedLifecycleRuntime(); err != nil {
		return ComponentKindRegistrationSet{}, err
	}
	return clone, nil
}

// ManagedLifecycleEffectAdapters returns a defensive copy of the immutable
// distribution-owned effect registry.
func (s ComponentKindRegistrationSet) ManagedLifecycleEffectAdapters() []ManagedLifecycleEffectAdapter {
	return append([]ManagedLifecycleEffectAdapter(nil), s.managedLifecycleEffects...)
}

// ManagedLifecycleRuntimeConfigured reports whether the snapshot carries
// runtime bindings beyond its component-kind registrations.
func (s ComponentKindRegistrationSet) ManagedLifecycleRuntimeConfigured() bool {
	return len(s.managedLifecycleEffects) != 0 || s.rejectLegacyManagedProviders
}

// ValidateManagedLifecycleRuntime applies the opt-in production activation
// gate to the same immutable registration snapshot used by source routing.
func (s ComponentKindRegistrationSet) ValidateManagedLifecycleRuntime() error {
	if err := ValidateManagedLifecycleEffectAdapters(s.managedLifecycleEffects); err != nil {
		return err
	}
	if s.rejectLegacyManagedProviders {
		return s.ValidateNoActiveLegacyManagedProviders()
	}
	return nil
}

// SameSnapshot reports whether two values retain the same immutable validated
// registration snapshot. Independently reconstructed sets are not identical.
func (s ComponentKindRegistrationSet) SameSnapshot(other ComponentKindRegistrationSet) bool {
	if len(s.entries) != len(other.entries) || len(s.managedLifecycleEffects) != len(other.managedLifecycleEffects) || s.rejectLegacyManagedProviders != other.rejectLegacyManagedProviders {
		return false
	}
	if len(s.entries) != 0 && &s.entries[0] != &other.entries[0] {
		return false
	}
	return len(s.managedLifecycleEffects) == 0 || &s.managedLifecycleEffects[0] == &other.managedLifecycleEffects[0]
}

// ValidateNoActiveLegacyManagedProviders is the production activation gate.
// Deprecated identifiers remain source-compatible, but a released lifecycle
// composition can reject every still-active five-callback registration.
func (s ComponentKindRegistrationSet) ValidateNoActiveLegacyManagedProviders() error {
	var kinds []string
	for _, entry := range s.entries {
		if entry.registration.ManagedProvider != nil {
			kinds = append(kinds, string(entry.registration.Contract.Kind))
		}
	}
	if len(kinds) != 0 {
		return fmt.Errorf("active legacy managed providers are forbidden: %s", strings.Join(kinds, ", "))
	}
	return nil
}

func (s ComponentKindRegistrationSet) reserves(kind ComponentKind) bool {
	for _, entry := range s.entries {
		if ComponentKind(entry.registration.Contract.Kind) == kind {
			return true
		}
	}
	return false
}

func (s *ComponentKindRegistrationSet) managedRegistration(kind ComponentKind) (cp.ComponentKindRegistration, bool) {
	if s == nil {
		return cp.ComponentKindRegistration{}, false
	}
	for _, entry := range s.entries {
		if ComponentKind(entry.registration.Contract.Kind) == kind && entry.registration.ManagedProvider != nil {
			return cloneComponentKindRegistration(entry.registration), true
		}
	}
	return cp.ComponentKindRegistration{}, false
}

func (s *ComponentKindRegistrationSet) managedLifecycle(kind ComponentKind) (cp.ComponentManagedLifecycleRegistration, bool) {
	if s == nil {
		return cp.ComponentManagedLifecycleRegistration{}, false
	}
	for _, entry := range s.entries {
		if ComponentKind(entry.registration.Contract.Kind) == kind && entry.lifecycle != nil {
			return *entry.lifecycle, true
		}
	}
	return cp.ComponentManagedLifecycleRegistration{}, false
}

func (s *ComponentKindRegistrationSet) managedEntry(kind ComponentKind) (componentKindRegistrationEntry, bool) {
	if s == nil {
		return componentKindRegistrationEntry{}, false
	}
	for _, entry := range s.entries {
		if ComponentKind(entry.registration.Contract.Kind) == kind && (entry.registration.ManagedProvider != nil || entry.lifecycle != nil) {
			entry.registration = cloneComponentKindRegistration(entry.registration)
			return entry, true
		}
	}
	return componentKindRegistrationEntry{}, false
}

// HasManagedOwnerPackTable reports whether this immutable distribution snapshot
// owns the complete managed lifecycle for kind on an OwnerPack-scoped table.
func (s ComponentKindRegistrationSet) HasManagedOwnerPackTable(kind ComponentKind, table string) bool {
	entry, ok := (&s).managedEntry(kind)
	registration := entry.registration
	if !ok || registration.Contract.OwnerDiscriminator != cp.ComponentOwnerStrategyOwnerPack {
		return false
	}
	if entry.lifecycle != nil {
		exactSet := false
		for _, capability := range entry.lifecycle.Capabilities() {
			exactSet = exactSet || capability == cp.ComponentManagedCapabilityExactSet
		}
		if !exactSet {
			return false
		}
	}
	for _, surface := range registration.Contract.WriteSet {
		if surface.Table == table {
			return true
		}
	}
	return false
}

// withManagedValidationInputs derives the source-validation registries from
// the immutable distribution set. Presentation-only registrations do not
// acquire managed install semantics through this path.
func (s ComponentKindRegistrationSet) withManagedValidationInputs(opts PackSourceOptions) PackSourceOptions {
	kinds := make(ComponentKindRegistry, len(opts.ComponentKindRegistry)+len(s.entries))
	for kind := range opts.ComponentKindRegistry {
		kinds[kind] = struct{}{}
	}
	baseCapabilities := opts.SourceBackedComponentCapabilities
	if baseCapabilities == nil {
		baseCapabilities = DefaultSourceBackedComponentCapabilityRegistry()
	}
	capabilities := make(SourceBackedComponentCapabilityRegistry, len(baseCapabilities)+len(s.entries))
	for kind, capability := range baseCapabilities {
		capability.Manageabilities = copyManageabilitySet(capability.Manageabilities)
		capabilities[kind] = capability
	}
	for _, entry := range s.entries {
		registration := entry.registration
		if registration.ManagedProvider == nil && entry.lifecycle == nil {
			continue
		}
		kind := ComponentKind(registration.Contract.Kind)
		manageabilities := make(map[Manageability]struct{}, len(registration.Capability.Manageabilities))
		for manageability := range registration.Capability.Manageabilities {
			manageabilities[Manageability(manageability)] = struct{}{}
		}
		kinds[kind] = struct{}{}
		capabilities[kind] = SourceBackedComponentCapability{
			Kind: kind, Installable: registration.Capability.Installable, Manageabilities: manageabilities,
		}
	}
	opts.ComponentKindRegistry = kinds
	opts.SourceBackedComponentCapabilities = capabilities
	return opts
}

func managedRegistrationAllowsCategory(registration cp.ComponentKindRegistration, category Category) bool {
	return slices.Contains(registration.AllowedCategories, cp.PackCategory(category))
}

func sourceValidationInstallPhase(kind ComponentKind, registrations *ComponentKindRegistrationSet) (InstallPlanPhase, bool) {
	if entry, ok := registrations.managedEntry(kind); ok {
		return InstallPlanPhase(entry.registration.Contract.Phase), true
	}
	contract, ok := ComponentKindContractFor(kind)
	return contract.Phase, ok
}

func (p *SourceBackedPack) installRegisteredBuildRoutes() error {
	if !p.componentRegistrationSetConfigured {
		return nil
	}
	if p.externalBuildRoutes == nil {
		p.externalBuildRoutes = map[ComponentKind]externalBuildRoute{}
	}
	for _, entry := range p.componentRegistrationSet.entries {
		registration := cloneComponentKindRegistration(entry.registration)
		kind := ComponentKind(registration.Contract.Kind)
		if !slices.ContainsFunc(p.manifest.Components, func(component ManifestComponent) bool { return component.Kind == kind }) {
			continue
		}
		if (registration.ManagedProvider != nil || entry.lifecycle != nil) && !managedRegistrationAllowsCategory(registration, p.Category()) {
			return fmt.Errorf("component kind %q does not support pack category %q", kind, p.Category())
		}
		if _, exists := p.externalBuildRoutes[kind]; exists {
			return fmt.Errorf("component kind %q has more than one external build route", kind)
		}
		cloned := registration
		p.externalBuildRoutes[kind] = externalBuildRoute{handler: cloned.BuildHandler, registration: &cloned, lifecycle: entry.lifecycle}
	}
	return nil
}

func validateComponentRegistrationDescriptor(registration cp.ComponentKindRegistration) error {
	canonical := []struct {
		name  string
		value string
	}{
		{"phase", string(registration.Contract.Phase)},
		{"business key", registration.Contract.BusinessKey},
		{"verify projection", registration.Contract.VerifyProjection},
		{"collision key", registration.Contract.CollisionKey},
	}
	for _, field := range canonical {
		if field.value == "" || field.value != strings.TrimSpace(field.value) {
			return fmt.Errorf("%s %q is not canonical", field.name, field.value)
		}
	}
	if _, ok := InstallPlanPhaseRank(InstallPlanPhase(registration.Contract.Phase)); !ok {
		return fmt.Errorf("phase %q is unsupported", registration.Contract.Phase)
	}
	if len(registration.AllowedCategories) == 0 {
		return fmt.Errorf("allowed categories are required")
	}
	seen := map[cp.PackCategory]struct{}{}
	for _, category := range registration.AllowedCategories {
		if category != cp.AppPack {
			return fmt.Errorf("allowed category %q is unsupported", category)
		}
		if _, exists := seen[category]; exists {
			return fmt.Errorf("allowed category %q is duplicated", category)
		}
		seen[category] = struct{}{}
	}
	return nil
}

func cloneComponentKindRegistration(registration cp.ComponentKindRegistration) cp.ComponentKindRegistration {
	registration.Contract.WriteSet = append([]cp.ComponentWriteSurface(nil), registration.Contract.WriteSet...)
	registration.Contract.Manageabilities = cloneContractManageabilities(registration.Contract.Manageabilities)
	registration.Capability.Manageabilities = cloneContractManageabilities(registration.Capability.Manageabilities)
	registration.AllowedCategories = append([]cp.PackCategory(nil), registration.AllowedCategories...)
	if registration.ManagedProvider != nil {
		provider := *registration.ManagedProvider
		registration.ManagedProvider = &provider
	}
	return registration
}

func cloneContractManageabilities(values map[cp.Manageability]struct{}) map[cp.Manageability]struct{} {
	out := make(map[cp.Manageability]struct{}, len(values))
	for value := range values {
		out[value] = struct{}{}
	}
	return out
}
