package controlplane

import (
	"context"
	"fmt"

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

// Runner is a headless control-plane runtime. It builds the application
// service graph (MetadataService, PermissionService, etc.) WITHOUT starting
// the HTTP server, background workers, or session cleanup. A NoOpEventBus
// is injected so listener subscriptions are inert during pack apply.
//
// The service graph is constructed by the backend composition root
// (pkg/serverruntime) and injected through the BuildServiceGraph seam, so the
// control-plane module does not import the application service package.
type Runner struct {
	DB     *database.TiDBConnection
	Infra  RunnerInfra
	SvcMgr ServiceGraph
}

// RunnerOptions is the immutable distribution input shared by one control-plane run.
type RunnerOptions struct {
	ComponentKindRegistrationSet packs.ComponentKindRegistrationSet
}

// runnerServiceGraph keeps constructor-owned state behind the existing
// ServiceGraph interface so Runner's published three-field shape stays intact.
type runnerServiceGraph struct {
	ServiceGraph
	options RunnerOptions
}

type runnerOptionsCarrier interface {
	runnerOptions() RunnerOptions
}

func (g *runnerServiceGraph) runnerOptions() RunnerOptions {
	return g.options
}

// NewRunner creates a headless control-plane runtime. The service graph is
// constructed by the composition-root-registered BuildServiceGraph builder.
func NewRunner(db *database.TiDBConnection) (*Runner, error) {
	return NewRunnerWithOptions(db, RunnerOptions{})
}

// NewRunnerWithOptions constructs a runner with one distribution-owned option snapshot.
func NewRunnerWithOptions(db *database.TiDBConnection, options RunnerOptions) (*Runner, error) {
	if BuildServiceGraph == nil {
		return nil, fmt.Errorf("controlplane: BuildServiceGraph not registered; import the backend composition root (pkg/serverruntime) to wire it")
	}
	if err := options.ComponentKindRegistrationSet.ValidateManagedLifecycleRuntime(); err != nil {
		return nil, fmt.Errorf("controlplane: managed lifecycle runtime: %w", err)
	}

	svc, infra, err := BuildServiceGraph(db)
	if err != nil {
		return nil, err
	}

	ownedServiceGraph := svc
	if len(options.ComponentKindRegistrationSet.Registrations()) != 0 || options.ComponentKindRegistrationSet.ManagedLifecycleRuntimeConfigured() {
		if svc == nil {
			return nil, fmt.Errorf("controlplane: BuildServiceGraph returned nil service graph for configured runner options")
		}
		ownedServiceGraph = &runnerServiceGraph{ServiceGraph: svc, options: options}
	}

	return &Runner{
		DB:     db,
		Infra:  infra,
		SvcMgr: ownedServiceGraph,
	}, nil
}

func (r *Runner) componentKindRegistrationSet() packs.ComponentKindRegistrationSet {
	if r == nil {
		return packs.ComponentKindRegistrationSet{}
	}
	carrier, ok := r.SvcMgr.(runnerOptionsCarrier)
	if !ok {
		return packs.ComponentKindRegistrationSet{}
	}
	return carrier.runnerOptions().ComponentKindRegistrationSet
}

// RefreshCache refreshes the metadata cache from the database.
func (r *Runner) RefreshCache(ctx context.Context) error {
	return r.SvcMgr.RefreshMetadataCache(ctx)
}

// PackDeps adapts the Runner's service graph into pack ApplyDeps.
func (r *Runner) PackDeps() packs.ApplyDeps {
	return r.PackDepsWithOptions(packs.ApplyOptions{})
}

func (r *Runner) PackDepsWithOptions(options packs.ApplyOptions) packs.ApplyDeps {
	return packs.ApplyDeps{
		DB:                 r.DB.DB(),
		DBConn:             r.DB,
		TxManager:          r.Infra.TxManager,
		Metadata:           r.SvcMgr.Metadata(),
		Schema:             r.SvcMgr.Schema(),
		Permissions:        r.SvcMgr.Permissions(),
		System:             r.SvcMgr.System(),
		FormulaView:        r.SvcMgr.FormulaView(),
		AutoNumberMetadata: r.SvcMgr.AutoNumberMetadata(),
		MetadataRepo:       r.Infra.MetadataRepo,
		SessionRepo:        r.Infra.SessionRepo,
		ConnectedAppHook:   r.SvcMgr.Auth(),
		Options:            options,
	}
}
