package controlplane

import (
	"context"
	"database/sql"
	"fmt"
	"strings"

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

type applyAllSourceRevisionInstalledTargetResolver func(
	context.Context,
	*sql.DB,
	installedPackRow,
	string,
	packs.PackSourceOptions,
	[]formula.FunctionBundle,
	bool,
	packs.ComponentKindRegistrationSet,
	...packs.SourceBackedPackOption,
) (*packs.SourceBackedPack, error)

var resolveApplyAllSourceRevisionInstalledTarget applyAllSourceRevisionInstalledTargetResolver = resolveApplyAllSourceRevisionInstalledTargetFromFrozenContext

func resolveApplyAllSourceRevisionInstalledTargetFromFrozenContext(
	_ context.Context,
	_ *sql.DB,
	row installedPackRow,
	sourceRevision string,
	sourceOptions packs.PackSourceOptions,
	formulaFunctionBundlesForValidation []formula.FunctionBundle,
	hasFormulaFunctionBundlesForValidation bool,
	registrations packs.ComponentKindRegistrationSet,
	opts ...packs.SourceBackedPackOption,
) (*packs.SourceBackedPack, error) {
	// The desired source tuple was already validated without a live-schema
	// fallback. Reconstruct its installed compatibility role from the same
	// source/dependency facts instead of observing mutable database schema.
	sourceOptions.ObjectSchemaResolver = nil
	pack, err := packs.ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptionsAndFormulaBundlesAndComponentKindRegistrationSet(
		row.Name, packs.Category(strings.TrimSpace(row.Category)), row.ManifestFormatVersion.String,
		row.SourceKind.String, row.SourceURI.String, sourceRevision, sourceOptions,
		formulaFunctionBundlesForValidation, hasFormulaFunctionBundlesForValidation, registrations, opts...,
	)
	if err != nil {
		return nil, fmt.Errorf("installed pack %s: %w", row.Name, err)
	}
	return pack, nil
}

type applyAllSourceRevisionFormulaContextResolver func(
	context.Context,
	*packs.ActiveInstalledRuntimeSnapshot,
	[]packs.Pack,
	packs.ComponentKindRegistrationSet,
) ([]formula.FunctionBundle, error)

var resolveApplyAllSourceRevisionFormulaContext applyAllSourceRevisionFormulaContextResolver = func(
	ctx context.Context,
	snapshot *packs.ActiveInstalledRuntimeSnapshot,
	installing []packs.Pack,
	registrations packs.ComponentKindRegistrationSet,
) ([]formula.FunctionBundle, error) {
	return snapshot.RuntimeFormulaFunctionBundlesForInstall(
		ctx, installing, offlineCandidateMixedMaterializationRuntimeOverrides(ctx, nil), registrations,
	)
}

// A mixed candidate may advance one immutable pack before replacing an active
// mutable peer. The peer's checkout already contains the frozen target, so it
// can no longer be reopened as the old active payload. Use the candidate's
// already-validated target only for runtime-declaration composition; lifecycle
// and dependency checks still observe the database transition order.
func offlineCandidateMixedMaterializationRuntimeOverrides(
	ctx context.Context,
	base map[string]packs.Pack,
) map[string]packs.Pack {
	if ctx == nil {
		return base
	}
	execution, _ := ctx.Value(offlineCandidateContextKey{}).(*offlineCandidateExecution)
	if execution == nil || !execution.plan.isMixedTransition() {
		return base
	}
	overrides := make(map[string]packs.Pack, len(base)+len(execution.plan.materialize))
	for name, pack := range base {
		overrides[name] = pack
	}
	for _, transition := range execution.plan.materialize {
		if transition.target.pack == nil {
			continue
		}
		overrides[strings.ToLower(strings.TrimSpace(transition.target.pack.Name()))] = transition.target.pack
	}
	return overrides
}

// applyAllSourceRevisionAdvanceActiveRuntimeObservation binds one opaque
// active-runtime snapshot to the exact runner database that minted it.
type applyAllSourceRevisionAdvanceActiveRuntimeObservation struct {
	identity *applyAllSourceRevisionAdvanceActiveRuntimeIdentity
	minted   bool
}

// applyAllSourceRevisionAdvanceActiveRuntimeIdentity is the non-zero opaque
// database-and-snapshot lineage shared by one observation and every capability
// derived from it.
type applyAllSourceRevisionAdvanceActiveRuntimeIdentity struct {
	db       *sql.DB
	snapshot *packs.ActiveInstalledRuntimeSnapshot
}

func readApplyAllSourceRevisionAdvanceActiveRuntimeObservation(
	ctx context.Context,
	db *sql.DB,
) (applyAllSourceRevisionAdvanceActiveRuntimeObservation, error) {
	if db == nil {
		return applyAllSourceRevisionAdvanceActiveRuntimeObservation{}, fmt.Errorf("runner database connection is unavailable")
	}
	snapshot, err := packs.ReadActiveInstalledRuntimeSnapshot(ctx, db)
	if err != nil {
		return applyAllSourceRevisionAdvanceActiveRuntimeObservation{}, err
	}
	return applyAllSourceRevisionAdvanceActiveRuntimeObservation{
		identity: &applyAllSourceRevisionAdvanceActiveRuntimeIdentity{db: db, snapshot: snapshot},
		minted:   true,
	}, nil
}

func (o applyAllSourceRevisionAdvanceActiveRuntimeObservation) databaseForRunner(r *Runner) (*sql.DB, error) {
	return o.requireDatabase(runnerSQLDB(r))
}

func (o applyAllSourceRevisionAdvanceActiveRuntimeObservation) requireDatabase(db *sql.DB) (*sql.DB, error) {
	if db == nil {
		return nil, fmt.Errorf("runner database connection is unavailable")
	}
	identity, err := o.identityToken()
	if err != nil {
		return nil, err
	}
	return identity.requireDatabase(db)
}

func (o applyAllSourceRevisionAdvanceActiveRuntimeObservation) identityToken() (*applyAllSourceRevisionAdvanceActiveRuntimeIdentity, error) {
	if !o.minted || o.identity == nil || o.identity.db == nil || o.identity.snapshot == nil {
		return nil, fmt.Errorf("active runtime observation was not minted by its reader")
	}
	return o.identity, nil
}

func (i *applyAllSourceRevisionAdvanceActiveRuntimeIdentity) requireDatabase(db *sql.DB) (*sql.DB, error) {
	if db == nil {
		return nil, fmt.Errorf("runner database connection is unavailable")
	}
	if i == nil || i.db == nil || i.snapshot == nil {
		return nil, fmt.Errorf("active runtime identity was not minted by its reader")
	}
	if db != i.db {
		return nil, fmt.Errorf("active runtime observation belongs to a different runner database")
	}
	return db, nil
}

// resolveApplyAllSourceRevisionAdvanceInstalledTuple rebuilds the already
// pinned source tuple through the exact installed rows and active-runtime
// observation captured by the precondition proof.
func resolveApplyAllSourceRevisionAdvanceInstalledTuple(
	ctx context.Context,
	r *Runner,
	precondition applyAllSourceRevisionAdvancePreconditionProof,
	sourceTuple applyAllSourceRevisionAdvanceSourceTuple,
) (applyAllSourceRevisionAdvanceInstalledCompatibilityTuple, error) {
	if sourceTuple.plan == nil || sourceTuple.targets == nil || !sourceTuple.hasSourceOptions {
		return applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{}, applyAllSourceRevisionAdvancePlanError(
			ApplyAllSourceRevisionAdvancePlanCompose, -1, "", fmt.Errorf("source tuple was not minted by the source resolver"),
		)
	}
	if err := validateApplyAllSourceRevisionAdvanceFrozenCurrentTargetFacts(precondition); err != nil {
		return applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{}, err
	}
	if len(sourceTuple.plan) == 0 {
		if len(sourceTuple.targets) != 0 {
			return applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanCompose, -1, "", fmt.Errorf("empty source tuple has resolved targets"),
			)
		}
		if len(precondition.snapshot.entries) != 0 {
			return applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanCompose, -1, "", fmt.Errorf("empty source tuple does not match frozen precondition"),
			)
		}
		return applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{
			applyAllSourceRevisionAdvanceTuple: applyAllSourceRevisionAdvanceTuple{
				plan:    []normalizedApplyAllSourceRevisionAdvance{},
				targets: map[string]*packs.SourceBackedPack{},
			},
		}, nil
	}
	ordered, err := orderApplyAllSourceRevisionAdvanceTuple(sourceTuple.plan, sourceTuple.targets)
	if err != nil {
		return applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{}, applyAllSourceRevisionAdvancePlanError(
			ApplyAllSourceRevisionAdvancePlanCompose, -1, "", fmt.Errorf("validate source tuple: %w", err),
		)
	}
	if len(precondition.snapshot.entries) != len(ordered) {
		return applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{}, applyAllSourceRevisionAdvancePlanError(
			ApplyAllSourceRevisionAdvancePlanCompose, -1, "", fmt.Errorf("source tuple and frozen precondition have different lengths"),
		)
	}
	orderedSnapshot := applyAllSourceRevisionAdvanceCurrentSnapshot{
		entries: make([]applyAllSourceRevisionAdvanceCurrentSnapshotEntry, 0, len(ordered)),
	}
	allowedOwnerlessObjectAPIs := make(map[string][]string, len(ordered))
	for _, advance := range ordered {
		name := strings.ToLower(strings.TrimSpace(advance.Name))
		entry, ok := precondition.snapshot.entry(advance.Name)
		if !ok || entry.advance != advance {
			return applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanCompose, advance.index, advance.Name,
				fmt.Errorf("source tuple does not match frozen precondition"),
			)
		}
		orderedSnapshot.entries = append(orderedSnapshot.entries, entry)
		currentOwned := lowerSet(precondition.current.targets[name].OwnedObjectAPINames())
		for _, objectAPIName := range sourceTuple.targets[name].OwnedObjectAPINames() {
			if currentOwned[strings.ToLower(strings.TrimSpace(objectAPIName))] {
				allowedOwnerlessObjectAPIs[name] = append(allowedOwnerlessObjectAPIs[name], objectAPIName)
			}
		}
	}
	tuple, err := resolveApplyAllSourceRevisionAdvanceInstalledTupleFromSnapshot(
		ctx,
		r,
		orderedSnapshot,
		applyAllSourceRevisionAdvanceSourceResolution{
			targets:       sourceTuple.targets,
			sourceOptions: packSourceOptionsForRequestedSourceValidation(sourceTuple.sourceOptions),
		},
		precondition.activeRuntime,
		allowedOwnerlessObjectAPIs,
	)
	if err != nil {
		return applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{}, err
	}
	ordered, err = orderApplyAllSourceRevisionAdvanceTuple(tuple.plan, tuple.targets)
	if err != nil {
		return applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{}, applyAllSourceRevisionAdvancePlanError(
			ApplyAllSourceRevisionAdvancePlanCompose, -1, "", fmt.Errorf("order installed target tuple: %w", err),
		)
	}
	activeRuntimeIdentity, err := precondition.activeRuntime.identityToken()
	if err != nil {
		return applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{}, applyAllSourceRevisionAdvancePlanError(
			ApplyAllSourceRevisionAdvancePlanCompose, -1, "", err,
		)
	}
	return applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{
		applyAllSourceRevisionAdvanceTuple: applyAllSourceRevisionAdvanceTuple{plan: ordered, targets: tuple.targets},
		activeRuntimeIdentity:              activeRuntimeIdentity,
	}, nil
}

