package core

import (
	"fmt"
	"sync"
	"time"
)

// BaseBot provides common functionality for all bot implementations
type BaseBot struct {
	config   *Config
	status   BotStatus
	handlers *eventHandlers
	mu       sync.RWMutex
	logger   Logger
}

// eventHandlers stores event handlers
type eventHandlers struct {
	message      []func(Message)
	error        []func(error)
	connected    []func()
	disconnected []func()
	ready        []func()
}

// NewBaseBot creates a new base bot
func NewBaseBot(config *Config) *BaseBot {
	return &BaseBot{
		config: config,
		status: BotStatus{
			Connection: &ConnectionDetails{
				Mode: ConnectionModePolling,
			},
		},
		handlers: &eventHandlers{
			message:      make([]func(Message), 0),
			error:        make([]func(error), 0),
			connected:    make([]func(), 0),
			disconnected: make([]func(), 0),
			ready:        make([]func(), 0),
		},
		logger: NewLogger(config.Logging),
	}
}

// UUID returns the bot's unique identifier
func (b *BaseBot) UUID() string {
	if b.config == nil {
		return ""
	}
	return b.config.UUID
}

// Config returns the bot configuration
func (b *BaseBot) Config() *Config {
	return b.config
}

// IsConnected returns whether the bot is connected
func (b *BaseBot) IsConnected() bool {
	b.mu.RLock()
	defer b.mu.RUnlock()
	return b.status.Connected
}

// IsReady returns whether the bot is ready
func (b *BaseBot) IsReady() bool {
	b.mu.RLock()
	defer b.mu.RUnlock()
	return b.status.Ready
}

// Status returns the current bot status
func (b *BaseBot) Status() *BotStatus {
	b.mu.RLock()
	defer b.mu.RUnlock()

	// Return a copy to avoid race conditions
	status := b.status
	if status.Connection != nil {
		conn := *status.Connection
		status.Connection = &conn
	}

	return &status
}

// SetStatus updates the bot status
func (b *BaseBot) SetStatus(status BotStatus) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.status = status
}

// UpdateConnected updates the connected state
func (b *BaseBot) UpdateConnected(connected bool) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.status.Connected = connected

	if connected && b.status.Connection != nil {
		b.status.Connection.ConnectedAt = time.Now().Unix()
		b.status.Connection.ReconnectAttempts = 0
	}
}

// UpdateAuthenticated updates the authenticated state
func (b *BaseBot) UpdateAuthenticated(authenticated bool) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.status.Authenticated = authenticated
}

// UpdateReady updates the ready state
func (b *BaseBot) UpdateReady(ready bool) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.status.Ready = ready
}

// MarkConnected marks the bot connected and authenticated, then emits connected.
func (b *BaseBot) MarkConnected(authenticated bool) {
	b.UpdateConnected(true)
	b.UpdateAuthenticated(authenticated)
	b.EmitConnected()
}

// MarkReady marks the bot ready and emits ready.
func (b *BaseBot) MarkReady() {
	b.UpdateReady(true)
	b.EmitReady()
}

// MarkDisconnected marks the bot disconnected and not ready, then emits disconnected.
func (b *BaseBot) MarkDisconnected() {
	b.UpdateConnected(false)
	b.UpdateReady(false)
	b.EmitDisconnected()
}

// UpdateLastActivity updates the last activity timestamp
func (b *BaseBot) UpdateLastActivity() {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.status.LastActivity = time.Now().Unix()
}

// SetError sets an error on the status
func (b *BaseBot) SetError(err error) {
	b.mu.Lock()
	defer b.mu.Unlock()

	if err != nil {
		b.status.Error = err.Error()
	} else {
		b.status.Error = ""
	}
}

// ClearError clears the error from the status
func (b *BaseBot) ClearError() {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.status.Error = ""
}

// Logger returns the logger
func (b *BaseBot) Logger() Logger {
	return b.logger
}

// EnsureReady checks if the bot is ready and returns an error if not
func (b *BaseBot) EnsureReady() error {
	if !b.IsReady() {
		return NewBotError(ErrConnectionFailed, "bot is not ready", false)
	}
	return nil
}

// OnMessage registers a message handler
func (b *BaseBot) OnMessage(handler func(Message)) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.handlers.message = append(b.handlers.message, handler)
}

