package service

import (
	"sync"

	edgeevents "iop/apps/edge/internal/events"
	edgenode "iop/apps/edge/internal/node"
	"iop/packages/go/config"
)

const (
	DefaultSessionID  = "default"
	DefaultTimeoutSec = 30
)

// Service is the surface-neutral application service shared by the console,
// HTTP/A2A input surfaces, and the Control Plane connector. Its responsibilities
// are split across this package by concern: status_provider.go (node snapshots,
// capabilities, health status), run_dispatch.go (run lifecycle), node_command.go
// (node command transport), and control_command.go (Control Plane command
// execution).
type Service struct {
	mu           sync.RWMutex
	registry     *edgenode.Registry
	events       *edgeevents.Bus
	nodeStore    *edgenode.NodeStore
	queue        *modelQueueManager
	modelCatalog []config.ModelCatalogEntry
}

func New(registry *edgenode.Registry, events *edgeevents.Bus) *Service {
	s := &Service{registry: registry, events: events}
	if events != nil {
		q := newModelQueueManager(nil)
		q.startEventWatcher(events)
		s.queue = q
	}
	return s
}

func (s *Service) SetNodeStore(store *edgenode.NodeStore) {
	s.mu.Lock()
	s.nodeStore = store
	s.mu.Unlock()
	if s.queue != nil {
		s.queue.setStore(store)
	}
}

// SetRuntimeConfig atomically replaces the runtime config snapshot used by
// dispatch and status readers. A reader observes either the old node
// store/catalog pair or the new pair, never a mix.
func (s *Service) SetRuntimeConfig(store *edgenode.NodeStore, catalog []config.ModelCatalogEntry) {
	catalog = cloneModelCatalog(catalog)
	s.mu.Lock()
	s.nodeStore = store
	s.modelCatalog = catalog
	s.mu.Unlock()
	if s.queue != nil {
		s.queue.setStore(store)
	}
}

// SetModelCatalog provides the top-level provider-pool model catalog to the
// service. Must be called before the first provider-pool SubmitRun.
func (s *Service) SetModelCatalog(catalog []config.ModelCatalogEntry) {
	s.mu.Lock()
	s.modelCatalog = cloneModelCatalog(catalog)
	s.mu.Unlock()
}

func (s *Service) runtimeConfigSnapshot() (*edgenode.NodeStore, []config.ModelCatalogEntry) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.nodeStore, cloneModelCatalog(s.modelCatalog)
}

func cloneModelCatalog(catalog []config.ModelCatalogEntry) []config.ModelCatalogEntry {
	if len(catalog) == 0 {
		return nil
	}
	out := make([]config.ModelCatalogEntry, len(catalog))
	for i, entry := range catalog {
		out[i] = entry
		if entry.Providers != nil {
			out[i].Providers = make(map[string]string, len(entry.Providers))
			for k, v := range entry.Providers {
				out[i].Providers[k] = v
			}
		}
	}
	return out
}

func (s *Service) ListNodes() []*edgenode.NodeEntry {
	return s.registry.All()
}

// NormalizeSessionID maps an empty session id to the default session.
func NormalizeSessionID(id string) string {
	if id == "" {
		return DefaultSessionID
	}
	return id
}

func normalizeTimeoutSec(timeoutSec int) int {
	if timeoutSec <= 0 {
		return DefaultTimeoutSec
	}
	return timeoutSec
}

func nodeLabel(entry *edgenode.NodeEntry) string {
	return entry.DisplayLabel()
}
