package packs

import (
	"context"
	"fmt"
	"sort"
	"strings"
)

// SourceBackedExecutionFacts identifies the exact source-backed pack revision
// captured by one execution atom.
type SourceBackedExecutionFacts struct {
	Name                   string
	Category               Category
	Metadata               PackHashMetadata
	PayloadHash            string
	HostRequirementsDigest string
}

// SourceBackedExecutionAtom is a packs-minted capability for executing one
// already-constructed SourceBackedPack through its retained install plan. It
// deliberately does not implement Pack or expose the plan's components.
type SourceBackedExecutionAtom struct {
	identity *SourceBackedPackIdentity
	facts    SourceBackedExecutionFacts
	plan     InstallPlan
	verify   []PackComponent
	minted   bool
}

// SourceBackedComponentDelta describes the component-level difference carried
// by a source-revision execution atom. The payload digest is calculated from
// the complete normalized component declaration (kind, business key,
// manageability, payload and references), not only from the source file bytes.
type SourceBackedComponentDelta struct {
	Added          []DeclaredComponent
	Changed        []DeclaredComponent
	Removed        []DeclaredComponent
	UnchangedCount int
}

func (d SourceBackedComponentDelta) IsNoop() bool {
	return len(d.Added) == 0 && len(d.Changed) == 0 && len(d.Removed) == 0
}

// FreezeSourceBackedExecutionAtom captures the target's immutable identity and
// builds its install plan exactly once. The target must already own all of its
// source-backed component state.
func FreezeSourceBackedExecutionAtom(target *SourceBackedPack) (*SourceBackedExecutionAtom, error) {
	return freezeSourceBackedExecutionAtom(target, nil)
}

// FreezeSourceBackedRetryExecutionAtom retains the complete target plan for a
// retryable failed or interrupted installation. A previous attempt may have
// committed only part of a component plan before whole-target verification
// failed, so source-delta equality is not evidence that live component state
// is complete. Retrying the complete idempotent target plan is the recovery
// boundary.
func FreezeSourceBackedRetryExecutionAtom(target *SourceBackedPack) (*SourceBackedExecutionAtom, error) {
	return freezeSourceBackedExecutionAtom(target, nil)
}

// FreezeSourceBackedReplayExecutionAtom retains the complete target plan while
// also carrying lifecycle components that exist only because the exact
// current-to-target transition proves a removal. This is the source-revision
// replay boundary: unchanged target components must be rewritten for a fresh
// pack incarnation, while a kind removed to zero must not lose its exact-set
// authority merely because the target manifest no longer declares that kind.
func FreezeSourceBackedReplayExecutionAtom(current, target *SourceBackedPack) (*SourceBackedExecutionAtom, SourceBackedComponentDelta, error) {
	transitionPlan, delta, err := sourceBackedDeltaInstallPlan(current, target)
	if err != nil {
		return nil, SourceBackedComponentDelta{}, fmt.Errorf("freeze source-backed replay execution atom transition proof: %w", err)
	}
	targetPlan, err := target.InstallPlan()
	if err != nil {
		return nil, SourceBackedComponentDelta{}, fmt.Errorf("freeze source-backed replay execution atom %q target plan: %w", target.Name(), err)
	}

	components := installPlanComponents(targetPlan)
	retained := make(map[string]bool, len(components))
	for _, component := range components {
		retained[componentIdentityKey(component.Kind(), component.BusinessKey())] = true
	}
	for _, component := range installPlanComponents(transitionPlan) {
		key := componentIdentityKey(component.Kind(), component.BusinessKey())
		if retained[key] {
			continue
		}
		components = append(components, component)
		retained[key] = true
	}
	replayPlan, err := NewInstallPlan(components)
	if err != nil {
		return nil, SourceBackedComponentDelta{}, fmt.Errorf("freeze source-backed replay execution atom %q install plan: %w", target.Name(), err)
	}
	atom, err := freezeSourceBackedExecutionAtom(target, &replayPlan)
	if err != nil {
		return nil, SourceBackedComponentDelta{}, err
	}
	return atom, delta, nil
}

