package packs

import (
	"context"
	"fmt"
	"strings"

	"github.com/nexuscrm/shared/pkg/models"
	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
	"github.com/pingcap-inc/nexuscrm-controlplane/internal/offlinecandidateauthority"
)

type InstallPlan struct {
	packName string
	phases   []InstallPlanPhase
	steps    map[InstallPlanPhase][]PackComponent
}

type InstallPlanHooks struct {
	AfterPhase func(context.Context, InstallPlanPhase) error
}

type managedInstallStep struct {
	itemRequest   *cp.ComponentManagedRequest
	setRequest    *cp.ComponentManagedSetRequest
	desiredKeys   []string
	lifecycleHost *ManagedLifecycleHost
}

func (s *managedInstallStep) apply(ctx context.Context, applyOnly bool) error {
	if s.lifecycleHost != nil && s.itemRequest != nil {
		request := cloneManagedComponentRequest(*s.itemRequest)
		var err error
		if applyOnly {
			_, err = s.lifecycleHost.applyItemWithoutVerification(ctx, request)
		} else {
			_, err = s.lifecycleHost.ApplyItem(ctx, request)
		}
		return err
	}
	if s.lifecycleHost != nil && s.setRequest != nil {
		var err error
		if applyOnly {
			_, err = s.lifecycleHost.reconcileSetWithoutVerification(
				ctx, *s.setRequest, append([]string(nil), s.desiredKeys...),
			)
		} else {
			_, err = s.lifecycleHost.ReconcileSet(ctx, *s.setRequest, append([]string(nil), s.desiredKeys...))
		}
		return err
	}
	return fmt.Errorf("managed install step is incomplete")
}

type componentApplyGroupProvider interface {
	ApplyGroupKey() string
}

type componentInstallPlanPhaseProvider interface {
	installPlanPhase() InstallPlanPhase
}

func NewInstallPlan(components []PackComponent) (InstallPlan, error) {
	if err := validateSupportedSourceBackedPageLayoutComponents(components); err != nil {
		return InstallPlan{}, err
	}
	steps := map[InstallPlanPhase][]PackComponent{}
	phaseByComponent := make(map[string]InstallPlanPhase, len(components))
	for _, component := range components {
		phaseProvider, externallyManaged := component.(componentInstallPlanPhaseProvider)
		phase := InstallPlanPhase("")
		if externallyManaged {
			phase = phaseProvider.installPlanPhase()
		} else {
			contract, ok := ComponentKindContractFor(component.Kind())
			if !ok {
				return InstallPlan{}, fmt.Errorf("component kind %q has no install contract", component.Kind())
			}
			phase = contract.Phase
		}
		if _, ok := InstallPlanPhaseRank(phase); !ok {
			return InstallPlan{}, fmt.Errorf("component kind %q has unsupported install phase %q", component.Kind(), phase)
		}
		steps[phase] = append(steps[phase], component)
		phaseByComponent[componentIdentityKey(component.Kind(), component.BusinessKey())] = phase
	}
	for _, component := range components {
		dependencies, ok := component.(ComponentDependencyProvider)
		if !ok {
			continue
		}
		phase := phaseByComponent[componentIdentityKey(component.Kind(), component.BusinessKey())]
		phaseRank, _ := InstallPlanPhaseRank(phase)
		for _, dependency := range dependencies.DependsOn() {
			dependencyPhase, exists := phaseByComponent[componentIdentityKey(dependency.Kind, dependency.BusinessKey)]
			if !exists {
				continue
			}
			dependencyRank, _ := InstallPlanPhaseRank(dependencyPhase)
			if dependencyRank > phaseRank {
				return InstallPlan{}, fmt.Errorf("component (%s, %s) is in install phase %q but depends on (%s, %s) in later phase %q; fixed-phase install plans cannot satisfy this dependency",
					component.Kind(), strings.TrimSpace(component.BusinessKey()), phase,
					dependency.Kind, strings.TrimSpace(dependency.BusinessKey), dependencyPhase)
			}
		}
	}
	var phases []InstallPlanPhase
	for _, phase := range DefaultInstallPlanPhaseOrder() {
		if len(steps[phase]) > 0 {
			phases = append(phases, phase)
		}
	}
	return InstallPlan{phases: phases, steps: steps}, nil
}

func splitSourceBackedPageLayoutBusinessKey(businessKey string) (objectAPIName string, tail string, ok bool) {
	head, rest, found := strings.Cut(strings.TrimSpace(businessKey), "/")
	if !found {
		return "", "", false
	}
	head = strings.TrimSpace(head)
	rest = strings.TrimSpace(rest)
	if head == "" || rest == "" {
		return "", "", false
	}
	return head, rest, true
}

type SourceBackedPageLayoutPreflightKey struct {
	ObjectAPIName string
	LayoutName    string
	IsDefault     bool
}