// OnError registers an error handler
func (b *BaseBot) OnError(handler func(error)) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.handlers.error = append(b.handlers.error, handler)
}

// OnConnected registers a connected handler
func (b *BaseBot) OnConnected(handler func()) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.handlers.connected = append(b.handlers.connected, handler)
}

// OnDisconnected registers a disconnected handler
func (b *BaseBot) OnDisconnected(handler func()) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.handlers.disconnected = append(b.handlers.disconnected, handler)
}

// OnReady registers a ready handler
func (b *BaseBot) OnReady(handler func()) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.handlers.ready = append(b.handlers.ready, handler)
}

// EmitMessage emits a message event
func (b *BaseBot) EmitMessage(msg Message) {
	b.UpdateLastActivity()
	b.emitMessageHandlers(b.snapshotMessageHandlers(), msg, "message")
}

// EmitError emits an error event
func (b *BaseBot) EmitError(err error) {
	b.emitErrorHandlers(b.snapshotErrorHandlers(), err, "error")
}

// EmitConnected emits a connected event
func (b *BaseBot) EmitConnected() {
	b.emitVoidHandlers(b.snapshotConnectedHandlers(), "connected")
}

// EmitDisconnected emits a disconnected event
func (b *BaseBot) EmitDisconnected() {
	b.emitVoidHandlers(b.snapshotDisconnectedHandlers(), "disconnected")
}

// EmitReady emits a ready event
func (b *BaseBot) EmitReady() {
	b.emitVoidHandlers(b.snapshotReadyHandlers(), "ready")
}

func (b *BaseBot) snapshotMessageHandlers() []func(Message) {
	b.mu.RLock()
	defer b.mu.RUnlock()
	handlers := make([]func(Message), len(b.handlers.message))
	copy(handlers, b.handlers.message)
	return handlers
}

func (b *BaseBot) snapshotErrorHandlers() []func(error) {
	b.mu.RLock()
	defer b.mu.RUnlock()
	handlers := make([]func(error), len(b.handlers.error))
	copy(handlers, b.handlers.error)
	return handlers
}

func (b *BaseBot) snapshotConnectedHandlers() []func() {
	b.mu.RLock()
	defer b.mu.RUnlock()
	handlers := make([]func(), len(b.handlers.connected))
	copy(handlers, b.handlers.connected)
	return handlers
}

func (b *BaseBot) snapshotDisconnectedHandlers() []func() {
	b.mu.RLock()
	defer b.mu.RUnlock()
	handlers := make([]func(), len(b.handlers.disconnected))
	copy(handlers, b.handlers.disconnected)
	return handlers
}

func (b *BaseBot) snapshotReadyHandlers() []func() {
	b.mu.RLock()
	defer b.mu.RUnlock()
	handlers := make([]func(), len(b.handlers.ready))
	copy(handlers, b.handlers.ready)
	return handlers
}

func (b *BaseBot) emitMessageHandlers(handlers []func(Message), msg Message, event string) {
	for _, handler := range handlers {
		go func(h func(Message)) {
			defer b.recoverHandler(event)
			h(msg)
		}(handler)
	}
}

func (b *BaseBot) emitErrorHandlers(handlers []func(error), err error, event string) {
	for _, handler := range handlers {
		go func(h func(error)) {
			defer b.recoverHandler(event)
			h(err)
		}(handler)
	}
}

func (b *BaseBot) emitVoidHandlers(handlers []func(), event string) {
	for _, handler := range handlers {
		go func(h func()) {
			defer b.recoverHandler(event)
			h()
		}(handler)
	}
}

func (b *BaseBot) recoverHandler(event string) {
	if r := recover(); r != nil {
		b.Logger().Error(fmt.Sprintf("panic in %s handler: %v", event, r))
	}
}

// ClearHandlers clears all event handlers
func (b *BaseBot) ClearHandlers() {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.handlers.message = nil
	b.handlers.error = nil
	b.handlers.connected = nil
	b.handlers.disconnected = nil
	b.handlers.ready = nil
}

// ValidateTextLength validates text length against platform limit
func (b *BaseBot) ValidateTextLength(text string) error {
	caps := GetPlatformCapabilities(b.config.Platform)
	if caps.TextLimit > 0 && len(text) > caps.TextLimit {
		return NewMessageTooLongError(b.config.Platform, len(text), caps.TextLimit)
	}
	return nil
}

