// Package engine — peer.go implements the peer registry for multi-node
// scheduling (v0.2 P02). A peer is a remote orca node reachable over
// mTLS. The registry is in-memory plus optionally SQLite-persisted;
// for P02 the in-memory map is the source of truth and persistence
// is best-effort.
package engine

import (
	"context"
	"fmt"
	"sort"
	"sync"
	"time"

	"git.cloudinit.dev/coreci/orca/internal/store"
)

// Peer is a remote orca node reachable over mTLS.
//
// Deprecated: v0.9 re-architecture replaces peer dispatch with a CLI-side
// scheduler + SSH-push (no orca binary on servers per R-001). The Peer
// type is retained for the dual-write window and scheduled for deletion
// in v0.10-P14. See .ciagent/PRD_v0.9.md R-001/R-006.
type Peer struct {
	NodeID     string
	Address    string // host:port (the peer's daemon listener)
	ServerName string // expected SAN on the peer's cert
	CAPath     string // path to the CA cert this peer validates against
	LastSeen   time.Time
	Capacity   *store.NodeCapacity
}

// PeerRegistry tracks known peers. Methods are safe for concurrent
// use; the underlying map is guarded by a sync.RWMutex.
//
// Deprecated: v0.9 re-architecture replaces peer dispatch with a CLI-side
// scheduler + SSH-push (no orca binary on servers per R-001). The
// PeerRegistry is retained for the dual-write window and scheduled for
// deletion in v0.10-P14. See .ciagent/PRD_v0.9.md R-001/R-006.
type PeerRegistry struct {
	mu    sync.RWMutex
	peers map[string]*Peer
	// optional persistence (not required for P02; can be added later)
	persist PeerPersister
}

// PeerPersister is an optional callback for persisting peer records.
// P02 doesn't use it; it's here for the P03 audit log integration.
type PeerPersister interface {
	SavePeer(ctx context.Context, p *Peer) error
}

// NewPeerRegistry returns an empty registry.
func NewPeerRegistry() *PeerRegistry {
	return &PeerRegistry{peers: make(map[string]*Peer)}
}

// Add inserts or updates a peer record.
func (r *PeerRegistry) Add(p *Peer) error {
	if p == nil {
		return fmt.Errorf("PeerRegistry.Add: nil peer")
	}
	if p.NodeID == "" {
		return fmt.Errorf("PeerRegistry.Add: NodeID is required")
	}
	r.mu.Lock()
	r.peers[p.NodeID] = p
	r.mu.Unlock()
	return nil
}

// Remove deletes a peer by ID. Returns true if a peer was removed.
func (r *PeerRegistry) Remove(nodeID string) bool {
	r.mu.Lock()
	defer r.mu.Unlock()
	_, ok := r.peers[nodeID]
	if ok {
		delete(r.peers, nodeID)
	}
	return ok
}

// Get returns the peer with the given ID, or nil.
func (r *PeerRegistry) Get(nodeID string) *Peer {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.peers[nodeID]
}

// All returns a snapshot of all peers, sorted by NodeID for determinism.
func (r *PeerRegistry) All(_ context.Context) ([]*Peer, error) {
	r.mu.RLock()
	out := make([]*Peer, 0, len(r.peers))
	for _, p := range r.peers {
		out = append(out, p)
	}
	r.mu.RUnlock()
	sort.Slice(out, func(i, j int) bool { return out[i].NodeID < out[j].NodeID })
	return out, nil
}

// Len returns the number of registered peers.
func (r *PeerRegistry) Len() int {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return len(r.peers)
}

// UpdateLastSeen bumps the LastSeen timestamp on a peer.
func (r *PeerRegistry) UpdateLastSeen(nodeID string) {
	r.mu.Lock()
	if p, ok := r.peers[nodeID]; ok {
		p.LastSeen = time.Now().UTC()
	}
	r.mu.Unlock()
}