// FreezeSourceBackedDeltaExecutionAtom captures the exact target identity while
// retaining only the changed component groups, their in-pack dependencies and
// the kind-scoped sweepers needed for removals. It is an execution optimization,
// not an authority shortcut: callers must still prove that current is the exact
// active immutable revision and must perform the normal whole-target verification
// before activation.
func FreezeSourceBackedDeltaExecutionAtom(current, target *SourceBackedPack) (*SourceBackedExecutionAtom, SourceBackedComponentDelta, error) {
	plan, delta, err := sourceBackedDeltaInstallPlan(current, target)
	if err != nil {
		return nil, SourceBackedComponentDelta{}, err
	}
	atom, err := freezeSourceBackedExecutionAtom(target, &plan)
	if err != nil {
		return nil, SourceBackedComponentDelta{}, err
	}
	return atom, delta, nil
}

func freezeSourceBackedExecutionAtom(target *SourceBackedPack, retainedPlan *InstallPlan) (*SourceBackedExecutionAtom, error) {
	identity, err := FreezeSourceBackedPackIdentity(target)
	if err != nil {
		return nil, fmt.Errorf("freeze source-backed execution atom: %w", err)
	}

	name := strings.TrimSpace(target.Name())
	if name == "" {
		return nil, fmt.Errorf("freeze source-backed execution atom: target name is empty")
	}
	category := Category(strings.TrimSpace(string(target.Category())))
	if category == "" {
		return nil, fmt.Errorf("freeze source-backed execution atom %q: target category is empty", name)
	}

	metadata := PackHashMetadataForPack(target)
	payloadHash, err := PackPayloadHash(target)
	if err != nil {
		return nil, fmt.Errorf("freeze source-backed execution atom %q payload hash: %w", name, err)
	}
	hostRequirementsDigest, err := PackHostRequirementsDigest(target)
	if err != nil {
		return nil, fmt.Errorf("freeze source-backed execution atom %q host requirements: %w", name, err)
	}
	var plan InstallPlan
	var verify []PackComponent
	if retainedPlan != nil {
		plan = *retainedPlan
		// Delta execution is a write optimization only. Activation still verifies
		// the complete immutable target so an unchanged-but-drifted component
		// fails closed instead of being silently accepted or opportunistically
		// rewritten on every release.
		verifyPlan, verifyErr := target.InstallPlan()
		if verifyErr != nil {
			return nil, fmt.Errorf("freeze source-backed execution atom %q verification plan: %w", name, verifyErr)
		}
		verify = installPlanComponents(verifyPlan)
		// A proven current-to-target removal can intentionally take an exact
		// managed set to zero even though the target manifest no longer contains
		// that kind. Retain the delta-minted set proof in final verification;
		// target.InstallPlan cannot reconstruct this transition proof from the
		// target source alone.
		verified := make(map[string]bool, len(verify))
		for _, component := range verify {
			verified[componentIdentityKey(component.Kind(), component.BusinessKey())] = true
		}
		for _, component := range installPlanComponents(plan) {
			if _, ok := component.(*sourceBackedExternalManagedSetComponent); !ok {
				continue
			}
			key := componentIdentityKey(component.Kind(), component.BusinessKey())
			if verified[key] {
				continue
			}
			verify = append(verify, component)
			verified[key] = true
		}
	} else {
		plan, err = target.InstallPlan()
		if err != nil {
			return nil, fmt.Errorf("freeze source-backed execution atom %q install plan: %w", name, err)
		}
		verify = installPlanComponents(plan)
	}

	return &SourceBackedExecutionAtom{
		identity: identity,
		facts: SourceBackedExecutionFacts{
			Name:                   name,
			Category:               category,
			Metadata:               metadata,
			PayloadHash:            payloadHash,
			HostRequirementsDigest: hostRequirementsDigest,
		},
		plan:   plan,
		verify: verify,
		minted: true,
	}, nil
}

