package session

import (
	"context"
	"crypto/sha256"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"

	"xbot/config"
	"xbot/llm"
	"xbot/memory"
	"xbot/storage/sqlite"
	"xbot/tools"
)

// TenantSession represents a single tenant's conversation session
type TenantSession struct {
	tenantID   int64
	channel    string
	chatID     string
	sessionSvc *sqlite.SessionService
	memorySvc  *sqlite.MemoryService // for consolidation state (LastConsolidated)
	memory     memory.MemoryProvider
	mcpManager *tools.SessionMCPManager // 会话 MCP 管理器
	lastActive time.Time                // 会话活跃时间
	mu         sync.RWMutex             // 保护 lastActive 和 cwd
	cwd        string                   // 当前工作目录（PWD 工具优化）
}

// AddMessage adds a message to this tenant's session
func (s *TenantSession) AddMessage(msg llm.ChatMessage) error {
	return s.sessionSvc.AddMessage(s.tenantID, msg)
}

// ReplaceToolMessage updates the most recent matching tool-role message.
// Empty toolName/toolCallID act as wildcards (match any).
func (s *TenantSession) ReplaceToolMessage(toolName, toolCallID, content string) error {
	return s.sessionSvc.ReplaceToolMessage(s.tenantID, toolName, toolCallID, content)
}

// GetHistory retrieves recent messages for LLM context window
func (s *TenantSession) GetHistory(maxMessages int) ([]llm.ChatMessage, error) {
	return s.sessionSvc.GetHistory(s.tenantID, maxMessages)
}

// GetMessages retrieves all messages for this tenant
func (s *TenantSession) GetMessages() ([]llm.ChatMessage, error) {
	return s.sessionSvc.GetAllMessages(s.tenantID)
}

// Len returns the number of messages in this tenant's session
func (s *TenantSession) Len() (int, error) {
	return s.sessionSvc.GetMessagesCount(s.tenantID)
}

// UserMessageCount returns the number of user-role messages (conversation turns).
func (s *TenantSession) UserMessageCount() (int, error) {
	return s.sessionSvc.GetUserMessageCount(s.tenantID)
}

// LastConsolidated returns the last consolidated message index
func (s *TenantSession) LastConsolidated() int {
	lastConsolidated, err := s.memorySvc.GetState(context.Background(), s.tenantID)
	if err != nil {
		// If error, return 0 as safe default
		return 0
	}
	return lastConsolidated
}

// SetLastConsolidated updates the last consolidated message index
func (s *TenantSession) SetLastConsolidated(n int) error {
	return s.memorySvc.SetState(context.Background(), s.tenantID, n)
}

// Clear removes all messages from this tenant's session
func (s *TenantSession) Clear() error {
	return s.sessionSvc.Clear(s.tenantID)
}

// PurgeOldMessages deletes messages older than the most recent `keepCount` messages.
// Returns the number of messages deleted.
func (s *TenantSession) PurgeOldMessages(keepCount int) (int64, error) {
	return s.sessionSvc.PurgeOldMessages(s.tenantID, keepCount)
}

// UpdateMessageContent updates the content of the Nth message (0-indexed) in this tenant's session.
// Used by observation masking to persist masked content back to session.
func (s *TenantSession) UpdateMessageContent(messageIndex int, content string) error {
	return s.sessionSvc.UpdateMessageContent(s.tenantID, messageIndex, content)
}

// UpdateMessageContentNonDisplayOnly updates the content of the Nth non-display-only
// message (0-indexed). Aligns with GetAllMessages() ordering (both filter display_only).
func (s *TenantSession) UpdateMessageContentNonDisplayOnly(messageIndex int, content string) error {
	return s.sessionSvc.UpdateMessageContentNonDisplayOnly(s.tenantID, messageIndex, content)
}

// Memory returns the memory provider for this tenant
func (s *TenantSession) Memory() memory.MemoryProvider {
	return s.memory
}

// TenantID returns the tenant ID
func (s *TenantSession) TenantID() int64 {
	return s.tenantID
}

// SaveContextTokens records the exact API prompt_tokens on the most recent
// user message in this tenant's session.
func (s *TenantSession) SaveContextTokens(promptTokens int64) error {
	return s.sessionSvc.UpdateUserMessageContextTokens(s.tenantID, promptTokens)
}

