package node

import (
	"fmt"
	"sort"
	"sync"

	"github.com/google/uuid"
	"iop/packages/go/config"
)

// NodeRecord is the pre-registered node definition stored in edge.
type NodeRecord struct {
	ID        string
	Alias     string
	Token     string
	AgentKind string
	Index     int
	Adapters  config.AdaptersConf
	Providers []config.NodeProviderConf
	Runtime   config.RuntimeConf
}

// NodeStore holds pre-registered node definitions, keyed by token.
type NodeStore struct {
	mu      sync.RWMutex
	byToken map[string]*NodeRecord
	byID    map[string]*NodeRecord
}

func NewNodeStore() *NodeStore {
	return &NodeStore{
		byToken: make(map[string]*NodeRecord),
		byID:    make(map[string]*NodeRecord),
	}
}

func (s *NodeStore) Add(rec *NodeRecord) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.byToken[rec.Token] = rec
	s.byID[rec.ID] = rec
}

func (s *NodeStore) FindByToken(token string) (*NodeRecord, bool) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	r, ok := s.byToken[token]
	return r, ok
}

func (s *NodeStore) FindByID(id string) (*NodeRecord, bool) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	r, ok := s.byID[id]
	return r, ok
}

// All returns all configured NodeRecords in deterministic, ascending Index order.
// Callers (e.g. status snapshot builder) rely on this ordering so the surface
// exposed to CLI/HTTP/Control Plane is reproducible across calls.
func (s *NodeStore) All() []*NodeRecord {
	s.mu.RLock()
	defer s.mu.RUnlock()
	out := make([]*NodeRecord, 0, len(s.byID))
	for _, r := range s.byID {
		out = append(out, r)
	}
	sort.Slice(out, func(i, j int) bool {
		return out[i].Index < out[j].Index
	})
	return out
}

// LoadFromConfig seeds the store from EdgeConfig.Nodes.
func LoadFromConfig(defs []config.NodeDefinition) (*NodeStore, error) {
	s := NewNodeStore()
	seenToken := make(map[string]bool)
	seenAlias := make(map[string]bool)
	seenID := make(map[string]bool)
	for i, d := range defs {
		if d.Token == "" {
			return nil, fmt.Errorf("node[%d] alias=%q: token must not be empty", i, d.Alias)
		}
		if seenToken[d.Token] {
			return nil, fmt.Errorf("node[%d] alias=%q: duplicate token", i, d.Alias)
		}
		if seenAlias[d.Alias] {
			return nil, fmt.Errorf("node[%d] alias=%q: duplicate alias", i, d.Alias)
		}
		if d.ID != "" && seenID[d.ID] {
			return nil, fmt.Errorf("node[%d] alias=%q: duplicate id %q", i, d.Alias, d.ID)
		}
		seenToken[d.Token] = true
		seenAlias[d.Alias] = true
		nodeID := d.ID
		if nodeID == "" {
			nodeID = uuid.NewString()
		} else {
			seenID[d.ID] = true
		}
		agentKind := d.AgentKind
		if agentKind == "" {
			agentKind = config.AgentKindGenericNode
		}
		adapters := d.Adapters
		if err := config.NormalizeAdapters(&adapters); err != nil {
			return nil, fmt.Errorf("node[%d] alias=%q: adapters: %w", i, d.Alias, err)
		}
		s.Add(&NodeRecord{
			ID:        nodeID,
			Alias:     d.Alias,
			Token:     d.Token,
			AgentKind: agentKind,
			Index:     i,
			Adapters:  adapters,
			Providers: d.Providers,
			Runtime:   d.Runtime,
		})
	}
	return s, nil
}