func resolveApplyAllSourceRevisionAdvanceCurrentInstalledTuple(
	ctx context.Context,
	r *Runner,
	snapshot applyAllSourceRevisionAdvanceCurrentSnapshot,
	activeRuntime applyAllSourceRevisionAdvanceActiveRuntimeObservation,
) (applyAllSourceRevisionAdvanceTuple, error) {
	if len(snapshot.entries) == 0 {
		return applyAllSourceRevisionAdvanceTuple{
			plan:    []normalizedApplyAllSourceRevisionAdvance{},
			targets: map[string]*packs.SourceBackedPack{},
		}, nil
	}
	if _, err := activeRuntime.databaseForRunner(r); err != nil {
		return applyAllSourceRevisionAdvanceTuple{}, applyAllSourceRevisionAdvancePlanError(
			ApplyAllSourceRevisionAdvancePlanCompose, -1, "", err,
		)
	}
	plan := make([]normalizedApplyAllSourceRevisionAdvance, 0, len(snapshot.entries))
	for _, entry := range snapshot.entries {
		plan = append(plan, entry.advance)
	}
	sourceOptions, ok := packs.OperationPackSourceOptions(ctx)
	if !ok {
		sourceOptions = packs.DefaultPackSourceOptions()
	}
	resolution, err := resolveApplyAllSourceRevisionAdvanceSourcesAtRevisionsWithBase(
		plan, snapshot.currentRevisions(), r.componentKindRegistrationSet(), sourceOptions,
	)
	if err != nil {
		return applyAllSourceRevisionAdvanceTuple{}, err
	}
	return resolveApplyAllSourceRevisionAdvanceInstalledTupleFromSnapshot(ctx, r, snapshot, resolution, activeRuntime, nil)
}

