package events

import (
	"sync"

	iop "iop/proto/gen/iop"
)

// DefaultReplayCapacity bounds the per-bus ring buffer of recent run/node
// events kept for late-joining ops surfaces.
const DefaultReplayCapacity = 64

// BusStats is a lock-safe snapshot of bus counters. Counters are cumulative
// since the Bus was created.
type BusStats struct {
	DroppedRunEvents  uint64
	DroppedNodeEvents uint64
}

// Bus is the in-process edge event fanout used by temporary consoles, future
// API handlers, and later event persistence.
type Bus struct {
	mu       sync.Mutex
	runSubs  map[string]map[chan *iop.RunEvent]struct{}
	nodeSubs map[string]map[chan *iop.EdgeNodeEvent]struct{}
	allRuns  map[chan *iop.RunEvent]struct{}
	allNodes map[chan *iop.EdgeNodeEvent]struct{}

	replayCap   int
	recentRuns  []*iop.RunEvent
	recentNodes []*iop.EdgeNodeEvent

	droppedRun  uint64
	droppedNode uint64
}

func NewBus() *Bus {
	return NewBusWithReplayCapacity(DefaultReplayCapacity)
}

// NewBusWithReplayCapacity creates a Bus whose replay ring keeps up to cap
// most-recent events of each kind. A cap <= 0 disables replay retention.
func NewBusWithReplayCapacity(cap int) *Bus {
	if cap < 0 {
		cap = 0
	}
	return &Bus{
		runSubs:   make(map[string]map[chan *iop.RunEvent]struct{}),
		nodeSubs:  make(map[string]map[chan *iop.EdgeNodeEvent]struct{}),
		allRuns:   make(map[chan *iop.RunEvent]struct{}),
		allNodes:  make(map[chan *iop.EdgeNodeEvent]struct{}),
		replayCap: cap,
	}
}

func (b *Bus) SubscribeRun(runID string, buffer int) (<-chan *iop.RunEvent, func()) {
	if buffer <= 0 {
		buffer = 1
	}
	ch := make(chan *iop.RunEvent, buffer)
	b.mu.Lock()
	if b.runSubs[runID] == nil {
		b.runSubs[runID] = make(map[chan *iop.RunEvent]struct{})
	}
	b.runSubs[runID][ch] = struct{}{}
	b.mu.Unlock()

	return ch, func() {
		b.mu.Lock()
		if subs := b.runSubs[runID]; subs != nil {
			delete(subs, ch)
			if len(subs) == 0 {
				delete(b.runSubs, runID)
			}
		}
		close(ch)
		b.mu.Unlock()
	}
}

func (b *Bus) SubscribeNode(nodeID string, buffer int) (<-chan *iop.EdgeNodeEvent, func()) {
	if buffer <= 0 {
		buffer = 1
	}
	ch := make(chan *iop.EdgeNodeEvent, buffer)
	b.mu.Lock()
	if b.nodeSubs[nodeID] == nil {
		b.nodeSubs[nodeID] = make(map[chan *iop.EdgeNodeEvent]struct{})
	}
	b.nodeSubs[nodeID][ch] = struct{}{}
	b.mu.Unlock()

	return ch, func() {
		b.mu.Lock()
		if subs := b.nodeSubs[nodeID]; subs != nil {
			delete(subs, ch)
			if len(subs) == 0 {
				delete(b.nodeSubs, nodeID)
			}
		}
		close(ch)
		b.mu.Unlock()
	}
}

func (b *Bus) SubscribeAllRuns(buffer int) (<-chan *iop.RunEvent, func()) {
	if buffer <= 0 {
		buffer = 1
	}
	ch := make(chan *iop.RunEvent, buffer)
	b.mu.Lock()
	b.allRuns[ch] = struct{}{}
	b.mu.Unlock()

	return ch, func() {
		b.mu.Lock()
		delete(b.allRuns, ch)
		close(ch)
		b.mu.Unlock()
	}
}