func ParseSourceBackedPageLayoutPreflightKey(businessKey string, payload []byte, source string) (SourceBackedPageLayoutPreflightKey, error) {
	layout, err := sourceBackedPageLayoutFromPayload(payload, businessKey, source)
	if err != nil {
		return SourceBackedPageLayoutPreflightKey{}, err
	}
	return SourceBackedPageLayoutPreflightKey{
		ObjectAPIName: strings.TrimSpace(layout.ObjectAPIName),
		LayoutName:    strings.TrimSpace(layout.LayoutName),
		IsDefault:     layout.IsDefault,
	}, nil
}

func validateSupportedSourceBackedPageLayoutComponents(components []PackComponent) error {
	layoutByIdentity := map[string]string{}
	layoutByTuple := map[string]string{}
	for _, component := range components {
		if component.Kind() != ComponentKindPageLayout {
			continue
		}
		if isSyntheticSourceBackedPageLayoutBusinessKey(component.BusinessKey()) {
			continue
		}
		payload, err := component.ExpectedPayload()
		if err != nil {
			return fmt.Errorf("page_layout %s payload: %w", component.BusinessKey(), err)
		}
		layout, err := sourceBackedPageLayoutFromPayload(
			payload,
			component.BusinessKey(),
			fmt.Sprintf("page_layout %s payload", strings.TrimSpace(component.BusinessKey())),
		)
		if err != nil {
			return err
		}
		identityKey := normalizedSourceBackedPageLayoutInstallIdentity(layout)
		if existing, ok := layoutByIdentity[identityKey]; ok {
			return fmt.Errorf(
				"source-backed page_layout identity %s already declared by %s (conflict: %s)",
				layout.ID,
				existing,
				component.BusinessKey(),
			)
		}
		layoutByIdentity[identityKey] = component.BusinessKey()
		tupleKey := normalizedSourceBackedPageLayoutTupleKey(layout.ObjectAPIName, layout.LayoutName)
		if existing, ok := layoutByTuple[tupleKey]; ok {
			return fmt.Errorf(
				"source-backed page_layout business key %s/%s already declared by %s (conflict: %s)",
				layout.ObjectAPIName,
				layout.LayoutName,
				existing,
				component.BusinessKey(),
			)
		}
		layoutByTuple[tupleKey] = component.BusinessKey()
	}
	return nil
}

func normalizedSourceBackedPageLayoutInstallIdentity(layout models.PageLayout) string {
	return strings.ToLower(sourceBackedLayoutID(layout.ObjectAPIName, layout.LayoutName, layout.IsDefault))
}

func isSyntheticSourceBackedPageLayoutBusinessKey(businessKey string) bool {
	return strings.HasPrefix(strings.TrimSpace(businessKey), "_pack_sweep_")
}

func normalizedSourceBackedPageLayoutTupleKey(objectAPIName, layoutName string) string {
	return strings.ToLower(strings.TrimSpace(objectAPIName)) + "\x00" + strings.ToLower(strings.TrimSpace(layoutName))
}

func (p InstallPlan) Phases() []InstallPlanPhase {
	return append([]InstallPlanPhase(nil), p.phases...)
}

func (p InstallPlan) ComponentsForPhase(phase InstallPlanPhase) []PackComponent {
	return append([]PackComponent(nil), p.steps[phase]...)
}

func (p InstallPlan) Apply(ctx context.Context, deps ApplyDeps, hooks InstallPlanHooks) error {
	applyOnly := offlinecandidateauthority.StoppedSourceSetApplyOnly(ctx, p.packName)
	if applyOnly {
		if err := p.validateStoppedSourceSetObjectSpecCompatibility(ctx, deps); err != nil {
			return err
		}
	}
	managedSteps, err := p.prepareManagedInstallSteps(ctx, deps)
	if err != nil {
		return err
	}
	appliedGroups := map[string]struct{}{}
	for _, phase := range p.phases {
		components := append([]PackComponent(nil), p.steps[phase]...)
		for _, component := range components {
			groupKey := componentInstallApplyGroupKey(component)
			if _, ok := appliedGroups[groupKey]; ok {
				continue
			}
			var managedStep *managedInstallStep
			switch managed := component.(type) {
			case *sourceBackedExternalManagedComponent:
				managedStep = managedSteps[managed]
			case *sourceBackedExternalManagedSetComponent:
				managedStep = managedSteps[managed]
			}
			var err error
			if managedStep != nil {
				err = managedStep.apply(ctx, applyOnly)
			} else if managed, ok := component.(*sourceBackedExternalManagedComponent); ok {
				return fmt.Errorf("managed component %s/%s has no prepared install step", managed.Kind(), managed.BusinessKey())
			} else {
				err = component.Apply(ctx, deps)
			}
			if err != nil {
				return fmt.Errorf("%s %s: %w", component.Kind(), component.BusinessKey(), err)
			}
			appliedGroups[groupKey] = struct{}{}
		}
		if hooks.AfterPhase != nil {
			if err := hooks.AfterPhase(ctx, phase); err != nil {
				return fmt.Errorf("after install phase %s: %w", phase, err)
			}
		}
	}
	return nil
}

