package router

import (
	"fmt"
	"path/filepath"
	"sync"

	ort "github.com/yalue/onnxruntime_go"
)

// ONNXRuntimeLease keeps the process-global ONNX Runtime environment alive.
//
// onnxruntime_go exposes the environment as package-global state: it may be
// initialized only once, and destroying it while any session is alive is
// unsafe. A lease makes that ownership explicit. Model wrappers hold one for
// the lifetime of their session; TestMain may hold an additional lease so a
// package's model-aware gates share one environment instead of repeatedly
// loading and unloading the shared library.
type ONNXRuntimeLease struct {
	once sync.Once
	err  error
}

var sharedONNXRuntime = struct {
	sync.Mutex
	refs    int
	libPath string
	managed bool
}{}

// AcquireONNXRuntime acquires the process-global ONNX Runtime environment.
// The first caller initializes it; later callers share it. A non-empty library
// path must agree with the path that initialized the active environment.
//
// The external-environment branch is intentional. It keeps this package safe
// when an executable initialized onnxruntime_go directly before constructing
// a router model; in that case the lease never destroys state it does not own.
func AcquireONNXRuntime(libPath string) (*ONNXRuntimeLease, error) {
	libPath = cleanONNXLibraryPath(libPath)

	sharedONNXRuntime.Lock()
	defer sharedONNXRuntime.Unlock()

	if sharedONNXRuntime.refs > 0 {
		if err := compatibleONNXLibraryPath(sharedONNXRuntime.libPath, libPath); err != nil {
			return nil, err
		}
		sharedONNXRuntime.refs++
		return &ONNXRuntimeLease{}, nil
	}

	if ort.IsInitialized() {
		// Some other component owns the already-live environment. Attach to it,
		// but never unload its shared library when our last lease closes.
		sharedONNXRuntime.refs = 1
		sharedONNXRuntime.libPath = libPath
		sharedONNXRuntime.managed = false
		return &ONNXRuntimeLease{}, nil
	}

	if libPath != "" {
		ort.SetSharedLibraryPath(libPath)
	}
	if err := ort.InitializeEnvironment(); err != nil {
		return nil, fmt.Errorf("onnx: init environment: %w", err)
	}
	sharedONNXRuntime.refs = 1
	sharedONNXRuntime.libPath = libPath
	sharedONNXRuntime.managed = true
	return &ONNXRuntimeLease{}, nil
}

// Close releases one environment lease. The last lease destroys an
// environment initialized by this package, after every model session that
// held a lease has already been destroyed.
func (l *ONNXRuntimeLease) Close() error {
	if l == nil {
		return nil
	}
	l.once.Do(func() {
		l.err = releaseONNXRuntime()
	})
	return l.err
}

func releaseONNXRuntime() error {
	sharedONNXRuntime.Lock()
	defer sharedONNXRuntime.Unlock()

	if sharedONNXRuntime.refs == 0 {
		return fmt.Errorf("onnx: release environment without a lease")
	}
	sharedONNXRuntime.refs--
	if sharedONNXRuntime.refs != 0 {
		return nil
	}

	managed := sharedONNXRuntime.managed
	sharedONNXRuntime.libPath = ""
	sharedONNXRuntime.managed = false
	if !managed {
		return nil
	}
	if err := ort.DestroyEnvironment(); err != nil {
		return fmt.Errorf("onnx: destroy environment: %w", err)
	}
	return nil
}

func cleanONNXLibraryPath(path string) string {
	if path == "" {
		return ""
	}
	return filepath.Clean(path)
}

func compatibleONNXLibraryPath(active, requested string) error {
	// Empty means "use the environment that is already active". RouterHeads
	// intentionally has no second copy of the embedder's library-path config.
	if active == "" || requested == "" || active == requested {
		return nil
	}
	return fmt.Errorf("onnx: runtime already uses shared library %q, cannot also use %q", active, requested)
}