// GetLastContextTokens returns the context_tokens of the most recent user message.
// Used by rewind to restore accurate token state.
func (s *TenantSession) GetLastContextTokens() (int64, error) {
	return s.sessionSvc.GetLastUserMessageContextTokens(s.tenantID)
}

// MemoryService returns the underlying SQLite memory service for this tenant.
// Used for tenant-level state operations (token state, consolidation state, etc.)
// that are independent of the memory provider implementation.
func (s *TenantSession) MemoryService() *sqlite.MemoryService {
	return s.memorySvc
}

// Channel returns the channel name
func (s *TenantSession) Channel() string {
	return s.channel
}

// ChatID returns the chat ID
func (s *TenantSession) ChatID() string {
	return s.chatID
}

// String returns a string representation of the tenant
func (s *TenantSession) String() string {
	return fmt.Sprintf("%s:%s (tenant_id=%d)", s.channel, s.chatID, s.tenantID)
}

// GetSessionKey 返回会话唯一标识
func (s *TenantSession) GetSessionKey() string {
	return sessKey(s.channel, s.chatID)
}

// MarkActive 更新会话活跃时间
func (s *TenantSession) MarkActive() {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.lastActive = time.Now()
}

// SetMCPManager 设置会话 MCP 管理器
func (s *TenantSession) SetMCPManager(mgr *tools.SessionMCPManager) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.mcpManager = mgr
}

// GetMCPManager 获取 MCP 管理器
func (s *TenantSession) GetMCPManager() *tools.SessionMCPManager {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.mcpManager
}

// LastActive 返回会话最后活跃时间
func (s *TenantSession) LastActive() time.Time {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.lastActive
}

// CleanupInactiveMCPs 清理不活跃的 MCP 连接
// 返回会话最后活跃时间（用于判断会话是否需要从缓存中移除）
func (s *TenantSession) CleanupInactiveMCPs() time.Time {
	s.mu.RLock()
	mgr := s.mcpManager
	s.mu.RUnlock()

	if mgr != nil {
		return mgr.UnloadInactiveServers()
	}
	return s.LastActive()
}

// Close 关闭会话资源
func (s *TenantSession) Close() {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.mcpManager != nil {
		s.mcpManager.Close()
		s.mcpManager = nil
	}
}

// InvalidateMCP 使会话的 MCP 连接失效，强制下次使用时重新加载
func (s *TenantSession) InvalidateMCP() {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.mcpManager != nil {
		s.mcpManager.Invalidate()
	}
}

// GetCurrentDir 获取当前工作目录（PWD 工具优化）
func (s *TenantSession) GetCurrentDir() string {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.cwd
}

// SetCurrentDir 设置当前工作目录（PWD 工具优化），并持久化到磁盘。
// 按 channel:chatID 唯一标识持久化，同一用户的多个会话互不干扰。
func (s *TenantSession) SetCurrentDir(dir string) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.cwd = dir

	// Persist CWD to disk so it survives server restarts.
	// Keyed by session (channel:chatID) not tenantID — multiple sessions
	// under the same user must have independent CWDs.
	cwdDir := filepath.Join(config.XbotHome(), "session_cwd")
	if err := os.MkdirAll(cwdDir, 0700); err == nil {
		cwdFile := filepath.Join(cwdDir, sessionCwdFileName(s.channel, s.chatID))
		_ = os.WriteFile(cwdFile, []byte(dir), 0600)
	}
}

// sessionCwdFileName returns a safe filename for the given session.
// Uses SHA256 hash of "channel:chatID" to avoid filesystem-unsafe characters.
func sessionCwdFileName(channel, chatID string) string {
	h := sha256.Sum256([]byte(sessKey(channel, chatID)))
	return fmt.Sprintf("%x.txt", h[:16])
}

// loadPersistedCWD tries to restore CWD from disk after a restart.
func loadPersistedCWD(channel, chatID string) string {
	cwdFile := filepath.Join(config.XbotHome(), "session_cwd", sessionCwdFileName(channel, chatID))
	data, err := os.ReadFile(cwdFile)
	if err != nil {
		return ""
	}
	return string(data)
}