func sourceBackedDeltaInstallPlan(current, target *SourceBackedPack) (InstallPlan, SourceBackedComponentDelta, error) {
	if current == nil || target == nil {
		return InstallPlan{}, SourceBackedComponentDelta{}, fmt.Errorf("freeze source-backed delta execution atom: current and target are required")
	}
	if strings.TrimSpace(current.Name()) != strings.TrimSpace(target.Name()) || current.Category() != target.Category() {
		return InstallPlan{}, SourceBackedComponentDelta{}, fmt.Errorf("freeze source-backed delta execution atom: current and target pack identity differs")
	}

	currentDeclared, err := current.DeclaredComponents()
	if err != nil {
		return InstallPlan{}, SourceBackedComponentDelta{}, fmt.Errorf("freeze source-backed delta execution atom %q current declarations: %w", target.Name(), err)
	}
	targetDeclared, err := target.DeclaredComponents()
	if err != nil {
		return InstallPlan{}, SourceBackedComponentDelta{}, fmt.Errorf("freeze source-backed delta execution atom %q target declarations: %w", target.Name(), err)
	}
	currentByKey, err := declaredComponentMap(currentDeclared)
	if err != nil {
		return InstallPlan{}, SourceBackedComponentDelta{}, err
	}
	targetByKey, err := declaredComponentMap(targetDeclared)
	if err != nil {
		return InstallPlan{}, SourceBackedComponentDelta{}, err
	}

	delta := SourceBackedComponentDelta{}
	selected := map[string]bool{}
	affectedKinds := map[ComponentKind]bool{}
	for key, targetComponent := range targetByKey {
		currentComponent, exists := currentByKey[key]
		switch {
		case !exists:
			delta.Added = append(delta.Added, targetComponent)
			selected[key] = true
			affectedKinds[targetComponent.Kind] = true
		case !declaredComponentDigestsEqual(currentComponent, targetComponent):
			delta.Changed = append(delta.Changed, targetComponent)
			selected[key] = true
			affectedKinds[targetComponent.Kind] = true
		default:
			delta.UnchangedCount++
		}
	}
	for key, currentComponent := range currentByKey {
		if _, exists := targetByKey[key]; exists {
			continue
		}
		delta.Removed = append(delta.Removed, currentComponent)
		affectedKinds[currentComponent.Kind] = true
	}
	sortDeclaredComponents(delta.Added)
	sortDeclaredComponents(delta.Changed)
	sortDeclaredComponents(delta.Removed)

	if delta.IsNoop() {
		plan, err := NewInstallPlan(nil)
		return plan, delta, err
	}

	// Retain changed declarations, every member of a selected apply group, and
	// declarations whose live projection depends on a selected declaration.
	//
	// Do not re-apply unchanged prerequisites merely because a changed component
	// names them. This is a revision advance from an already-active exact source,
	// so unchanged prerequisites are already installed and the final whole-target
	// verification still proves them. Re-applying them is both wasted work and a
	// correctness bug: a prerequisite writer can reset a projection owned by an
	// unchanged dependent. Conversely, when the prerequisite itself changed, the
	// dependent must be replayed even when its own payload digest did not change.
	targetComponents := target.Components()
	componentByKey := make(map[string]PackComponent, len(targetComponents))
	for _, component := range targetComponents {
		componentByKey[componentIdentityKey(component.Kind(), component.BusinessKey())] = component
	}
	for changed := true; changed; {
		changed = false
		selectedGroups := map[string]bool{}
		for key := range selected {
			component := componentByKey[key]
			if component == nil {
				continue
			}
			selectedGroups[componentInstallApplyGroupKey(component)] = true
		}
		for key, component := range componentByKey {
			if selectedGroups[componentInstallApplyGroupKey(component)] && !selected[key] {
				selected[key] = true
				changed = true
			}
			dependencies, ok := component.(ComponentDependencyProvider)
			if !ok || selected[key] {
				continue
			}
			for _, dependency := range dependencies.DependsOn() {
				if selected[componentIdentityKey(dependency.Kind, dependency.BusinessKey)] {
					selected[key] = true
					changed = true
					break
				}
			}
		}
	}

	selectedComponents := make([]PackComponent, 0, len(selected))
	for _, component := range targetComponents {
		if selected[componentIdentityKey(component.Kind(), component.BusinessKey())] {
			selectedComponents = append(selectedComponents, component)
		}
	}

	// Lifecycle components own deletion and exact-set convergence. They run only
	// for kinds touched by this delta; unchanged kinds are covered by the final
	// read-only whole-target verification.
	lifecycle, err := target.lifecycleClosureComponentsWithProvenKinds(affectedKinds)
	if err != nil {
		return InstallPlan{}, SourceBackedComponentDelta{}, err
	}
	lifecycleKinds := make(map[ComponentKind]bool, len(lifecycle))
	for _, component := range lifecycle {
		lifecycleKinds[component.Kind()] = true
		if affectedKinds[component.Kind()] {
			selectedComponents = append(selectedComponents, component)
		}
	}
	for _, removed := range delta.Removed {
		if !lifecycleKinds[removed.Kind] {
			return InstallPlan{}, SourceBackedComponentDelta{}, fmt.Errorf(
				"freeze source-backed delta execution atom %q: removed component kind %q has no lifecycle owner",
				target.Name(), removed.Kind,
			)
		}
	}

	plan, err := NewInstallPlan(selectedComponents)
	if err != nil {
		return InstallPlan{}, SourceBackedComponentDelta{}, fmt.Errorf("freeze source-backed delta execution atom %q install plan: %w", target.Name(), err)
	}
	return plan, delta, nil
}