func resolveApplyAllSourceRevisionAdvanceInstalledTupleFromSnapshot(
	ctx context.Context,
	r *Runner,
	snapshot applyAllSourceRevisionAdvanceCurrentSnapshot,
	resolution applyAllSourceRevisionAdvanceSourceResolution,
	activeRuntime applyAllSourceRevisionAdvanceActiveRuntimeObservation,
	allowedOwnerlessObjectAPIs map[string][]string,
) (applyAllSourceRevisionAdvanceTuple, error) {
	db, err := activeRuntime.databaseForRunner(r)
	if err != nil {
		return applyAllSourceRevisionAdvanceTuple{}, applyAllSourceRevisionAdvancePlanError(
			ApplyAllSourceRevisionAdvancePlanCompose, -1, "", err,
		)
	}
	if len(snapshot.entries) != len(resolution.targets) {
		return applyAllSourceRevisionAdvanceTuple{}, applyAllSourceRevisionAdvancePlanError(
			ApplyAllSourceRevisionAdvancePlanCompose, -1, "", fmt.Errorf("snapshot has %d entries but resolution has %d targets", len(snapshot.entries), len(resolution.targets)),
		)
	}

	plan := make([]normalizedApplyAllSourceRevisionAdvance, 0, len(snapshot.entries))
	installing := make([]packs.Pack, 0, len(snapshot.entries))
	for _, entry := range snapshot.entries {
		advance := entry.advance
		revision := applyAllSourceRevisionFromOptions(advance, resolution.sourceOptions)
		expected := advance
		expected.ToRevision = revision
		target := resolution.targets[strings.ToLower(advance.Name)]
		if revision == "" {
			return applyAllSourceRevisionAdvanceTuple{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanCompose, advance.index, advance.Name, fmt.Errorf("exact requested source endpoint is missing"),
			)
		}
		if err := validateApplyAllSourceRevisionAdvanceTarget(expected, target); err != nil {
			return applyAllSourceRevisionAdvanceTuple{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanCompose, advance.index, advance.Name, err,
			)
		}
		plan = append(plan, advance)
		installing = append(installing, target)
	}

	registrations := r.componentKindRegistrationSet()
	bundles, err := resolveApplyAllSourceRevisionFormulaContext(ctx, activeRuntime.identity.snapshot, installing, registrations)
	if err != nil {
		return applyAllSourceRevisionAdvanceTuple{}, applyAllSourceRevisionAdvancePlanError(
			ApplyAllSourceRevisionAdvancePlanCompose, -1, "", fmt.Errorf("load source revision tuple formula context: %w", err),
		)
	}
	targets := make(map[string]*packs.SourceBackedPack, len(snapshot.entries))
	for _, entry := range snapshot.entries {
		advance := entry.advance
		name := strings.ToLower(strings.TrimSpace(advance.Name))
		revision := applyAllSourceRevisionFromOptions(advance, resolution.sourceOptions)
		var targetOptions []packs.SourceBackedPackOption
		if allowedOwnerlessObjectAPIs != nil {
			targetOptions = append(
				targetOptions,
				packs.WithAllowedOwnerlessObjectAPIs(allowedOwnerlessObjectAPIs[name]),
			)
		}
		target, err := resolveApplyAllSourceRevisionInstalledTarget(
			ctx, db, entry.row, revision, resolution.sourceOptions, bundles, true, registrations,
			targetOptions...,
		)
		if err != nil {
			return applyAllSourceRevisionAdvanceTuple{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanCompose, advance.index, advance.Name, err,
			)
		}
		expected := advance
		expected.ToRevision = revision
		if err := validateApplyAllSourceRevisionAdvanceTarget(expected, target); err != nil {
			return applyAllSourceRevisionAdvanceTuple{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanCompose, advance.index, advance.Name, err,
			)
		}
		targets[name] = target
	}
	return applyAllSourceRevisionAdvanceTuple{
		plan:    append([]normalizedApplyAllSourceRevisionAdvance(nil), plan...),
		targets: targets,
	}, nil
}
