package bot

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strings"
	"sync"
	"time"
)

const (
	QueueModeSteer     = "steer"
	QueueModeFollowup  = "followup"
	QueueModeCollect   = "collect"
	QueueModeInterrupt = "interrupt"

	QueueDropSummarize = "summarize"
	QueueDropOld       = "old"
	QueueDropNew       = "new"

	DefaultQueueCap = 20
)

type QueueOptions struct {
	Mode string
	Cap  int
	Drop string
}

type QueueResult struct {
	Acquired bool
	Queued   bool
	Rejected bool
	Dropped  bool
	Pending  int
	Mode     string
}

type QueueSnapshot struct {
	Active   int
	Pending  int
	Dropped  int
	Sessions int
}

func NormalizeQueueMode(mode string) string {
	switch strings.ToLower(strings.TrimSpace(mode)) {
	case QueueModeSteer:
		return QueueModeSteer
	case QueueModeFollowup:
		return QueueModeFollowup
	case QueueModeCollect:
		return QueueModeCollect
	case QueueModeInterrupt:
		return QueueModeInterrupt
	default:
		return QueueModeSteer
	}
}

func NormalizeOptionalQueueMode(mode string) string {
	switch strings.ToLower(strings.TrimSpace(mode)) {
	case QueueModeSteer:
		return QueueModeSteer
	case QueueModeFollowup:
		return QueueModeFollowup
	case QueueModeCollect:
		return QueueModeCollect
	case QueueModeInterrupt:
		return QueueModeInterrupt
	default:
		return ""
	}
}

func NormalizeQueueDrop(drop string) string {
	switch strings.ToLower(strings.TrimSpace(drop)) {
	case QueueDropOld:
		return QueueDropOld
	case QueueDropNew:
		return QueueDropNew
	default:
		return QueueDropSummarize
	}
}

// BuildSessionKey 根据 Hermes 模式生成稳定的 session key：
//   - DM：按 chat 隔离（同一 DM 会话共享历史）
//   - 群聊：按 user 隔离（每人独立会话）
//   - thread：共享（thread 内所有人共享上下文）
func BuildSessionKey(src SessionSource) string {
	var scope string
	source := sessionSourceID(src)
	switch src.ChatType {
	case ChatDM:
		scope = fmt.Sprintf("%s:dm:%s", source, src.ChatID)
	case ChatGroup:
		scope = fmt.Sprintf("%s:group:%s:%s", source, src.ChatID, src.UserID)
	case ChatGuild:
		scope = fmt.Sprintf("%s:guild:%s:%s", source, src.ChatID, src.UserID)
	case ChatDirect:
		scope = fmt.Sprintf("%s:direct:%s", source, src.ChatID)
	case ChatThread:
		threadID := src.ThreadID
		if threadID == "" {
			threadID = src.ChatID
		}
		scope = fmt.Sprintf("%s:thread:%s", source, threadID)
	default:
		scope = fmt.Sprintf("%s:%s:%s:%s", source, src.ChatType, src.ChatID, src.UserID)
	}
	h := sha256.Sum256([]byte(scope))
	return hex.EncodeToString(h[:])[:16]
}

func sessionSourceID(src SessionSource) string {
	if src.ConnectionID != "" {
		return src.ConnectionID
	}
	if src.Domain != "" {
		return fmt.Sprintf("%s:%s", src.Platform, src.Domain)
	}
	return string(src.Platform)
}

// slashCommands 是绕过忙碌队列的命令集合。
var slashCommands = map[string]bool{
	"/stop":            true,
	"/new":             true,
	"/reset":           true,
	"/approve":         true,
	"/approve-session": true,
	"/approve-project": true,
	"/deny":            true,
	"/answer":          true,
	"/yolo":            true,
	"/mode":            true,
	"/model":           true,
	"/queue":           true,
	"/projects":        true,
	"/use":             true,
	"/sessions":        true,
	"/attach":          true,
	"/search":          true,
	"/desktop":         true,
	"/status":          true,
	"/help":            true,
}

// IsSlashBypass 判断消息是否为绕过队列的斜杠命令。
func IsSlashBypass(text string) bool {
	if len(text) == 0 {
		return false
	}
	cmd := text
	for i, r := range text {
		if r == ' ' {
			cmd = text[:i]
			break
		}
	}
	return slashCommands[cmd]
}

// pendingTurn 是等待执行的一轮对话。
type pendingTurn struct {
	msg       InboundMessage
	timestamp time.Time
	mode      string
}