func (b *Bus) SubscribeAllNodes(buffer int) (<-chan *iop.EdgeNodeEvent, func()) {
	if buffer <= 0 {
		buffer = 1
	}
	ch := make(chan *iop.EdgeNodeEvent, buffer)
	b.mu.Lock()
	b.allNodes[ch] = struct{}{}
	b.mu.Unlock()

	return ch, func() {
		b.mu.Lock()
		delete(b.allNodes, ch)
		close(ch)
		b.mu.Unlock()
	}
}

func (b *Bus) PublishRun(event *iop.RunEvent) {
	if event == nil {
		return
	}
	b.mu.Lock()
	defer b.mu.Unlock()
	for ch := range b.allRuns {
		if !offerRun(ch, event) {
			b.droppedRun++
		}
	}
	for ch := range b.runSubs[event.GetRunId()] {
		if !offerRun(ch, event) {
			b.droppedRun++
		}
	}
	b.recordRunLocked(event)
}

func (b *Bus) PublishNode(event *iop.EdgeNodeEvent) {
	if event == nil {
		return
	}
	b.mu.Lock()
	defer b.mu.Unlock()
	for ch := range b.allNodes {
		if !offerNode(ch, event) {
			b.droppedNode++
		}
	}
	for ch := range b.nodeSubs[event.GetNodeId()] {
		if !offerNode(ch, event) {
			b.droppedNode++
		}
	}
	b.recordNodeLocked(event)
}

// Stats returns a snapshot of cumulative bus counters.
func (b *Bus) Stats() BusStats {
	b.mu.Lock()
	defer b.mu.Unlock()
	return BusStats{
		DroppedRunEvents:  b.droppedRun,
		DroppedNodeEvents: b.droppedNode,
	}
}

// ReplayRun returns up to replayCap most-recent run events for runID, oldest
// first. Returns nil when replay is disabled or no matching events are buffered.
func (b *Bus) ReplayRun(runID string) []*iop.RunEvent {
	b.mu.Lock()
	defer b.mu.Unlock()
	if len(b.recentRuns) == 0 {
		return nil
	}
	out := make([]*iop.RunEvent, 0, len(b.recentRuns))
	for _, e := range b.recentRuns {
		if runID == "" || e.GetRunId() == runID {
			out = append(out, e)
		}
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

// ReplayNode returns up to replayCap most-recent node events for nodeID,
// oldest first. Returns nil when replay is disabled or no matching events are
// buffered.
func (b *Bus) ReplayNode(nodeID string) []*iop.EdgeNodeEvent {
	b.mu.Lock()
	defer b.mu.Unlock()
	if len(b.recentNodes) == 0 {
		return nil
	}
	out := make([]*iop.EdgeNodeEvent, 0, len(b.recentNodes))
	for _, e := range b.recentNodes {
		if nodeID == "" || e.GetNodeId() == nodeID {
			out = append(out, e)
		}
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func (b *Bus) recordRunLocked(event *iop.RunEvent) {
	if b.replayCap <= 0 {
		return
	}
	if len(b.recentRuns) >= b.replayCap {
		b.recentRuns = append(b.recentRuns[:0], b.recentRuns[1:]...)
	}
	b.recentRuns = append(b.recentRuns, event)
}

func (b *Bus) recordNodeLocked(event *iop.EdgeNodeEvent) {
	if b.replayCap <= 0 {
		return
	}
	if len(b.recentNodes) >= b.replayCap {
		b.recentNodes = append(b.recentNodes[:0], b.recentNodes[1:]...)
	}
	b.recentNodes = append(b.recentNodes, event)
}

func offerRun(ch chan *iop.RunEvent, event *iop.RunEvent) bool {
	select {
	case ch <- event:
		return true
	default:
		return false
	}
}

func offerNode(ch chan *iop.EdgeNodeEvent, event *iop.EdgeNodeEvent) bool {
	select {
	case ch <- event:
		return true
	default:
		return false
	}
}
