package bridge

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

	"git.yeij.top/AskaEth/Cyrene/platform-bridge/internal/permissions"
)


const participantTTL = 5 * time.Minute

// adapterKey returns the unique key for an adapter in the router map.
// Uses ConfigName() if the adapter implements it, otherwise PlatformName().
func adapterKey(a PlatformAdapter) string {
	if named, ok := a.(interface{ ConfigName() string }); ok {
		return named.ConfigName()
	}
	return a.PlatformName()
}

// PlatformRouter manages all platform adapters and routes messages.
type PlatformRouter struct {
	mu        sync.RWMutex
	adapters  map[string]PlatformAdapter
	mapper    *IdentityMapper
	checker   *permissions.Checker
	handler   MessageHandler

	// Conversational context per channel.
	contexts map[string]*ChannelContext // channelKey -> context
}

// ChannelContext stores the active conversation state for a channel.
type ChannelContext struct {
	Platform            string
	ChannelID           string
	ChannelType         string
	LastUserMsg         string
	LastSenderUID       string
	RecentSenders       []string // last 5 sender UIDs (original platform UIDs)
	ActiveParticipants map[string]time.Time // UID -> last bot reply time (for multi-user conversation continuity)
	MessageCount        int
}

func NewPlatformRouter(mapper *IdentityMapper, checker *permissions.Checker) *PlatformRouter {
	return &PlatformRouter{
		adapters: make(map[string]PlatformAdapter),
		mapper:   mapper,
		checker:  checker,
		contexts: make(map[string]*ChannelContext),
	}
}

// RegisterAdapter adds a platform adapter, keyed by its config name.
func (r *PlatformRouter) RegisterAdapter(a PlatformAdapter) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.adapters[adapterKey(a)] = a
}

// RemoveAdapter disconnects and removes a platform adapter.
func (r *PlatformRouter) RemoveAdapter(platform string) {
	r.mu.Lock()
	a, ok := r.adapters[platform]
	if ok {
		delete(r.adapters, platform)
	}
	r.mu.Unlock()
	if ok {
		a.Disconnect(context.Background())
	}
}

// ReplaceAdapter disconnects the old adapter (if present), registers the new one,
// and connects it. Returns an error if the new adapter fails to connect.
func (r *PlatformRouter) ReplaceAdapter(a PlatformAdapter) error {
	key := adapterKey(a)
	r.mu.Lock()
	if old, ok := r.adapters[key]; ok {
		old.Disconnect(context.Background())
	}
	r.adapters[key] = a
	r.mu.Unlock()
	return a.Connect(context.Background())
}

// GetAdapter returns the adapter for a platform.
func (r *PlatformRouter) GetAdapter(platform string) (PlatformAdapter, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	a, ok := r.adapters[platform]
	if !ok {
		return nil, fmt.Errorf("no adapter for platform: %s", platform)
	}
	return a, nil
}

// ListAdapters returns all registered adapter names (config names).
func (r *PlatformRouter) ListAdapters() []string {
	r.mu.RLock()
	defer r.mu.RUnlock()
	names := make([]string, 0, len(r.adapters))
	for name := range r.adapters {
		names = append(names, name)
	}
	return names
}

// GetAdaptersByPlatform returns all registered adapters for a given platform type.
func (r *PlatformRouter) GetAdaptersByPlatform(platform string) []PlatformAdapter {
	r.mu.RLock()
	defer r.mu.RUnlock()
	var result []PlatformAdapter
	for _, a := range r.adapters {
		if a.PlatformName() == platform {
			result = append(result, a)
		}
	}
	return result
}

// SetMessageHandler sets the callback for processing unified messages.
func (r *PlatformRouter) SetMessageHandler(h MessageHandler) {
	r.handler = h
}

// RouteMessage converts a platform message to unified, checks permissions, and dispatches.
// adapterKey is the config name (e.g., "obv11", "obv11-home") used to look up the adapter instance.
func (r *PlatformRouter) RouteMessage(adapterKey string, rawMsg interface{}) (*UnifiedResponse, error) {
	a, err := r.GetAdapter(adapterKey)
	if err != nil {
		return nil, err
	}

	unified, err := a.ToUnified(rawMsg)
	if err != nil {
		return nil, fmt.Errorf("convert to unified: %w", err)
	}

	// Preserve original platform UID before identity mapping.
	unified.OriginalSenderUID = unified.SenderID
	unified.OriginalSenderName = unified.SenderName
	unified.OriginalRawMessage = rawMsg

	// Capture bot's own UID for @mention detection.
	if selfAware, ok := a.(interface{ SelfID() string }); ok {
		unified.BotUID = selfAware.SelfID()
	}

	// Capture adapter config name for multi-account routing.
	if named, ok := a.(interface{ ConfigName() string }); ok {
		unified.AdapterName = named.ConfigName()
	}

	// Resolve identity (nil for unknown users; caller decides routing).
	// Use platform type (e.g. "obv11") for identity resolution, not adapter key.
	identity := r.mapper.ResolveOrNil(a.PlatformName(), unified.SenderID)
	if identity != nil {
		unified.SenderID = identity.CyreneUser
		unified.SenderName = identity.Nickname
	}

	// Update channel context.
	r.updateContext(unified)

	if r.handler == nil {
		return nil, fmt.Errorf("no message handler configured")
	}

	response, err := r.handler(unified)
	if err != nil {
		return nil, err
	}

	// Use adapter key for response routing so SendResponse finds the correct instance.
	response.Platform = adapterKey
	response.PlatformHints = r.platformHints(adapterKey)

	return response, nil
}