// SessionManager 管理 session 级别的并发控制：同一 session 同时只跑一个任务。
type SessionManager struct {
	mu            sync.Mutex
	active        map[string]bool          // session key -> 是否正在运行
	pending       map[string][]pendingTurn // session key -> 等待队列
	debounce      time.Duration
	modeOverrides map[string]string
	dropped       map[string][]string
}

// NewSessionManager 创建一个新的 session 管理器。debounce 是消息合并窗口。
func NewSessionManager(debounce time.Duration) *SessionManager {
	if debounce <= 0 {
		debounce = 1500 * time.Millisecond
	}
	return &SessionManager{
		active:        make(map[string]bool),
		pending:       make(map[string][]pendingTurn),
		debounce:      debounce,
		modeOverrides: make(map[string]string),
		dropped:       make(map[string][]string),
	}
}

// TryAcquire 尝试获取 session 锁。如果 session 正忙且消息非绕过命令，返回 false。
// 返回 (acquired, merged) — merged 为 true 表示消息已合并到等待队列。
func (sm *SessionManager) TryAcquire(key string, msg InboundMessage) (acquired bool, merged bool) {
	result := sm.TryAcquireWithQueue(key, msg, QueueOptions{Mode: QueueModeCollect, Cap: DefaultQueueCap, Drop: QueueDropSummarize})
	return result.Acquired, result.Queued
}

func (sm *SessionManager) TryAcquireWithQueue(key string, msg InboundMessage, opts QueueOptions) QueueResult {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	mode := NormalizeQueueMode(opts.Mode)
	if mode == QueueModeSteer || mode == QueueModeInterrupt {
		mode = QueueModeFollowup
	}
	cap := opts.Cap
	if cap <= 0 {
		cap = DefaultQueueCap
	}
	drop := NormalizeQueueDrop(opts.Drop)

	if sm.active[key] {
		// 绕过命令立即返回 true（让调用方直接处理）
		if IsSlashBypass(msg.Text) {
			return QueueResult{Acquired: true, Mode: mode}
		}
		queue := sm.pending[key]
		if len(queue) >= cap {
			switch drop {
			case QueueDropNew:
				return QueueResult{Rejected: true, Pending: len(queue), Mode: mode}
			case QueueDropOld, QueueDropSummarize:
				removed := queue[0]
				queue = queue[1:]
				if drop == QueueDropSummarize {
					sm.dropped[key] = append(sm.dropped[key], queueSummary(removed.msg.Text))
				}
			}
		}
		if mode == QueueModeCollect && len(queue) > 0 {
			last := &queue[len(queue)-1]
			if msg.Text != "" && time.Since(last.timestamp) < sm.debounce {
				if last.msg.Text != "" {
					last.msg.Text = last.msg.Text + "\n" + msg.Text
				} else {
					last.msg.Text = msg.Text
				}
				last.timestamp = time.Now()
				last.mode = mode
				sm.pending[key] = queue
				return QueueResult{Queued: true, Dropped: len(sm.dropped[key]) > 0, Pending: len(queue), Mode: mode}
			}
		}
		queue = append(queue, pendingTurn{msg: msg, timestamp: time.Now(), mode: mode})
		sm.pending[key] = queue
		return QueueResult{Queued: true, Dropped: len(sm.dropped[key]) > 0, Pending: len(queue), Mode: mode}
	}

	sm.active[key] = true
	return QueueResult{Acquired: true, Mode: mode}
}

// TryAcquireIdle acquires an idle session without enqueueing when another
// goroutine wins the race. Durable inbox items already live on disk, so adding
// a second in-memory copy on a failed claim would execute them twice.
func (sm *SessionManager) TryAcquireIdle(key string) bool {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	if sm.active[key] {
		return false
	}
	sm.active[key] = true
	return true
}

func (sm *SessionManager) ReplacePending(key string, msg InboundMessage) QueueResult {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	if !sm.active[key] {
		sm.active[key] = true
		return QueueResult{Acquired: true, Mode: QueueModeInterrupt}
	}
	sm.pending[key] = []pendingTurn{{msg: msg, timestamp: time.Now(), mode: QueueModeFollowup}}
	delete(sm.dropped, key)
	return QueueResult{Queued: true, Pending: 1, Mode: QueueModeInterrupt}
}

