// Package bus provides in-process agent-to-agent message passing.
package bus

import (
	"context"
	"fmt"
	"log/slog"
	"sync"
	"time"
)

// Well-known message kinds used by the orchestrator.
const (
	KindTask       = "task"        // orchestrator → bot: handle this question
	KindTaskResult = "task_result" // bot → orchestrator: here is my answer
)

// AgentID identifies an agent.
type AgentID string

// AgentMessage is a message between agents.
type AgentMessage struct {
	From    AgentID
	To      AgentID
	Kind    string
	Payload map[string]string
}

// Bus manages channels for inter-agent communication.
type Bus struct {
	mu       sync.RWMutex
	channels map[AgentID]chan AgentMessage

	replyMu  sync.Mutex
	replyChs map[string]chan AgentMessage // taskID → one-shot reply channel

	logger *slog.Logger
}

// New creates a new Bus.
func New(logger *slog.Logger) *Bus {
	return &Bus{
		channels: make(map[AgentID]chan AgentMessage),
		replyChs: make(map[string]chan AgentMessage),
		logger:   logger.With("component", "bus"),
	}
}

// Subscribe registers an agent and returns its receive channel.
func (b *Bus) Subscribe(id AgentID) <-chan AgentMessage {
	b.mu.Lock()
	defer b.mu.Unlock()
	ch := make(chan AgentMessage, 64)
	b.channels[id] = ch
	b.logger.Info("bus_subscribe", "agent", id)
	return ch
}

// Send delivers a message to an agent's channel.
func (b *Bus) Send(msg AgentMessage) error {
	b.mu.RLock()
	ch, ok := b.channels[msg.To]
	b.mu.RUnlock()
	if !ok {
		b.logger.Warn("bus_not_found", "to", msg.To, "from", msg.From, "kind", msg.Kind)
		return fmt.Errorf("agent %q not registered on bus", msg.To)
	}
	select {
	case ch <- msg:
		b.logger.Debug("bus_send", "from", msg.From, "to", msg.To, "kind", msg.Kind)
		return nil
	default:
		b.logger.Warn("bus_queue_full", "to", msg.To, "from", msg.From, "kind", msg.Kind)
		return fmt.Errorf("agent %q message queue full", msg.To)
	}
}

// SendAndWait sends a task message and blocks until a reply with the matching
// taskID arrives or the context expires. The caller must ensure the reply is
// routed via Reply().
func (b *Bus) SendAndWait(ctx context.Context, msg AgentMessage, taskID string, timeout time.Duration) (AgentMessage, error) {
	ch := make(chan AgentMessage, 1)
	b.replyMu.Lock()
	b.replyChs[taskID] = ch
	b.replyMu.Unlock()

	defer func() {
		b.replyMu.Lock()
		delete(b.replyChs, taskID)
		b.replyMu.Unlock()
	}()

	if err := b.Send(msg); err != nil {
		return AgentMessage{}, err
	}

	b.logger.Debug("bus_send_and_wait", "task", taskID, "to", msg.To, "timeout", timeout)

	timer := time.NewTimer(timeout)
	defer timer.Stop()

	select {
	case reply := <-ch:
		return reply, nil
	case <-timer.C:
		b.logger.Warn("bus_timeout", "task", taskID, "to", msg.To, "timeout", timeout)
		return AgentMessage{}, fmt.Errorf("task %s: delegation timeout after %s", taskID, timeout)
	case <-ctx.Done():
		return AgentMessage{}, ctx.Err()
	}
}

// Reply routes a task_result message to the waiting SendAndWait caller.
// If no one is waiting for this taskID, it falls back to regular Send.
func (b *Bus) Reply(taskID string, msg AgentMessage) error {
	b.replyMu.Lock()
	ch, ok := b.replyChs[taskID]
	b.replyMu.Unlock()

	if ok {
		select {
		case ch <- msg:
			b.logger.Debug("bus_reply", "task", taskID, "from", msg.From)
			return nil
		default:
			b.logger.Warn("bus_reply_full", "task", taskID)
			return fmt.Errorf("reply channel full for task %s", taskID)
		}
	}
	// Fallback: deliver via regular channel
	return b.Send(msg)
}

// Unsubscribe removes an agent from the bus.
func (b *Bus) Unsubscribe(id AgentID) {
	b.mu.Lock()
	defer b.mu.Unlock()
	if ch, ok := b.channels[id]; ok {
		close(ch)
		delete(b.channels, id)
		b.logger.Info("bus_unsubscribe", "agent", id)
	}
}