func declaredComponentDigestsEqual(left, right DeclaredComponent) bool {
	leftDigest, leftErr := left.Digest()
	rightDigest, rightErr := right.Digest()
	return leftErr == nil && rightErr == nil && leftDigest == rightDigest
}

func declaredComponentMap(components []DeclaredComponent) (map[string]DeclaredComponent, error) {
	out := make(map[string]DeclaredComponent, len(components))
	for _, component := range components {
		key := componentIdentityKey(component.Kind, component.BusinessKey)
		if _, exists := out[key]; exists {
			return nil, fmt.Errorf("source-backed component declaration %s/%s is duplicated", component.Kind, component.BusinessKey)
		}
		out[key] = component
	}
	return out, nil
}

func sortDeclaredComponents(components []DeclaredComponent) {
	sort.Slice(components, func(i, j int) bool {
		if components[i].Kind != components[j].Kind {
			return components[i].Kind < components[j].Kind
		}
		return strings.TrimSpace(components[i].BusinessKey) < strings.TrimSpace(components[j].BusinessKey)
	})
}

func installPlanComponents(plan InstallPlan) []PackComponent {
	components := make([]PackComponent, 0)
	for _, phase := range plan.phases {
		components = append(components, plan.steps[phase]...)
	}
	return components
}

// ValidateSourceBackedExecutionAtomTarget confirms that atom was minted for
// the exact constructed target without rebuilding either target state or plan.
func ValidateSourceBackedExecutionAtomTarget(atom *SourceBackedExecutionAtom, target *SourceBackedPack) error {
	if err := atom.requireMinted(); err != nil {
		return err
	}
	if err := ValidateSourceBackedPackIdentityTarget(atom.identity, target); err != nil {
		return fmt.Errorf("source-backed execution atom target identity: %w", err)
	}
	return nil
}

// Facts returns the captured source identity without exposing the retained
// target or install plan.
func (a *SourceBackedExecutionAtom) Facts() (SourceBackedExecutionFacts, error) {
	if err := a.requireMinted(); err != nil {
		return SourceBackedExecutionFacts{}, err
	}
	return a.facts, nil
}

// Execute applies only the plan retained when the atom was minted.
func (a *SourceBackedExecutionAtom) Execute(ctx context.Context, deps ApplyDeps, hooks InstallPlanHooks) error {
	if err := a.requireMinted(); err != nil {
		return err
	}
	return a.plan.Apply(ctx, deps, hooks)
}

// Verify checks the complete immutable target captured by this atom. Delta
// execution changes only the write set; it never weakens activation proof.
func (a *SourceBackedExecutionAtom) Verify(ctx context.Context, deps VerifyDeps) error {
	if err := a.requireMinted(); err != nil {
		return err
	}
	return verifyPackComponents(ctx, deps, append([]PackComponent(nil), a.verify...))
}

func (a *SourceBackedExecutionAtom) requireMinted() error {
	if a == nil || !a.minted || a.identity == nil || a.identity.requireMinted() != nil {
		return fmt.Errorf("source-backed execution atom was not minted by packs")
	}
	return nil
}
