package controlplane

import (
	"database/sql"
	"fmt"
	"reflect"

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

// applyAllSourceRevisionAdvanceExecutionRuntime is the immutable runtime
// authority frozen into one nonempty execution proof.
type applyAllSourceRevisionAdvanceExecutionRuntime struct {
	runner                       *Runner
	runnerDatabase               *database.TiDBConnection
	database                     *sql.DB
	serviceGraph                 ServiceGraph
	applyDeps                    packs.ApplyDeps
	componentKindRegistrationSet packs.ComponentKindRegistrationSet
	provenance                   *applyAllSourceRevisionAdvanceExecutionRuntimeProvenance
	minted                       bool
}

type applyAllSourceRevisionAdvanceExecutionRuntimeProvenance struct {
	runner                       *Runner
	runnerDatabase               *database.TiDBConnection
	database                     *sql.DB
	serviceGraph                 ServiceGraph
	applyDeps                    packs.ApplyDeps
	componentKindRegistrationSet packs.ComponentKindRegistrationSet
}

func freezeApplyAllSourceRevisionAdvanceExecutionRuntime(
	r *Runner,
) (*applyAllSourceRevisionAdvanceExecutionRuntime, error) {
	if r == nil {
		return nil, fmt.Errorf("runner database connection is unavailable")
	}
	runnerSnapshot := *r
	if runnerSnapshot.DB == nil || runnerSQLDB(&runnerSnapshot) == nil {
		return nil, fmt.Errorf("runner database connection is unavailable")
	}
	serviceGraph := runnerSnapshot.SvcMgr
	if serviceGraph == nil {
		return nil, fmt.Errorf("runner service graph is unavailable")
	}
	db := runnerSQLDB(&runnerSnapshot)
	if serviceGraph.DB() != db {
		return nil, fmt.Errorf("runner service graph does not own its database")
	}
	registrations, err := runnerSnapshot.componentKindRegistrationSet().Clone()
	if err != nil {
		return nil, fmt.Errorf("freeze component registrations: %w", err)
	}
	deps := runnerSnapshot.PackDeps()
	if deps.DB != db || deps.DBConn != runnerSnapshot.DB {
		return nil, fmt.Errorf("runner apply dependencies do not own its database")
	}
	runtime := &applyAllSourceRevisionAdvanceExecutionRuntime{
		runner: r, runnerDatabase: runnerSnapshot.DB, database: db,
		serviceGraph:                 serviceGraph,
		applyDeps:                    deps,
		componentKindRegistrationSet: registrations,
		minted:                       true,
	}
	runtime.provenance = &applyAllSourceRevisionAdvanceExecutionRuntimeProvenance{
		runner: runtime.runner, runnerDatabase: runtime.runnerDatabase, database: runtime.database,
		serviceGraph:                 runtime.serviceGraph,
		applyDeps:                    runtime.applyDeps,
		componentKindRegistrationSet: runtime.componentKindRegistrationSet,
	}
	if err := runtime.validate(); err != nil {
		return nil, err
	}
	return runtime, nil
}

func (i *applyAllSourceRevisionAdvanceExecutionRuntime) validate() error {
	if i == nil || !i.minted || i.provenance == nil || i.runner == nil || i.runnerDatabase == nil || i.database == nil ||
		i.serviceGraph == nil || i.applyDeps.DB == nil || i.applyDeps.DBConn == nil {
		return fmt.Errorf("execution runtime was not frozen from a complete runner")
	}
	p := i.provenance
	if i.runner != p.runner || i.runnerDatabase != p.runnerDatabase || i.database != p.database ||
		!sameApplyAllSourceRevisionAdvanceRuntimeInterface(i.serviceGraph, p.serviceGraph) ||
		!sameApplyAllSourceRevisionAdvanceRuntimeDeps(i.applyDeps, p.applyDeps) ||
		!i.componentKindRegistrationSet.SameSnapshot(p.componentKindRegistrationSet) {
		return fmt.Errorf("execution runtime does not match its frozen provenance")
	}
	if i.runner.DB != i.runnerDatabase || i.runnerDatabase.DB() != i.database ||
		runnerSQLDB(i.runner) != i.database || i.serviceGraph.DB() != i.database ||
		i.applyDeps.DB != i.database ||
		i.applyDeps.DBConn != i.runnerDatabase {
		return fmt.Errorf("execution runtime runner or database identity changed")
	}
	return nil
}

func sameApplyAllSourceRevisionAdvanceRuntimeDeps(left packs.ApplyDeps, right packs.ApplyDeps) bool {
	return left.DB == right.DB && left.DBConn == right.DBConn &&
		sameApplyAllSourceRevisionAdvanceRuntimeInterface(left.TxManager, right.TxManager) &&
		left.Metadata == right.Metadata && left.Schema == right.Schema &&
		left.Permissions == right.Permissions &&
		sameApplyAllSourceRevisionAdvanceRuntimeInterface(left.System, right.System) &&
		left.FormulaView == right.FormulaView && left.AutoNumberMetadata == right.AutoNumberMetadata &&
		sameApplyAllSourceRevisionAdvanceRuntimeInterface(left.MetadataRepo, right.MetadataRepo) &&
		sameApplyAllSourceRevisionAdvanceRuntimeInterface(left.SessionRepo, right.SessionRepo) &&
		sameApplyAllSourceRevisionAdvanceRuntimeInterface(left.ConnectedAppHook, right.ConnectedAppHook) &&
		left.Options == right.Options
}

func sameApplyAllSourceRevisionAdvanceRuntimeInterface(left any, right any) bool {
	if left == nil || right == nil {
		return left == nil && right == nil
	}
	leftValue := reflect.ValueOf(left)
	rightValue := reflect.ValueOf(right)
	if leftValue.Type() != rightValue.Type() || !leftValue.Type().Comparable() {
		return false
	}
	return leftValue.Interface() == rightValue.Interface()
}