// SendResponse converts and sends a unified response through the platform adapter.
func (r *PlatformRouter) SendResponse(response *UnifiedResponse) ([]PlatformMessage, error) {
	a, err := r.GetAdapter(response.Platform)
	if err != nil {
		return nil, err
	}
	return a.FromUnified(response)
}

// SendProactive sends a proactive message through a platform adapter that supports it.
// Returns an error if the adapter doesn't support proactive sending or the send fails.
func (r *PlatformRouter) SendProactive(adapterName, chatType string, userID, groupID int64, content string) error {
	a, err := r.GetAdapter(adapterName)
	if err != nil {
		return err
	}
	sender, ok := a.(ProactiveSender)
	if !ok {
		return fmt.Errorf("adapter %s does not support proactive sending", adapterName)
	}
	return sender.SendProactive(chatType, userID, groupID, content)
}

func (r *PlatformRouter) platformHints(platform string) PlatformHints {
	cap := PlatformCapabilities{}
	if a, err := r.GetAdapter(platform); err == nil {
		cap = a.Capabilities()
	}
	return PlatformHints{
		TypingIndicator: cap.SupportsTypingHint,
		BurstMode:       cap.RecommendBurstMax > 1,
	}
}

func (r *PlatformRouter) channelKey(platform, channelID string) string {
	return platform + ":" + channelID
}

func (r *PlatformRouter) updateContext(msg *UnifiedMessage) {
	key := r.channelKey(msg.Platform, msg.ChannelID)
	r.mu.Lock()
	defer r.mu.Unlock()
	ctx, ok := r.contexts[key]
	if !ok {
		ctx = &ChannelContext{
			Platform:    msg.Platform,
			ChannelID:   msg.ChannelID,
			ChannelType: msg.ChannelType,
		}
		r.contexts[key] = ctx
	}
	ctx.LastUserMsg = msg.Content
	ctx.LastSenderUID = msg.OriginalSenderUID
	ctx.RecentSenders = append(ctx.RecentSenders, msg.OriginalSenderUID)
	if len(ctx.RecentSenders) > 5 {
		ctx.RecentSenders = ctx.RecentSenders[len(ctx.RecentSenders)-5:]
	}
	ctx.MessageCount++
}

// ListAllIdentities returns all registered identity mappings.
func (r *PlatformRouter) ListAllIdentities() map[string][]permissions.PlatformIdentity {
	return r.mapper.ListAll()
}

// GetContext returns the channel context.
func (r *PlatformRouter) GetContext(platform, channelID string) *ChannelContext {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.contexts[platform+":"+channelID]
}

// NoteBotReply records that the bot just replied to a specific user in a channel.
// Used for conversation continuity: subsequent messages from this user continue the
// conversation even without an explicit @mention, within the participant TTL window.
func (r *PlatformRouter) NoteBotReply(platform, channelID, recipientUID string) {
	r.mu.Lock()
	defer r.mu.Unlock()
	key := r.channelKey(platform, channelID)
	ctx, ok := r.contexts[key]
	if !ok {
		return
	}
	if ctx.ActiveParticipants == nil {
		ctx.ActiveParticipants = make(map[string]time.Time)
	}
	ctx.ActiveParticipants[recipientUID] = time.Now()
}

// IsActiveParticipant checks if a user was recently engaged by the bot.
// TTL controls how long the continuity window stays open after the last bot reply.
func (r *PlatformRouter) IsActiveParticipant(platform, channelID, uid string) bool {
	r.mu.RLock()
	defer r.mu.RUnlock()
	key := r.channelKey(platform, channelID)
	ctx, ok := r.contexts[key]
	if !ok || ctx.ActiveParticipants == nil {
		return false
	}
	t, ok := ctx.ActiveParticipants[uid]
	if !ok {
		return false
	}
	return time.Since(t) < participantTTL
}