// GetMessageLimit returns the message length limit for this bot's platform
func (b *BaseBot) GetMessageLimit() int {
	caps := GetPlatformCapabilities(b.config.Platform)
	if caps.TextLimit > 0 {
		return caps.TextLimit
	}
	return 4000 // Default fallback
}

// ChunkText chunks text into smaller parts based on platform limit
func (b *BaseBot) ChunkText(text string) []string {
	caps := GetPlatformCapabilities(b.config.Platform)
	if caps.TextLimit <= 0 || len(text) <= caps.TextLimit {
		return []string{text}
	}

	var chunks []string
	remaining := text
	limit := caps.TextLimit

	for len(remaining) > limit {
		breakPoint := b.findBreakPoint(remaining, limit)
		chunks = append(chunks, remaining[:breakPoint])
		remaining = remaining[breakPoint:]
	}

	if len(remaining) > 0 {
		chunks = append(chunks, remaining)
	}

	return chunks
}

// findBreakPoint finds a good break point for chunking
// Avoids breaking inside code blocks (``` or `)
func (b *BaseBot) findBreakPoint(text string, limit int) int {
	// First, check if we're inside a code block at the limit point
	inCodeBlock := false
	codeBlockChar := rune(0)

	for i := 0; i < limit && i < len(text); i++ {
		if text[i] == '`' {
			if i+2 < len(text) && text[i:i+3] == "```" {
				if !inCodeBlock {
					// Start of code block
					inCodeBlock = true
					codeBlockChar = '`'
					i += 2
				} else if codeBlockChar == '`' {
					// End of code block
					inCodeBlock = false
					codeBlockChar = 0
					i += 2
				}
			} else if !inCodeBlock {
				// Start of inline code
				inCodeBlock = true
				codeBlockChar = '`'
			} else if codeBlockChar == '`' && (i == 0 || text[i-1] != '`') {
				// End of inline code
				inCodeBlock = false
				codeBlockChar = 0
			}
		}
	}

	// If we're inside a code block at the limit, try to extend to find the end
	if inCodeBlock {
		// Look for the end of the code block (up to 50% beyond limit)
		extendLimit := limit * 3 / 2
		if extendLimit > len(text) {
			extendLimit = len(text)
		}
		for i := limit; i < extendLimit; i++ {
			if text[i] == '`' {
				if i+2 < len(text) && text[i:i+3] == "```" && codeBlockChar == '`' {
					return i + 3 // Break after code block end
				} else if codeBlockChar == '`' {
					return i + 1 // Break after inline code end
				}
			}
		}
		// If we can't find the end, just break at newline if possible
	}

	// Try to break at newline (within 30% to 100% of limit)
	for i := limit - 1; i >= limit*7/10 && i >= 0; i-- {
		if text[i] == '\n' {
			return i + 1
		}
	}

	// Try to break at space (within 70% to 100% of limit)
	for i := limit - 1; i >= limit*7/10 && i >= 0; i-- {
		if text[i] == ' ' {
			return i + 1
		}
	}

	// Hard break at limit
	return limit
}

// ChunkTextForPlatform splits text into chunks within the platform's message size limit.
// It uses the same code-block-aware break-point logic as BaseBot.ChunkText.
func ChunkTextForPlatform(platform Platform, text string) []string {
	caps := GetPlatformCapabilities(platform)
	if caps == nil || caps.TextLimit <= 0 || len(text) <= caps.TextLimit {
		return []string{text}
	}

	// Reuse the method logic via a temporary BaseBot with minimal config.
	b := &BaseBot{config: &Config{Platform: platform}}
	var chunks []string
	remaining := text
	limit := caps.TextLimit

	for len(remaining) > limit {
		breakPoint := b.findBreakPoint(remaining, limit)
		chunks = append(chunks, remaining[:breakPoint])
		remaining = remaining[breakPoint:]
	}
	if len(remaining) > 0 {
		chunks = append(chunks, remaining)
	}
	return chunks
}

// Close closes the base bot resources
func (b *BaseBot) Close() error {
	b.ClearHandlers()
	return nil
}
