package galleryop

import (
	"context"

	"github.com/mudler/LocalAI/core/config"
	"github.com/mudler/LocalAI/core/gallery"
	"github.com/mudler/LocalAI/pkg/model"
	"github.com/mudler/LocalAI/pkg/system"
	"github.com/mudler/xlog"
)

// LocalModelManager handles model install/delete on the local instance.
type LocalModelManager struct {
	systemState                 *system.SystemState
	modelLoader                 *model.ModelLoader
	enforcePredownloadScans     bool
	automaticallyInstallBackend bool
	requireBackendIntegrity     bool
}

// NewLocalModelManager creates a LocalModelManager from the application config.
func NewLocalModelManager(appConfig *config.ApplicationConfig, ml *model.ModelLoader) *LocalModelManager {
	return &LocalModelManager{
		systemState:                 appConfig.SystemState,
		modelLoader:                 ml,
		enforcePredownloadScans:     appConfig.EnforcePredownloadScans,
		automaticallyInstallBackend: appConfig.AutoloadBackendGalleries,
		requireBackendIntegrity:     appConfig.RequireBackendIntegrity,
	}
}

// SetAutoInstallBackend controls whether backend binaries are automatically
// installed when a model is installed. In distributed mode the frontend node
// disables this because backends only run on workers.
func (m *LocalModelManager) SetAutoInstallBackend(v bool) {
	m.automaticallyInstallBackend = v
}

func (m *LocalModelManager) DeleteModel(name string) error {
	if err := m.modelLoader.ShutdownModel(name); err != nil {
		xlog.Warn("Failed to unload model during deletion", "model", name, "error", err)
	}
	return gallery.DeleteModelFromSystem(m.systemState, name)
}

func (m *LocalModelManager) InstallModel(ctx context.Context, op *ManagementOp[gallery.GalleryModel, gallery.ModelConfig], progressCb ProgressCallback) error {
	switch {
	case op.GalleryElement != nil:
		installedModel, err := gallery.InstallModel(ctx, m.systemState, op.GalleryElement.Name,
			op.GalleryElement, op.Req.Overrides, progressCb, m.enforcePredownloadScans)
		if err != nil {
			return err
		}
		if m.automaticallyInstallBackend && installedModel.Backend != "" {
			xlog.Debug("Installing backend", "backend", installedModel.Backend)
			return gallery.InstallBackendFromGallery(ctx, op.BackendGalleries, m.systemState,
				m.modelLoader, installedModel.Backend, progressCb, false, m.requireBackendIntegrity)
		}
		return nil
	case op.GalleryElementName != "":
		return gallery.InstallModelFromGallery(ctx, op.Galleries, op.BackendGalleries,
			m.systemState, m.modelLoader, op.GalleryElementName, op.Req, progressCb,
			m.enforcePredownloadScans, m.automaticallyInstallBackend, m.requireBackendIntegrity)
	default:
		return installModelFromRemoteConfig(ctx, m.systemState, m.modelLoader, op.Req,
			progressCb, m.enforcePredownloadScans, m.automaticallyInstallBackend, op.BackendGalleries, m.requireBackendIntegrity)
	}
}

// LocalBackendManager handles backend install/delete on the local instance.
type LocalBackendManager struct {
	systemState             *system.SystemState
	modelLoader             *model.ModelLoader
	backendGalleries        []config.Gallery
	requireBackendIntegrity bool
}

// NewLocalBackendManager creates a LocalBackendManager from the application config.
func NewLocalBackendManager(appConfig *config.ApplicationConfig, ml *model.ModelLoader) *LocalBackendManager {
	return &LocalBackendManager{
		systemState:             appConfig.SystemState,
		modelLoader:             ml,
		backendGalleries:        appConfig.BackendGalleries,
		requireBackendIntegrity: appConfig.RequireBackendIntegrity,
	}
}

func (b *LocalBackendManager) DeleteBackend(name string) error {
	err := gallery.DeleteBackendFromSystem(b.systemState, name)
	b.modelLoader.DeleteExternalBackend(name)
	return err
}

func (b *LocalBackendManager) ListBackends() (gallery.SystemBackends, error) {
	return gallery.ListSystemBackends(b.systemState)
}

// UpgradeBackend ignores opID: a single-node install reports progress through
// the local progressCb already; opID only matters for distributed per-node
// streaming (see DistributedBackendManager.UpgradeBackend).
func (b *LocalBackendManager) UpgradeBackend(ctx context.Context, _ string, name string, progressCb ProgressCallback) error {
	return gallery.UpgradeBackend(ctx, b.systemState, b.modelLoader, b.backendGalleries, name, progressCb, b.requireBackendIntegrity)
}

func (b *LocalBackendManager) CheckUpgrades(ctx context.Context) (map[string]gallery.UpgradeInfo, error) {
	return gallery.CheckBackendUpgrades(ctx, b.backendGalleries, b.systemState)
}

func (b *LocalBackendManager) InstallBackend(ctx context.Context, op *ManagementOp[gallery.GalleryBackend, any], progressCb ProgressCallback) error {
	if op.ExternalURI != "" {
		return InstallExternalBackend(ctx, b.backendGalleries, b.systemState, b.modelLoader,
			progressCb, op.ExternalURI, op.ExternalName, op.ExternalAlias, op.Force, b.requireBackendIntegrity)
	}
	// op.Force distinguishes an explicit reinstall from an idempotent
	// "make sure it's installed" op; the latter must not re-download an
	// already-runnable backend (supervisors apply on every boot).
	return gallery.InstallBackendFromGallery(ctx, b.backendGalleries, b.systemState,
		b.modelLoader, op.GalleryElementName, progressCb, op.Force, b.requireBackendIntegrity)
}

func (b *LocalBackendManager) IsDistributed() bool { return false }
