package controlplane

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

const applyAllSourceRevisionAdvanceRuntimeRestoreTimeout = 10 * time.Minute

// applyAllSourceRevisionAdvanceRuntimeRegistration is an opaque witness that
// the complete admitted target tuple has replaced the frozen active runtime
// context for one exact snapshot-bound attempt. It exposes no execution method
// or lifecycle transition by itself.
type applyAllSourceRevisionAdvanceRuntimeRegistration struct {
	identity *applyAllSourceRevisionAdvanceRuntimeRegistrationIdentity
	witness  applyAllSourceRevisionAdvanceRuntimeRegistrationProvenance
}

type applyAllSourceRevisionAdvanceRuntimeRegistrationIdentity struct {
	snapshots         applyAllSourceRevisionAdvanceSnapshots
	selectedAtomClaim *applyAllSourceRevisionAdvanceSelectedAtomClaim
	provenance        *applyAllSourceRevisionAdvanceRuntimeRegistrationProvenance
	minted            bool
}

type applyAllSourceRevisionAdvanceRuntimeRegistrationProvenance struct {
	snapshotsIdentity *applyAllSourceRevisionAdvanceSnapshotsIdentity
	selectedAtomClaim *applyAllSourceRevisionAdvanceSelectedAtomClaim
}

type applyAllSourceRevisionAdvanceRuntimeRegistrationState struct {
	snapshots applyAllSourceRevisionAdvanceSnapshotsState
}

type applyAllSourceRevisionAdvanceRuntimeMetadataRegistrar func(
	context.Context,
	*packs.ActiveInstalledRuntimeSnapshot,
	[]packs.Pack,
	map[string]packs.Pack,
	packs.ComponentKindRegistrationSet,
) error

// registerApplyAllSourceRevisionAdvanceRuntime registers every installed-
// compatibility target in the retained proof, including at-target entries,
// against the proof-owned frozen active-runtime snapshot. A failed candidate
// registration restores that exact pre-Begin runtime context before returning.
func registerApplyAllSourceRevisionAdvanceRuntime(
	ctx context.Context,
	snapshots applyAllSourceRevisionAdvanceSnapshots,
) (applyAllSourceRevisionAdvanceRuntimeRegistration, error) {
	return registerApplyAllSourceRevisionAdvanceRuntimeWith(
		ctx, snapshots,
		func(
			ctx context.Context,
			snapshot *packs.ActiveInstalledRuntimeSnapshot,
			installing []packs.Pack,
			activeOverrides map[string]packs.Pack,
			registrations packs.ComponentKindRegistrationSet,
		) error {
			return snapshot.RegisterRuntimeMetadataForInstallingPacksWithOverrides(
				ctx, installing,
				applyAllSourceRevisionAdvanceRuntimeRegistrationOverrides(
					ctx, installing, activeOverrides,
				),
				registrations,
			)
		},
	)
}

func applyAllSourceRevisionAdvanceRuntimeRegistrationOverrides(
	ctx context.Context,
	installing []packs.Pack,
	activeOverrides map[string]packs.Pack,
) map[string]packs.Pack {
	if len(installing) == 0 {
		return activeOverrides
	}
	return offlineCandidateMixedMaterializationRuntimeOverrides(ctx, activeOverrides)
}

