package controlplane

import (
	"context"
	"database/sql"
	"fmt"
	"sync/atomic"

	connectorcapabilities "github.com/pingcap-inc/nexuscrm-connectors/features/connector/capabilities"
	handlerconfig "github.com/pingcap-inc/nexuscrm-connectors/features/connector/inboundhandlerconfig"
	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

type applyAllSourceRevisionAdvanceActivationClaim struct {
	claimed atomic.Bool
}

// applyAllSourceRevisionAdvanceActivationReadiness is the opaque proof that
// the exact target completed the same cache, validator, layout, derived-
// artifact, handler-config, and post-maintenance verification chain as an
// ordinary source-backed install while its row was still installing.
type applyAllSourceRevisionAdvanceActivationReadiness struct {
	identity *applyAllSourceRevisionAdvanceActivationReadinessIdentity
	witness  applyAllSourceRevisionAdvanceActivationReadinessProvenance
}

type applyAllSourceRevisionAdvanceActivationReadinessIdentity struct {
	execution       applyAllSourceRevisionAdvanceExecution
	activationClaim *applyAllSourceRevisionAdvanceActivationClaim
	provenance      *applyAllSourceRevisionAdvanceActivationReadinessProvenance
	minted          bool
}

type applyAllSourceRevisionAdvanceActivationReadinessProvenance struct {
	executionIdentity *applyAllSourceRevisionAdvanceExecutionIdentity
	preparationClaim  *applyAllSourceRevisionAdvanceFinalizationClaim
	activationClaim   *applyAllSourceRevisionAdvanceActivationClaim
}

type applyAllSourceRevisionAdvanceActivationReadinessState struct {
	execution       applyAllSourceRevisionAdvanceExecutionState
	activationClaim *applyAllSourceRevisionAdvanceActivationClaim
}

type applyAllSourceRevisionAdvanceActivationPreparationOperations struct {
	refreshCache            func(context.Context, *Runner) error
	validateInsertRules     func(context.Context, *Runner) error
	verifyExecutionAtom     func(context.Context, *packs.SourceBackedExecutionAtom, packs.VerifyDeps) error
	registerRuntimeMetadata applyAllSourceRevisionAdvanceRuntimeMetadataRegistrar
	runLayoutMaintenance    func(*sql.DB) error
	materializeDerived      func(context.Context, *Runner, *packs.SourceBackedPack, bool) error
	registerHandlerConfigs  func(context.Context, *sql.DB, string) error
	compensate              func(
		context.Context,
		applyAllSourceRevisionAdvanceRuntimeRegistration,
		error,
	) error
}

// prepareApplyAllSourceRevisionAdvanceActivation consumes a successful atom
// execution exactly once and mints the only carrier accepted by exact
// activation. Any failure before that mint enters mandatory compensation, so
// the fresh installation incarnation cannot remain active but incomplete.
func prepareApplyAllSourceRevisionAdvanceActivation(
	ctx context.Context,
	r *Runner,
	execution applyAllSourceRevisionAdvanceExecution,
	candidateFinalVerify CandidateFinalVerifier,
) (applyAllSourceRevisionAdvanceActivationReadiness, error) {
	return prepareApplyAllSourceRevisionAdvanceActivationWith(
		ctx,
		r,
		execution,
		candidateFinalVerify,
		defaultApplyAllSourceRevisionAdvanceActivationPreparationOperations(),
	)
}

func prepareApplyAllSourceRevisionAdvanceActivationWith(
	ctx context.Context,
	r *Runner,
	execution applyAllSourceRevisionAdvanceExecution,
	candidateFinalVerify CandidateFinalVerifier,
	operations applyAllSourceRevisionAdvanceActivationPreparationOperations,
) (applyAllSourceRevisionAdvanceActivationReadiness, error) {
	state, err := requireApplyAllSourceRevisionAdvanceExecution(execution)
	if err != nil {
		return applyAllSourceRevisionAdvanceActivationReadiness{}, err
	}
	registration := execution.identity.registration
	attempt := state.registration.snapshots.attempt
	advance := attempt.advance
	proof := attempt.identity.admission.identity.proof
	runtime := proof.executionRuntime
	if err := operations.validate(); err != nil {
		return applyAllSourceRevisionAdvanceActivationReadiness{},
			applyAllSourceRevisionAdvanceAttemptError("prepare activation", advance, err)
	}
	if r == nil || runtime == nil || r != runtime.runner || runnerSQLDB(r) != runtime.database {
		return applyAllSourceRevisionAdvanceActivationReadiness{},
			applyAllSourceRevisionAdvanceAttemptError(
				"prepare activation", advance,
				fmt.Errorf("activation preparation runner changed from the exact execution runtime"),
			)
	}
	if !state.finalizationClaim.claimed.CompareAndSwap(false, true) {
		return applyAllSourceRevisionAdvanceActivationReadiness{},
			applyAllSourceRevisionAdvanceAttemptError(
				"prepare activation", advance,
				fmt.Errorf("successful execution was already prepared for activation"),
			)
	}

	fail := func(preparationErr error) (applyAllSourceRevisionAdvanceActivationReadiness, error) {
		return applyAllSourceRevisionAdvanceActivationReadiness{},
			compensateApplyAllSourceRevisionAdvanceOperationError(
				ctx,
				registration,
				advance,
				"prepare activation",
				preparationErr,
				operations.compensate,
			)
	}
	if err := operations.refreshCache(ctx, r); err != nil {
		return fail(fmt.Errorf("refresh cache after exact atom execution: %w", err))
	}
	if err := operations.validateInsertRules(ctx, r); err != nil {
		return fail(fmt.Errorf("validate insert rules after exact atom execution: %w", err))
	}

	target := attempt.target
	entry := proof.entries[attempt.identity.selectedProofIndex]
	verifyCtx := applyAllSourceRevisionAdvancePostExecutionVerificationContext(
		ctx,
		attempt,
	)
	installing := make([]packs.Pack, len(proof.entries))
	for index, entry := range proof.entries {
		installing[index] = entry.installedCompatibility
	}
	if err := prepareAppliedAppPacksForActivationWithVerifier(
		verifyCtx,
		runtime.database,
		[]appliedAppPack{{pack: target, payloadHash: attempt.facts.PayloadHash}},
		func(ctx context.Context, _ *sql.DB, _ []packs.Pack) error {
			return operations.registerRuntimeMetadata(
				ctx,
				proof.activeRuntimeIdentity.snapshot,
				installing,
				nil,
				runtime.componentKindRegistrationSet,
			)
		},
		operations.runLayoutMaintenance,
		func(ctx context.Context) error {
			// Read the defer flag from the attempt identity's provenance-checked
			// applyOptions — the same authority execution and compensation use
			// (deps.Options = attempt.identity.applyOptions). The frozen
			// runtime's applyDeps carries default PackDeps options and must
			// not decide cutover behavior.
			if err := operations.materializeDerived(ctx, r, target, attempt.identity.applyOptions.DeferFormulaViewRebuild); err != nil {
				return err
			}
			return operations.registerHandlerConfigs(ctx, runtime.database, target.Name())
		},
		candidateFinalVerifyWithRunner(r, candidateFinalVerify),
		func(ctx context.Context, appPack appliedAppPack) error {
			if appPack.pack != target {
				return fmt.Errorf(
					"source revision activation verifier received a different target %T",
					appPack.pack,
				)
			}
			return operations.verifyExecutionAtom(
				ctx,
				entry.atom,
				packs.VerifyDeps{DB: runtime.database, DeepChecks: false},
			)
		},
	); err != nil {
		return fail(err)
	}

	activationClaim := &applyAllSourceRevisionAdvanceActivationClaim{}
	identity := &applyAllSourceRevisionAdvanceActivationReadinessIdentity{
		execution:       execution,
		activationClaim: activationClaim,
		minted:          true,
	}
	identity.provenance = &applyAllSourceRevisionAdvanceActivationReadinessProvenance{
		executionIdentity: execution.identity,
		preparationClaim:  state.finalizationClaim,
		activationClaim:   activationClaim,
	}
	readiness := applyAllSourceRevisionAdvanceActivationReadiness{
		identity: identity,
		witness:  *identity.provenance,
	}
	if _, err := requireApplyAllSourceRevisionAdvanceActivationReadiness(readiness); err != nil {
		return fail(err)
	}
	return readiness, nil
}

func applyAllSourceRevisionAdvancePostExecutionVerificationContext(
	ctx context.Context,
	attempt applyAllSourceRevisionAdvanceAttemptState,
) context.Context {
	proof := attempt.identity.admission.identity.proof
	overrides := make(map[string]*packs.SourceBackedPack, len(proof.entries))
	for index, entry := range proof.entries {
		if index == attempt.identity.selectedProofIndex {
			overrides[entry.name] = entry.installedCompatibility
			continue
		}
		if entry.current.row.Status == installedPackStatusActive && entry.current.row.IsDeleted == 0 {
			overrides[entry.name] = entry.currentTarget
		}
	}
	return packs.WithExactPeerDeclarationOverrides(ctx, overrides)
}

func requireApplyAllSourceRevisionAdvanceActivationReadiness(
	readiness applyAllSourceRevisionAdvanceActivationReadiness,
) (applyAllSourceRevisionAdvanceActivationReadinessState, error) {
	identity := readiness.identity
	if identity == nil || !identity.minted || identity.provenance == nil {
		return applyAllSourceRevisionAdvanceActivationReadinessState{}, fmt.Errorf(
			"activation readiness was not minted by post-execution preparation",
		)
	}
	if identity.execution.identity == nil ||
		identity.execution.identity != identity.provenance.executionIdentity ||
		identity.execution.identity != readiness.witness.executionIdentity {
		return applyAllSourceRevisionAdvanceActivationReadinessState{}, fmt.Errorf(
			"activation readiness changed execution identity",
		)
	}
	if identity.execution.identity.finalizationClaim == nil ||
		identity.execution.identity.finalizationClaim != identity.provenance.preparationClaim ||
		identity.execution.identity.finalizationClaim != readiness.witness.preparationClaim ||
		!identity.execution.identity.finalizationClaim.claimed.Load() {
		return applyAllSourceRevisionAdvanceActivationReadinessState{}, fmt.Errorf(
			"activation readiness changed or did not consume its preparation claim",
		)
	}
	if identity.activationClaim == nil ||
		identity.activationClaim != identity.provenance.activationClaim ||
		identity.activationClaim != readiness.witness.activationClaim {
		return applyAllSourceRevisionAdvanceActivationReadinessState{}, fmt.Errorf(
			"activation readiness changed its one-shot activation claim",
		)
	}
	executionState, err := requireApplyAllSourceRevisionAdvanceExecution(identity.execution)
	if err != nil {
		return applyAllSourceRevisionAdvanceActivationReadinessState{}, fmt.Errorf(
			"validate activation readiness execution: %w", err,
		)
	}
	return applyAllSourceRevisionAdvanceActivationReadinessState{
		execution:       executionState,
		activationClaim: identity.activationClaim,
	}, nil
}

func defaultApplyAllSourceRevisionAdvanceActivationPreparationOperations() applyAllSourceRevisionAdvanceActivationPreparationOperations {
	return applyAllSourceRevisionAdvanceActivationPreparationOperations{
		refreshCache: func(ctx context.Context, r *Runner) error {
			return r.RefreshCache(ctx)
		},
		validateInsertRules: runInsertRulesValidators,
		verifyExecutionAtom: func(
			ctx context.Context,
			atom *packs.SourceBackedExecutionAtom,
			deps packs.VerifyDeps,
		) error {
			return atom.Verify(ctx, deps)
		},
		registerRuntimeMetadata: func(
			ctx context.Context,
			snapshot *packs.ActiveInstalledRuntimeSnapshot,
			installing []packs.Pack,
			activeOverrides map[string]packs.Pack,
			registrations packs.ComponentKindRegistrationSet,
		) error {
			return snapshot.RegisterRuntimeMetadataForInstallingPacksWithOverrides(
				ctx, installing,
				offlineCandidateMixedMaterializationRuntimeOverrides(ctx, activeOverrides),
				registrations,
			)
		},
		runLayoutMaintenance: runLayoutMaintenance,
		materializeDerived:   materializeSourceBackedPackDerived,
		registerHandlerConfigs: func(ctx context.Context, db *sql.DB, packName string) error {
			return handlerconfig.RegisterHandlerConfigsForPack(
				ctx,
				db,
				connectorcapabilities.Default(),
				packName,
			)
		},
		compensate: compensateApplyAllSourceRevisionAdvanceExecution,
	}
}

func (o applyAllSourceRevisionAdvanceActivationPreparationOperations) validate() error {
	if o.refreshCache == nil || o.validateInsertRules == nil || o.verifyExecutionAtom == nil ||
		o.registerRuntimeMetadata == nil || o.runLayoutMaintenance == nil ||
		o.materializeDerived == nil || o.registerHandlerConfigs == nil ||
		o.compensate == nil {
		return fmt.Errorf("activation preparation operations are incomplete")
	}
	return nil
}
