package packs

import (
	"context"
	"fmt"

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

type sourceBackedExternalManagedSetComponent struct {
	packName      string
	declaredKeys  []string
	request       cp.ComponentManagedSetRequest
	registration  cp.ComponentKindRegistration
	lifecycle     *cp.ComponentManagedLifecycleRegistration
	effects       []ManagedLifecycleEffectAdapter
	manageability Manageability
}

var _ componentCustomVerifier = (*sourceBackedExternalManagedSetComponent)(nil)

func (p *SourceBackedPack) externalManagedSetComponents() ([]PackComponent, error) {
	return p.externalManagedSetComponentsWithProvenKinds(nil)
}

// externalManagedSetComponentsWithProvenKinds mints exact-set authority only
// for kinds declared by this source revision. additionalProvenKinds is reserved
// for a current-to-target delta whose removed declarations prove that the
// target intentionally owns the transition to an empty set. A globally
// registered lifecycle is not, by itself, source authority for an absent kind.
func (p *SourceBackedPack) externalManagedSetComponentsWithProvenKinds(additionalProvenKinds map[ComponentKind]bool) ([]PackComponent, error) {
	if !p.componentRegistrationSetConfigured {
		return nil, nil
	}
	provenKinds := make(map[ComponentKind]bool, len(additionalProvenKinds)+len(p.components))
	for kind, proven := range additionalProvenKinds {
		if proven {
			provenKinds[kind] = true
		}
	}
	for _, component := range p.components {
		provenKinds[component.Kind()] = true
	}
	components := make([]PackComponent, 0)
	for _, entry := range p.componentRegistrationSet.entries {
		registration := cloneComponentKindRegistration(entry.registration)
		if (registration.ManagedProvider == nil && entry.lifecycle == nil) || !managedRegistrationAllowsCategory(registration, p.Category()) {
			continue
		}
		if entry.lifecycle != nil {
			exactSet := false
			for _, capability := range entry.lifecycle.Capabilities() {
				exactSet = exactSet || capability == cp.ComponentManagedCapabilityExactSet
			}
			if !exactSet {
				continue
			}
		}
		kind := ComponentKind(registration.Contract.Kind)
		if !provenKinds[kind] {
			continue
		}
		declaredKeys := make([]string, 0)
		for _, component := range p.components {
			if component.Kind() == kind {
				declaredKeys = append(declaredKeys, component.BusinessKey())
			}
		}
		var component *sourceBackedExternalManagedSetComponent
		var err error
		if entry.lifecycle == nil {
			component, err = newSourceBackedExternalManagedSetComponent(p.Name(), registration, declaredKeys)
		} else {
			component, err = newSourceBackedExternalManagedSetComponent(p.Name(), registration, declaredKeys, *entry.lifecycle)
		}
		if err != nil {
			return nil, err
		}
		component.effects = p.componentRegistrationSet.ManagedLifecycleEffectAdapters()
		components = append(components, component)
	}
	return components, nil
}

func newSourceBackedExternalManagedSetComponent(packName string, registration cp.ComponentKindRegistration, declaredKeys []string, lifecycles ...cp.ComponentManagedLifecycleRegistration) (*sourceBackedExternalManagedSetComponent, error) {
	keys, err := canonicalManagedComponentKeys(declaredKeys)
	if err != nil {
		return nil, fmt.Errorf("managed component set %s for pack %s: %w", registration.Contract.Kind, packName, err)
	}
	request := cp.ComponentManagedSetRequest{PackName: packName, Kind: registration.Contract.Kind}
	switch registration.Contract.OwnerDiscriminator {
	case cp.ComponentOwnerStrategyOwnerPack:
	case cp.ComponentOwnerStrategySource:
		return nil, fmt.Errorf("source-owned managed component set %s for pack %s has no explicit source owner", registration.Contract.Kind, packName)
	default:
		return nil, fmt.Errorf("managed component set %s for pack %s has unsupported owner discriminator %q", registration.Contract.Kind, packName, registration.Contract.OwnerDiscriminator)
	}
	manageability := ManageabilityDefaultsOnly
	if _, ok := registration.Contract.Manageabilities[cp.ManageabilityLocked]; ok {
		manageability = ManageabilityLocked
	}
	component := &sourceBackedExternalManagedSetComponent{
		packName: packName, declaredKeys: keys, request: request,
		registration: cloneComponentKindRegistration(registration), manageability: manageability,
	}
	if len(lifecycles) == 1 {
		lifecycle := lifecycles[0]
		component.lifecycle = &lifecycle
	}
	return component, nil
}

func (c *sourceBackedExternalManagedSetComponent) Kind() ComponentKind {
	return ComponentKind(c.request.Kind)
}

func (c *sourceBackedExternalManagedSetComponent) PackName() string { return c.packName }

func (c *sourceBackedExternalManagedSetComponent) BusinessKey() string {
	return JoinComponentBusinessKey(c.packName, "@managed-set")
}

func (c *sourceBackedExternalManagedSetComponent) Manageability() Manageability {
	return c.manageability
}

func (c *sourceBackedExternalManagedSetComponent) ExpectedPayload() ([]byte, error) {
	return nil, nil
}

func (c *sourceBackedExternalManagedSetComponent) ExpectedProjection() (any, error) {
	return append([]string(nil), c.declaredKeys...), nil
}

func (c *sourceBackedExternalManagedSetComponent) LiveProjection(context.Context, VerifyDeps) (any, error) {
	return nil, fmt.Errorf("managed component set reconciliation is install-only for %s/%s", c.Kind(), c.PackName())
}

func (c *sourceBackedExternalManagedSetComponent) Apply(ctx context.Context, deps ApplyDeps) error {
	return fmt.Errorf("managed set reconciliation must run through the additive install-plan lifecycle for %s/%s", c.Kind(), c.PackName())
}

func (c *sourceBackedExternalManagedSetComponent) VerifyComponent(ctx context.Context, deps VerifyDeps) error {
	if c.lifecycle == nil {
		return fmt.Errorf("legacy managed provider verification is disabled for %s/%s; bind an additive managed lifecycle", c.Kind(), c.PackName())
	}
	host, err := NewManagedLifecycleHost(deps.DB, c.registration, *c.lifecycle, c.effects)
	if err != nil {
		return fmt.Errorf("prepare managed lifecycle set %s/%s verification: %w", c.Kind(), c.PackName(), err)
	}
	verification, err := host.VerifySet(ctx, c.request, append([]string(nil), c.declaredKeys...))
	if err != nil {
		return err
	}
	if verification.Matches {
		return nil
	}
	return fmt.Errorf("managed lifecycle set %s/%s projection drift: expected %q, live %q", c.Kind(), c.PackName(), verification.Projection.Expected, verification.Projection.Live)
}

func managedComponentSetNeedsReconcile(declaredKeys []string, liveKeys []cp.ComponentLiveBusinessKey) bool {
	declared := make(map[string]struct{}, len(declaredKeys))
	for _, key := range declaredKeys {
		declared[key] = struct{}{}
	}
	for _, key := range liveKeys {
		if _, ok := declared[key.BusinessKey]; !ok && !key.Customized {
			return true
		}
	}
	return false
}

func (c *sourceBackedExternalManagedSetComponent) ApplyGroupKey() string {
	return "@managed-set-reconcile/" + string(c.Kind())
}

func (c *sourceBackedExternalManagedSetComponent) installPlanPhase() InstallPlanPhase {
	return InstallPlanPhase(c.registration.Contract.Phase)
}