// Release 释放 session 锁，返回等待队列中的下一条消息（合并后）。
func (sm *SessionManager) Release(key string) *InboundMessage {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	queue := sm.pending[key]
	if len(queue) == 0 {
		delete(sm.active, key)
		delete(sm.pending, key)
		delete(sm.dropped, key)
		return nil
	}

	mode := NormalizeQueueMode(queue[0].mode)
	var merged *InboundMessage
	if mode == QueueModeFollowup {
		m := queue[0].msg
		merged = &m
		if len(queue) == 1 {
			delete(sm.pending, key)
		} else {
			sm.pending[key] = queue[1:]
		}
		merged.Text = sm.consumeDroppedPrefixLocked(key, merged.Text)
		return merged
	}

	// collect 模式取出等待队列，并合并其中所有消息。
	for i := range queue {
		if merged == nil {
			m := queue[i].msg
			merged = &m
		} else {
			if queue[i].msg.Text != "" {
				merged.Text = merged.Text + "\n" + queue[i].msg.Text
			}
		}
	}
	delete(sm.pending, key)
	merged.Text = sm.consumeDroppedPrefixLocked(key, merged.Text)
	// active 保持 true，因为调用方会立即用 merged 消息开始新 turn
	return merged
}

func (sm *SessionManager) consumeDroppedPrefixLocked(key, text string) string {
	dropped := sm.dropped[key]
	if len(dropped) == 0 {
		return text
	}
	delete(sm.dropped, key)
	var b strings.Builder
	fmt.Fprintf(&b, "[Queue note: %d older pending message(s) were dropped because this bot session reached its queue cap.", len(dropped))
	if len(dropped) > 0 {
		b.WriteString(" Dropped summaries:")
		limit := min(len(dropped), 3)
		for i := range limit {
			fmt.Fprintf(&b, "\n- %s", dropped[i])
		}
		if len(dropped) > limit {
			fmt.Fprintf(&b, "\n- ... and %d more", len(dropped)-limit)
		}
	}
	b.WriteString("]\n\n")
	b.WriteString(text)
	return b.String()
}

func queueSummary(text string) string {
	text = strings.TrimSpace(text)
	if text == "" {
		return "(empty message)"
	}
	runes := []rune(text)
	if len(runes) <= 180 {
		return text
	}
	return string(runes[:180]) + "..."
}

// IsActive 返回 session 是否有正在运行的任务。
func (sm *SessionManager) IsActive(key string) bool {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	return sm.active[key]
}

// Debounce returns the collect-mode merge window.
func (sm *SessionManager) Debounce() time.Duration {
	if sm == nil {
		return 1500 * time.Millisecond
	}
	return sm.debounce
}

// runIfIdle holds the per-gateway admission lock while fn switches runtime
// ownership for key. A normal message cannot become active between the idle
// check and the controller unlink/close sequence.
func (sm *SessionManager) runIfIdle(key string, fn func() bool) bool {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	if sm.active[key] || fn == nil {
		return false
	}
	if !fn() {
		return false
	}
	delete(sm.pending, key)
	delete(sm.dropped, key)
	return true
}

// ActiveCount 返回当前活跃 session 数。
func (sm *SessionManager) ActiveCount() int {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	return len(sm.active)
}

func (sm *SessionManager) PendingCount(key string) int {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	return len(sm.pending[key])
}

func (sm *SessionManager) Snapshot() QueueSnapshot {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	var pending int
	var dropped int
	for _, queue := range sm.pending {
		pending += len(queue)
	}
	for _, summaries := range sm.dropped {
		dropped += len(summaries)
	}
	return QueueSnapshot{
		Active:   len(sm.active),
		Pending:  pending,
		Dropped:  dropped,
		Sessions: len(sm.active) + len(sm.pending),
	}
}

func (sm *SessionManager) QueueMode(key, fallback string) string {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	if mode := sm.modeOverrides[key]; mode != "" {
		return mode
	}
	return NormalizeQueueMode(fallback)
}

func (sm *SessionManager) SetQueueMode(key, mode string) {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	if normalized := NormalizeOptionalQueueMode(mode); normalized != "" {
		sm.modeOverrides[key] = normalized
	}
}

func (sm *SessionManager) ClearQueueMode(key string) {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	delete(sm.modeOverrides, key)
}

// ForceRelease 强制释放 session（用于 session 关闭或错误恢复）。
func (sm *SessionManager) ForceRelease(key string) {
	sm.mu.Lock()
	defer sm.mu.Unlock()
	delete(sm.active, key)
	delete(sm.pending, key)
	delete(sm.dropped, key)
}