func registerApplyAllSourceRevisionAdvanceRuntimeWith(
	ctx context.Context,
	snapshots applyAllSourceRevisionAdvanceSnapshots,
	register applyAllSourceRevisionAdvanceRuntimeMetadataRegistrar,
) (applyAllSourceRevisionAdvanceRuntimeRegistration, error) {
	state, err := requireApplyAllSourceRevisionAdvanceSnapshots(snapshots)
	if err != nil {
		return applyAllSourceRevisionAdvanceRuntimeRegistration{}, err
	}
	if register == nil {
		return applyAllSourceRevisionAdvanceRuntimeRegistration{}, fmt.Errorf(
			"runtime metadata registrar is unavailable",
		)
	}
	attempt := state.attempt.identity
	proof := attempt.admission.identity.proof
	runtimeSnapshot := proof.activeRuntimeIdentity.snapshot
	registrations := proof.executionRuntime.componentKindRegistrationSet
	installing := make([]packs.Pack, len(proof.entries))
	for index, entry := range proof.entries {
		installing[index] = entry.installedCompatibility
	}
	frozenOverrides, err := applyAllSourceRevisionAdvanceFrozenRuntimeOverrides(
		ctx, runtimeSnapshot, proof.entries,
	)
	if err != nil {
		return applyAllSourceRevisionAdvanceRuntimeRegistration{},
			applyAllSourceRevisionAdvanceAttemptError(
				"freeze runtime rollback for", state.attempt.advance, err,
			)
	}
	restore := func() error {
		restoreCtx, cancel := context.WithTimeout(
			context.WithoutCancel(ctx),
			applyAllSourceRevisionAdvanceRuntimeRestoreTimeout,
		)
		defer cancel()
		return register(restoreCtx, runtimeSnapshot, nil, frozenOverrides, registrations)
	}
	if err := register(ctx, runtimeSnapshot, installing, nil, registrations); err != nil {
		registrationErr := fmt.Errorf("register complete admitted runtime tuple: %w", err)
		if restoreErr := restore(); restoreErr != nil {
			registrationErr = errors.Join(
				registrationErr,
				fmt.Errorf("restore frozen active runtime metadata: %w", restoreErr),
			)
		}
		return applyAllSourceRevisionAdvanceRuntimeRegistration{},
			applyAllSourceRevisionAdvanceAttemptError(
				"register runtime for", state.attempt.advance, registrationErr,
			)
	}

	identity := &applyAllSourceRevisionAdvanceRuntimeRegistrationIdentity{
		snapshots:         snapshots,
		selectedAtomClaim: snapshots.identity.selectedAtomClaim,
		minted:            true,
	}
	identity.provenance = &applyAllSourceRevisionAdvanceRuntimeRegistrationProvenance{
		snapshotsIdentity: snapshots.identity,
		selectedAtomClaim: identity.selectedAtomClaim,
	}
	registration := applyAllSourceRevisionAdvanceRuntimeRegistration{
		identity: identity,
		witness:  *identity.provenance,
	}
	if _, err := requireApplyAllSourceRevisionAdvanceRuntimeRegistration(registration); err != nil {
		restoreErr := restore()
		if restoreErr != nil {
			err = errors.Join(err, fmt.Errorf("restore frozen active runtime metadata: %w", restoreErr))
		}
		return applyAllSourceRevisionAdvanceRuntimeRegistration{}, err
	}
	return registration, nil
}

func applyAllSourceRevisionAdvanceFrozenRuntimeOverrides(
	ctx context.Context,
	snapshot *packs.ActiveInstalledRuntimeSnapshot,
	entries []applyAllSourceRevisionAdvanceExecutionProofEntry,
) (map[string]packs.Pack, error) {
	overrides := make(map[string]packs.Pack, len(entries))
	if ctx != nil {
		execution, _ := ctx.Value(offlineCandidateContextKey{}).(*offlineCandidateExecution)
		if execution != nil && execution.plan.isMixedTransition() {
			var err error
			overrides, err = snapshot.FrozenPersistedRuntimeDeclarationOverrides()
			if err != nil {
				return nil, err
			}
		}
	}
	for _, entry := range entries {
		// The source-advance proof owns a stronger source-verified current
		// target. Other mixed peers retain their frozen persisted declarations.
		overrides[entry.name] = entry.currentTarget
	}
	return overrides, nil
}

func requireApplyAllSourceRevisionAdvanceRuntimeRegistration(
	registration applyAllSourceRevisionAdvanceRuntimeRegistration,
) (applyAllSourceRevisionAdvanceRuntimeRegistrationState, error) {
	identity := registration.identity
	if identity == nil || !identity.minted || identity.provenance == nil {
		return applyAllSourceRevisionAdvanceRuntimeRegistrationState{}, fmt.Errorf(
			"runtime registration was not minted from source-backed snapshots",
		)
	}
	if identity.snapshots.identity == nil ||
		identity.snapshots.identity != identity.provenance.snapshotsIdentity ||
		identity.snapshots.identity != registration.witness.snapshotsIdentity {
		return applyAllSourceRevisionAdvanceRuntimeRegistrationState{}, fmt.Errorf(
			"runtime registration changed source-backed snapshot identity",
		)
	}
	if identity.selectedAtomClaim == nil ||
		identity.selectedAtomClaim != identity.snapshots.identity.selectedAtomClaim ||
		identity.selectedAtomClaim != identity.provenance.selectedAtomClaim ||
		identity.selectedAtomClaim != registration.witness.selectedAtomClaim {
		return applyAllSourceRevisionAdvanceRuntimeRegistrationState{}, fmt.Errorf(
			"runtime registration changed selected-atom claim",
		)
	}
	snapshotState, err := requireApplyAllSourceRevisionAdvanceSnapshots(identity.snapshots)
	if err != nil {
		return applyAllSourceRevisionAdvanceRuntimeRegistrationState{}, err
	}
	return applyAllSourceRevisionAdvanceRuntimeRegistrationState{snapshots: snapshotState}, nil
}