func (p InstallPlan) prepareManagedInstallSteps(ctx context.Context, deps ApplyDeps) (map[PackComponent]*managedInstallStep, error) {
	items := make([]*sourceBackedExternalManagedComponent, 0)
	sets := make([]*sourceBackedExternalManagedSetComponent, 0)
	for _, phase := range p.phases {
		for _, component := range p.steps[phase] {
			if managed, ok := component.(*sourceBackedExternalManagedComponent); ok {
				if managed.lifecycle == nil {
					return nil, fmt.Errorf("legacy managed provider execution is disabled for %s/%s; bind an additive managed lifecycle", managed.Kind(), managed.BusinessKey())
				}
				items = append(items, managed)
			}
			if managedSet, ok := component.(*sourceBackedExternalManagedSetComponent); ok {
				if managedSet.lifecycle == nil {
					return nil, fmt.Errorf("legacy managed provider execution is disabled for %s/%s; bind an additive managed lifecycle", managedSet.Kind(), managedSet.PackName())
				}
				sets = append(sets, managedSet)
			}
		}
	}
	if len(items) == 0 && len(sets) == 0 {
		return nil, nil
	}
	if err := validateManagedCollisionClaims(items); err != nil {
		return nil, err
	}
	steps := make(map[PackComponent]*managedInstallStep, len(items)+len(sets))
	for _, component := range items {
		request, err := component.managedRequest()
		if err != nil {
			return nil, err
		}
		request = cloneManagedComponentRequest(request)
		step := &managedInstallStep{itemRequest: &request}
		step.lifecycleHost, err = NewManagedLifecycleHost(deps.DB, component.registration, *component.lifecycle, component.effects)
		if err != nil {
			return nil, fmt.Errorf("prepare managed component %s/%s: %w", component.Kind(), component.BusinessKey(), err)
		}
		steps[component] = step
	}
	for _, component := range sets {
		request := component.request
		host, err := NewManagedLifecycleHost(deps.DB, component.registration, *component.lifecycle, component.effects)
		if err != nil {
			return nil, fmt.Errorf("prepare managed component set %s/%s: %w", component.Kind(), component.PackName(), err)
		}
		steps[component] = &managedInstallStep{
			setRequest: &request, desiredKeys: append([]string(nil), component.declaredKeys...), lifecycleHost: host,
		}
	}
	for _, component := range items {
		step := steps[component]
		err := step.lifecycleHost.PreflightItem(ctx, cloneManagedComponentRequest(*step.itemRequest))
		if err != nil {
			phase := "preflight"
			if offlinecandidateauthority.StoppedSourceSetApplyOnly(ctx, p.packName) {
				phase = "plan"
			}
			return nil, fmt.Errorf("%s %s %s: %w", phase, component.Kind(), component.BusinessKey(), err)
		}
	}
	for _, component := range sets {
		step := steps[component]
		if err := step.lifecycleHost.PreflightSet(ctx, *step.setRequest, append([]string(nil), step.desiredKeys...)); err != nil {
			phase := "preflight"
			if offlinecandidateauthority.StoppedSourceSetApplyOnly(ctx, p.packName) {
				phase = "plan"
			}
			return nil, fmt.Errorf("%s %s %s: %w", phase, component.Kind(), component.BusinessKey(), err)
		}
	}
	return steps, nil
}

func validateManagedCollisionClaims(components []*sourceBackedExternalManagedComponent) error {
	type claimOwner struct {
		kind ComponentKind
		key  string
	}
	owners := make(map[string]claimOwner)
	for _, component := range components {
		if err := component.descriptor.Validate(); err != nil {
			return fmt.Errorf("managed component %s/%s descriptor: %w", component.Kind(), component.BusinessKey(), err)
		}
		for _, claim := range component.descriptor.CollisionClaims {
			namespace, key := strings.TrimSpace(claim.Namespace), strings.TrimSpace(claim.Key)
			identity := namespace + "\x00" + key
			if owner, exists := owners[identity]; exists {
				return fmt.Errorf(
					"managed collision claim %s/%s is declared by both %s/%s and %s/%s",
					namespace, key, owner.kind, owner.key, component.Kind(), component.BusinessKey(),
				)
			}
			owners[identity] = claimOwner{kind: component.Kind(), key: component.BusinessKey()}
		}
	}
	return nil
}

func componentInstallApplyGroupKey(component PackComponent) string {
	if grouped, ok := component.(componentApplyGroupProvider); ok {
		key := strings.TrimSpace(grouped.ApplyGroupKey())
		if key != "" {
			return component.PackName() + "/" + key
		}
	}
	return component.PackName() + "/" + string(component.Kind()) + "/" + component.BusinessKey()
}
