package controlplane

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"time"

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

const applyAllSourceRevisionAdvanceCompensationStepTimeout = 10 * time.Minute

type applyAllSourceRevisionAdvanceCompensationOperations struct {
	restoreConnectedApps func(
		context.Context, *packs.ConnectedAppPackSnapshot, packs.ApplyDeps,
	) error
	restoreChildCloneRules func(
		context.Context, *packs.ObjectSpecChildCloneRulePackOwnershipSnapshot, *sql.DB,
	) error
	restoreExclusiveSiblingFields func(
		context.Context, *packs.ObjectSpecExclusiveSiblingFieldPackOwnershipSnapshot, *sql.DB,
	) error
	failAttempt            func(context.Context, applyAllSourceRevisionAdvanceAttempt, error) error
	restoreRuntimeMetadata func(
		context.Context, *packs.ActiveInstalledRuntimeSnapshot,
		map[string]packs.Pack,
		packs.ComponentKindRegistrationSet,
	) error
	refreshFormulaView func(*analytics.FormulaViewService) error
}

// compensateApplyAllSourceRevisionAdvanceExecution restores the narrow
// source-backed snapshots, records the exact installing incarnation as failed,
// and restores the frozen process-local runtime baseline before returning the
// failure to any caller. Generic install-plan component writes remain subject
// to the existing idempotent retry contract.
func compensateApplyAllSourceRevisionAdvanceExecution(
	ctx context.Context,
	registration applyAllSourceRevisionAdvanceRuntimeRegistration,
	executionErr error,
) error {
	return compensateApplyAllSourceRevisionAdvanceExecutionWith(
		ctx, registration, executionErr,
		defaultApplyAllSourceRevisionAdvanceCompensationOperations(),
	)
}

func compensateApplyAllSourceRevisionAdvanceExecutionWith(
	ctx context.Context,
	registration applyAllSourceRevisionAdvanceRuntimeRegistration,
	executionErr error,
	operations applyAllSourceRevisionAdvanceCompensationOperations,
) error {
	state, err := requireApplyAllSourceRevisionAdvanceRuntimeRegistration(registration)
	if err != nil {
		if executionErr == nil {
			return err
		}
		return errors.Join(executionErr, err)
	}
	if executionErr == nil {
		return applyAllSourceRevisionAdvanceAttemptError(
			"recover", state.snapshots.attempt.advance,
			fmt.Errorf("execution error is required"),
		)
	}
	if err := operations.validate(); err != nil {
		return applyAllSourceRevisionAdvanceAttemptError(
			"recover", state.snapshots.attempt.advance,
			errors.Join(executionErr, err),
		)
	}

	snapshots := registration.identity.snapshots
	attempt := snapshots.identity.attempt
	proof := state.snapshots.attempt.identity.admission.identity.proof
	runtime := proof.executionRuntime
	frozenOverrides, frozenOverridesErr := applyAllSourceRevisionAdvanceFrozenRuntimeOverrides(
		ctx, proof.activeRuntimeIdentity.snapshot, proof.entries,
	)
	deps := runtime.applyDeps
	deps.Options = attempt.identity.applyOptions
	recoveryErrors := []error{executionErr}
	runStep := func(label string, operation func(context.Context, error) error) {
		cause := errors.Join(recoveryErrors...)
		stepCtx, cancel := context.WithTimeout(
			context.WithoutCancel(ctx),
			applyAllSourceRevisionAdvanceCompensationStepTimeout,
		)
		stepErr := operation(stepCtx, cause)
		cancel()
		if stepErr != nil {
			recoveryErrors = append(recoveryErrors, fmt.Errorf("%s: %w", label, stepErr))
		}
	}

	runStep("restore ConnectedApp source-backed state", func(stepCtx context.Context, _ error) error {
		return operations.restoreConnectedApps(stepCtx, state.snapshots.connectedApps, deps)
	})
	runStep("restore ChildCloneRules pack ownership sidecars", func(stepCtx context.Context, _ error) error {
		return operations.restoreChildCloneRules(
			stepCtx, state.snapshots.childCloneRules, state.snapshots.attempt.database,
		)
	})
	runStep("restore ExclusiveSiblingFields pack ownership sidecars", func(stepCtx context.Context, _ error) error {
		return operations.restoreExclusiveSiblingFields(
			stepCtx, state.snapshots.exclusiveSiblingFields, state.snapshots.attempt.database,
		)
	})
	runStep("mark exact installing incarnation failed", func(stepCtx context.Context, cause error) error {
		return operations.failAttempt(stepCtx, attempt, cause)
	})
	runStep("restore frozen active runtime metadata", func(stepCtx context.Context, _ error) error {
		if frozenOverridesErr != nil {
			return frozenOverridesErr
		}
		if err := operations.restoreRuntimeMetadata(
			stepCtx, proof.activeRuntimeIdentity.snapshot,
			frozenOverrides,
			runtime.componentKindRegistrationSet,
		); err != nil {
			return err
		}
		if runtime.applyDeps.FormulaView == nil {
			return nil
		}
		if err := operations.refreshFormulaView(runtime.applyDeps.FormulaView); err != nil {
			return fmt.Errorf("refresh formula view function registry: %w", err)
		}
		return nil
	})

	return applyAllSourceRevisionAdvanceAttemptError(
		"recover", state.snapshots.attempt.advance,
		errors.Join(recoveryErrors...),
	)
}

func defaultApplyAllSourceRevisionAdvanceCompensationOperations() applyAllSourceRevisionAdvanceCompensationOperations {
	return applyAllSourceRevisionAdvanceCompensationOperations{
		restoreConnectedApps: func(
			ctx context.Context,
			snapshot *packs.ConnectedAppPackSnapshot,
			deps packs.ApplyDeps,
		) error {
			return snapshot.Restore(ctx, deps)
		},
		restoreChildCloneRules: func(
			ctx context.Context,
			snapshot *packs.ObjectSpecChildCloneRulePackOwnershipSnapshot,
			db *sql.DB,
		) error {
			return snapshot.Restore(ctx, db)
		},
		restoreExclusiveSiblingFields: func(
			ctx context.Context,
			snapshot *packs.ObjectSpecExclusiveSiblingFieldPackOwnershipSnapshot,
			db *sql.DB,
		) error {
			return snapshot.Restore(ctx, db)
		},
		failAttempt: failApplyAllSourceRevisionAdvanceAttempt,
		restoreRuntimeMetadata: func(
			ctx context.Context,
			snapshot *packs.ActiveInstalledRuntimeSnapshot,
			activeOverrides map[string]packs.Pack,
			registrations packs.ComponentKindRegistrationSet,
		) error {
			return snapshot.RegisterRuntimeMetadataForInstallingPacksWithOverrides(
				ctx, nil, activeOverrides,
				registrations,
			)
		},
		refreshFormulaView: func(service *analytics.FormulaViewService) error {
			return service.RefreshRuntimeFunctionRegistry()
		},
	}
}

func (o applyAllSourceRevisionAdvanceCompensationOperations) validate() error {
	if o.restoreConnectedApps == nil || o.restoreChildCloneRules == nil ||
		o.restoreExclusiveSiblingFields == nil || o.failAttempt == nil ||
		o.restoreRuntimeMetadata == nil || o.refreshFormulaView == nil {
		return fmt.Errorf("compensation operations are incomplete")
	}
	return nil
}
