package main

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"log"
	"math/rand"
	"net"
	"strings"
	"sync"
	"time"
)

// Handle CAP negotiation and capability enabling
func (c *Client) handleCap(parts []string) {
	if len(parts) < 2 {
		return
	}
	subcmd := strings.ToUpper(parts[1])
	
	serverName := "techircd.local"
	if c.server != nil && c.server.config != nil {
		serverName = c.server.config.Server.Name
	}
	
	switch subcmd {
	case "LS":
		// Start CAP negotiation
		c.capNegotiation = true
		
		// Advertise capabilities - using proper IRC format
		capabilities := "away-notify chghost invite-notify multi-prefix userhost-in-names server-time message-tags account-tag account-notify"
		c.SendMessage(fmt.Sprintf(":%s CAP * LS :%s", serverName, capabilities))
		
	case "REQ":
		if len(parts) < 3 {
			return
		}
		// Join all parts from [2] onwards to handle multi-word capability lists
		requestedCaps := strings.Join(parts[2:], " ")
		if strings.HasPrefix(requestedCaps, ":") {
			requestedCaps = requestedCaps[1:]
		}
		
		caps := strings.Fields(requestedCaps)
		ack := []string{}
		for _, cap := range caps {
			// Accept common capabilities
			switch cap {
			case "away-notify", "chghost", "invite-notify", "multi-prefix", "userhost-in-names", "server-time", "message-tags", "account-tag", "account-notify":
				c.SetCapability(cap, true)
				ack = append(ack, cap)
			}
		}
		if len(ack) > 0 {
			c.SendMessage(fmt.Sprintf(":%s CAP %s ACK :%s", serverName, c.Nick(), strings.Join(ack, " ")))
		}
		
	case "END":
		c.capNegotiation = false
		// Try to complete registration now that CAP negotiation is done
		c.checkRegistration()
		
	case "LIST":
		// List active capabilities for this client
		c.mu.RLock()
		var activeCaps []string
		for cap := range c.capabilities {
			activeCaps = append(activeCaps, cap)
		}
		c.mu.RUnlock()
		
		capsList := strings.Join(activeCaps, " ")
		c.SendMessage(fmt.Sprintf(":%s CAP %s LIST :%s", serverName, c.Nick(), capsList))
	}
}

type Client struct {
	conn         net.Conn
	nick         string
	user         string
	realname     string
	host         string
	server       *Server
	channels     map[string]*Channel
	modes        map[rune]bool
	away         string
	oper         bool
	operClass    string // Operator class name
	ssl          bool
	registered   bool
	account      string    // Services account name
	connectTime  time.Time // When the client connected
	lastActivity time.Time // Last time client sent a message

	// Unique client ID for server tracking
	clientID string

	// Connection stability tracking
	disconnected   bool      // Flag to track if client is disconnected
	writeErrors    int       // Count of consecutive write errors
	maxWriteErrors int       // Maximum write errors before disconnection
	lastWriteError time.Time // Time of last write error

	// Flood protection
	lastMessage  time.Time
	messageCount int

	// SASL authentication
	saslMech string
	saslData string

	// IRCv3 capabilities
	capabilities   map[string]bool
	capNegotiation bool // True if client is in CAP negotiation

	// Server Notice Masks (snomasks) for operators
	snomasks map[rune]bool

	// Ping timeout tracking
	lastPong       time.Time
	waitingForPong bool

	// SILENCE and MONITOR lists
	silenceList []string
	monitorList []string

	mu sync.RWMutex
}

func NewClient(conn net.Conn, server *Server) *Client {
	host, _, _ := net.SplitHostPort(conn.RemoteAddr().String())

	// Check if connection is SSL
	isSSL := false
	if _, ok := conn.(*tls.Conn); ok {
		isSSL = true
	}

	// Generate unique client ID
	clientID := fmt.Sprintf("%s_%d_%d", host, time.Now().Unix(), rand.Intn(10000))

	client := &Client{
		clientID:       clientID,
		conn:           conn,
		host:           host,
		server:         server,
		channels:       make(map[string]*Channel),
		modes:          make(map[rune]bool),
		capabilities:   make(map[string]bool),
		capNegotiation: false,
		snomasks:       make(map[rune]bool),
		silenceList:    make([]string, 0),
		monitorList:    make([]string, 0),
		ssl:            isSSL,
		connectTime:    time.Now(),
		lastActivity:   time.Now(),
		lastMessage:    time.Now(),
		lastPong:       time.Now(),
		waitingForPong: false,
		// Connection stability
		disconnected:   false,
		writeErrors:    0,
		maxWriteErrors: 3, // Allow 3 consecutive write errors before marking disconnected
	}

	// Set SSL user mode if connected via SSL
	if isSSL {
		client.SetMode('z', true)
	}

	return client
}

func (c *Client) SendMessage(message string) {
	c.mu.Lock()
	defer c.mu.Unlock()

	// Enhanced connection health check
	if c.conn == nil {
		log.Printf("SendMessage: connection is nil for client %s", c.Nick())
		return
	}

	// Validate message before sending
	if message == "" {
		return
	}

	// Prevent sending to disconnected clients
	if c.isDisconnected() {
		return
	}
	
	// Debug logging for outgoing messages
	if DebugMode {
		log.Printf(">>> SEND to %s: %s", c.getClientInfoUnsafe(), message)
	}

	// Set write deadline to prevent hanging with retry mechanism
	writeTimeout := 15 * time.Second
	c.conn.SetWriteDeadline(time.Now().Add(writeTimeout))
	defer func() {
		// Always clear deadline, ignore errors on disconnected connections
		if c.conn != nil {
			c.conn.SetWriteDeadline(time.Time{})
		}
	}()

	// Attempt to write with error recovery
	_, err := fmt.Fprintf(c.conn, "%s\r\n", message)
	if err != nil {
		// Enhanced error logging with client identification
		clientInfo := c.getClientInfoUnsafe()
		log.Printf("Error sending message to %s: %v (msg: %.50s...)", clientInfo, err, message)

		// Mark client for disconnection on write errors
		c.markDisconnected()

		// Don't attempt to send error messages that could cause recursion
		return
	}

	// Reset write error counter on successful write
	c.resetWriteErrors()

	// Only log PONG messages in debug mode to reduce log spam
	if strings.HasPrefix(message, "PONG") && c.server != nil && c.server.config != nil {
		if c.server.config.Logging.Level == "debug" {
			log.Printf("Successfully sent to client %s: %s", c.getClientInfoUnsafe(), message)
		}
	}
}

func (c *Client) SendFrom(source, message string) {
	c.SendMessage(fmt.Sprintf(":%s %s", source, message))
}

func (c *Client) SendNumeric(code int, message string) {
	if c.server == nil || c.server.config == nil {
		return
	}
	c.SendFrom(c.server.config.Server.Name, fmt.Sprintf("%03d %s %s", code, c.Nick(), message))
}

func (c *Client) Nick() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.nick
}

func (c *Client) SetNick(nick string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.nick = nick
}

func (c *Client) User() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.user
}

func (c *Client) SetUser(user string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.user = user
}

func (c *Client) Realname() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.realname
}

func (c *Client) SetRealname(realname string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.realname = realname
}

func (c *Client) Host() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.host
}

// HostForUser returns the appropriate hostname to show to a requesting user
// based on privacy settings and the requester's privileges
func (c *Client) HostForUser(requester *Client) string {
	c.mu.RLock()
	defer c.mu.RUnlock()

	// If host hiding is disabled, always show real host
	if !c.server.config.Privacy.HideHostsFromUsers {
		return c.host
	}

	// If requester is an operator and bypass is enabled, show real host
	if requester != nil && requester.IsOper() && c.server.config.Privacy.OperBypassHostHide {
		return c.host
	}

	// If requester is viewing themselves, show real host
	if requester != nil && requester.Nick() == c.Nick() {
		return c.host
	}

	// Check if user has +x mode set (host masking)
	if c.HasMode('x') {
		// Return masked hostname
		return c.nick + "." + c.server.config.Privacy.MaskedHostSuffix
	}

	// Default behavior: show masked host when privacy is enabled
	return c.nick + "." + c.server.config.Privacy.MaskedHostSuffix
}

// canSeeWhoisInfo checks if the requester can see specific WHOIS information about the target
func (c *Client) canSeeWhoisInfo(target *Client, infoType string) bool {
	config := c.server.config.WhoisFeatures

	var toEveryone, toOpers, toSelf bool

	switch infoType {
	case "user_modes":
		toEveryone = config.ShowUserModes.ToEveryone
		toOpers = config.ShowUserModes.ToOpers
		toSelf = config.ShowUserModes.ToSelf
	case "ssl_status":
		toEveryone = config.ShowSSLStatus.ToEveryone
		toOpers = config.ShowSSLStatus.ToOpers
		toSelf = config.ShowSSLStatus.ToSelf
	case "idle_time":
		toEveryone = config.ShowIdleTime.ToEveryone
		toOpers = config.ShowIdleTime.ToOpers
		toSelf = config.ShowIdleTime.ToSelf
	case "signon_time":
		toEveryone = config.ShowSignonTime.ToEveryone
		toOpers = config.ShowSignonTime.ToOpers
		toSelf = config.ShowSignonTime.ToSelf
	case "real_host":
		toEveryone = config.ShowRealHost.ToEveryone
		toOpers = config.ShowRealHost.ToOpers
		toSelf = config.ShowRealHost.ToSelf
	case "oper_class":
		toEveryone = config.ShowOperClass.ToEveryone
		toOpers = config.ShowOperClass.ToOpers
		toSelf = config.ShowOperClass.ToSelf
	case "client_info":
		toEveryone = config.ShowClientInfo.ToEveryone
		toOpers = config.ShowClientInfo.ToOpers
		toSelf = config.ShowClientInfo.ToSelf
	case "account_name":
		toEveryone = config.ShowAccountName.ToEveryone
		toOpers = config.ShowAccountName.ToOpers
		toSelf = config.ShowAccountName.ToSelf
	default:
		return false
	}

	// Check if target is viewing themselves
	if c.Nick() == target.Nick() && toSelf {
		return true
	}

	// Check if requester is an operator
	if c.IsOper() && toOpers {
		return true
	}

	// Check if everyone can see this info
	if toEveryone {
		return true
	}

	return false
}

// canSeeChannels checks if the requester can see channel information
func (c *Client) canSeeChannels(target *Client) bool {
	config := c.server.config.WhoisFeatures.ShowChannels

	// Check if target is viewing themselves
	if c.Nick() == target.Nick() && config.ToSelf {
		return true
	}

	// Check if requester is an operator
	if c.IsOper() && config.ToOpers {
		return true
	}

	// Check if everyone can see this info
	if config.ToEveryone {
		return true
	}

	return false
}

// UpdateActivity updates the last activity time for the client
func (c *Client) UpdateActivity() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.lastActivity = time.Now()
}

// SetAccount sets the account name for services integration
func (c *Client) SetAccount(account string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	oldAccount := c.account
	c.account = account
	// IRCv3 account-notify: send ACCOUNT message if capability enabled and account changes
	if c.HasCapability("account-notify") && oldAccount != account {
		msg := "ACCOUNT "
		if account == "" {
			msg += "*"
		} else {
			msg += account
		}
		c.SendMessage(msg)
	}
}

// Account returns the account name
func (c *Client) Account() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.account
}

// ConnectTime returns when the client connected
func (c *Client) ConnectTime() time.Time {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.connectTime
}

// LastActivity returns the last activity time
func (c *Client) LastActivity() time.Time {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.lastActivity
}

// SetOperClass sets the operator class for this client
func (c *Client) SetOperClass(class string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.operClass = class
}

// OperClass returns the operator class
func (c *Client) OperClass() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.operClass
}

// HasOperPermission checks if the client has a specific operator permission
func (c *Client) HasOperPermission(permission string) bool {
	if !c.IsOper() {
		return false
	}

	// Load oper config and check permissions
	operConfig, err := LoadOperConfig(c.server.config.OperConfig.ConfigFile)
	if err != nil || !c.server.config.OperConfig.Enable {
		// Fallback to legacy system
		return true // Basic oper permissions
	}

	return operConfig.HasPermission(c.Nick(), permission)
}

// GetOperRank returns the operator rank (higher number = higher authority)
func (c *Client) GetOperRank() int {
	if !c.IsOper() {
		return 0
	}

	operConfig, err := LoadOperConfig(c.server.config.OperConfig.ConfigFile)
	if err != nil || !c.server.config.OperConfig.Enable {
		return 1 // Basic rank for legacy
	}

	return operConfig.GetOperRank(c.Nick())
}

// CanOperateOn checks if this operator can perform actions on another operator
func (c *Client) CanOperateOn(target *Client) bool {
	if !c.IsOper() {
		return false
	}

	if !target.IsOper() {
		return true // Opers can operate on regular users
	}

	operConfig, err := LoadOperConfig(c.server.config.OperConfig.ConfigFile)
	if err != nil || !c.server.config.OperConfig.Enable {
		return true // Legacy behavior
	}

	return operConfig.CanOperateOn(c.Nick(), target.Nick())
}

// GetOperSymbol returns the symbol for this operator class
func (c *Client) GetOperSymbol() string {
	if !c.IsOper() {
		return ""
	}

	operConfig, err := LoadOperConfig(c.server.config.OperConfig.ConfigFile)
	if err != nil || !c.server.config.OperConfig.Enable {
		return "*" // Default symbol
	}

	class := operConfig.GetOperClass(c.OperClass())
	if class == nil {
		return "*"
	}

	return class.Symbol
}

func (c *Client) IsRegistered() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.registered
}

func (c *Client) SetRegistered(registered bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.registered = registered
}

func (c *Client) IsOper() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.oper
}

func (c *Client) SetOper(oper bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.oper = oper
}

func (c *Client) IsSSL() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.ssl
}

func (c *Client) Away() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.away
}

func (c *Client) SetAway(away string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.away = away
}

func (c *Client) HasMode(mode rune) bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.modes[mode]
}

func (c *Client) SetMode(mode rune, set bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if set {
		c.modes[mode] = true
	} else {
		delete(c.modes, mode)
	}
}

func (c *Client) GetModes() string {
	c.mu.RLock()
	defer c.mu.RUnlock()

	var modes []rune
	for mode := range c.modes {
		modes = append(modes, mode)
	}

	if len(modes) == 0 {
		return ""
	}

	return "+" + string(modes)
}

func (c *Client) HasSnomask(snomask rune) bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.snomasks[snomask]
}

func (c *Client) SetSnomask(snomask rune, set bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if set {
		c.snomasks[snomask] = true
	} else {
		delete(c.snomasks, snomask)
	}
}

func (c *Client) GetSnomasks() string {
	c.mu.RLock()
	defer c.mu.RUnlock()

	var snomasks []rune
	for snomask := range c.snomasks {
		snomasks = append(snomasks, snomask)
	}

	if len(snomasks) == 0 {
		return ""
	}

	return "+" + string(snomasks)
}

// HasGodMode returns true if the client has god mode enabled (user mode +G)
func (c *Client) HasGodMode() bool {
	return c.HasMode('G') && c.HasOperPermission("god_mode")
}

// HasStealthMode returns true if the client has stealth mode enabled (user mode +S)
func (c *Client) HasStealthMode() bool {
	return c.HasMode('S') && c.HasOperPermission("stealth_mode")
}

// IsVisibleTo returns true if this client should be visible to the target client
func (c *Client) IsVisibleTo(target *Client) bool {
	// If client doesn't have stealth mode, always visible
	if !c.HasStealthMode() {
		return true
	}

	// If target is an operator, stealth users are visible
	if target.IsOper() {
		return true
	}

	// If target is the stealth user themselves, always visible
	if target == c {
		return true
	}

	// Otherwise, stealth users are invisible to normal users
	return false
}

// CanBypassChannelRestrictions returns true if the client can bypass channel restrictions
func (c *Client) CanBypassChannelRestrictions() bool {
	return c.HasGodMode()
}

func (c *Client) AddChannel(channel *Channel) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.channels[strings.ToLower(channel.name)] = channel
}

func (c *Client) RemoveChannel(channelName string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	delete(c.channels, strings.ToLower(channelName))
}

func (c *Client) IsInChannel(channelName string) bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	_, exists := c.channels[strings.ToLower(channelName)]
	return exists
}

func (c *Client) GetChannels() map[string]*Channel {
	c.mu.RLock()
	defer c.mu.RUnlock()

	channels := make(map[string]*Channel)
	for name, channel := range c.channels {
		channels[name] = channel
	}
	return channels
}

func (c *Client) Prefix() string {
	return fmt.Sprintf("%s!%s@%s", c.Nick(), c.User(), c.Host())
}

// sendSnomask sends a server notice to all operators with the specified snomask
func (c *Client) sendSnomask(snomask rune, message string) {
	if c.server == nil {
		return
	}

	serverName := "localhost"
	if c.server.config != nil {
		serverName = c.server.config.Server.Name
	}

	// Send to all operators with this snomask
	c.server.mu.RLock()
	clients := make([]*Client, 0, len(c.server.clients))
	for _, client := range c.server.clients {
		clients = append(clients, client)
	}
	c.server.mu.RUnlock()

	for _, client := range clients {
		if client.IsOper() && client.HasSnomask(snomask) {
			client.SendMessage(fmt.Sprintf(":%s NOTICE %s :*** %s", serverName, client.Nick(), message))
		}
	}
}

// getServerConfig safely returns the server config, or nil if not available
func (c *Client) getServerConfig() *Config {
	if c.server == nil {
		return nil
	}
	return c.server.config
}

// getRegistrationTimeout safely gets the registration timeout duration
func (c *Client) getRegistrationTimeout() time.Duration {
	config := c.getServerConfig()
	if config == nil {
		return 60 * time.Second // default 60 seconds
	}
	return config.RegistrationTimeoutDuration()
}

func (c *Client) CheckFlood() bool {
	c.mu.Lock()
	defer c.mu.Unlock()

	// Enhanced nil checks and stability
	if c.server == nil || c.server.config == nil {
		return false
	}

	// IRC operators are exempt from flood protection
	if c.oper {
		return false
	}

	// Be very lenient with flood protection for unregistered clients
	// during the initial connection phase (first 60 seconds)
	if !c.registered {
		// Allow up to 100 commands per minute for unregistered clients
		now := time.Now()
		if now.Sub(c.lastMessage) > 60*time.Second {
			c.messageCount = 0
		}
		c.messageCount++
		c.lastMessage = now
		return c.messageCount > 100
	}

	// For registered clients, use enhanced flood protection
	now := time.Now()
	floodWindow := time.Duration(c.server.config.Limits.FloodSeconds) * time.Second

	// Ensure minimum flood window to prevent issues
	if floodWindow < 10*time.Second {
		floodWindow = 10 * time.Second
	}

	if now.Sub(c.lastMessage) > floodWindow {
		c.messageCount = 0
	}

	c.messageCount++
	c.lastMessage = now

	// Use higher limits than configured for better user experience
	// and prevent false positives
	configuredLimit := c.server.config.Limits.FloodLines
	if configuredLimit < 5 {
		configuredLimit = 5 // Minimum reasonable limit
	}

	maxLines := configuredLimit * 3 // Triple the configured limit
	if maxLines > 100 {
		maxLines = 100 // Cap at reasonable maximum
	}

	exceeded := c.messageCount > maxLines

	// Log flood attempts for debugging
	if exceeded {
		log.Printf("Flood limit exceeded for %s: %d messages in %v (limit: %d)",
			c.getClientInfoUnsafe(), c.messageCount, floodWindow, maxLines)
	}

	return exceeded
}

func (c *Client) HasCapability(cap string) bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.capabilities[cap]
}

func (c *Client) SetCapability(cap string, enabled bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if enabled {
		c.capabilities[cap] = true
	} else {
		delete(c.capabilities, cap)
	}
}

// Connection stability helper methods

// isDisconnected checks if the client is marked as disconnected
func (c *Client) isDisconnected() bool {
	// Don't need to lock here since we're already locked in SendMessage
	return c.disconnected
}

// markDisconnected marks the client as disconnected
func (c *Client) markDisconnected() {
	// Don't need to lock here since we're already locked in SendMessage
	if !c.disconnected {
		c.disconnected = true
		c.writeErrors++
		c.lastWriteError = time.Now()
		log.Printf("Client %s marked as disconnected after %d write errors", c.getClientInfoUnsafe(), c.writeErrors)
	}
}

// getClientInfo returns identifying information about the client (thread-safe)
func (c *Client) getClientInfo() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.getClientInfoUnsafe()
}

// getClientInfoUnsafe returns identifying information about the client (not thread-safe)
func (c *Client) getClientInfoUnsafe() string {
	nick := c.nick
	if nick == "" {
		nick = "unknown"
	}
	host := c.host
	if host == "" {
		host = "unknown"
	}
	return fmt.Sprintf("%s@%s[%s]", nick, host, c.clientID)
}

// IsConnected checks if the client connection is still valid
func (c *Client) IsConnected() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.conn != nil && !c.disconnected
}

// resetWriteErrors resets the write error counter (called on successful writes)
func (c *Client) resetWriteErrors() {
	// Don't need to lock here since we're already locked in SendMessage
	if c.writeErrors > 0 {
		c.writeErrors = 0
		c.lastWriteError = time.Time{}
	}
}

// HealthCheck performs a comprehensive health check on the client
func (c *Client) HealthCheck() (bool, string) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	// Check if disconnected
	if c.disconnected {
		return false, "client marked as disconnected"
	}

	// Check connection validity
	if c.conn == nil {
		return false, "connection is nil"
	}

	// Check for excessive write errors
	if c.writeErrors >= c.maxWriteErrors {
		return false, fmt.Sprintf("too many write errors (%d/%d)", c.writeErrors, c.maxWriteErrors)
	}

	// Check for stale connections (inactive for too long)
	maxInactiveTime := 30 * time.Minute
	if !c.registered {
		maxInactiveTime = 5 * time.Minute // Shorter timeout for unregistered clients
	}

	if time.Since(c.lastActivity) > maxInactiveTime {
		return false, fmt.Sprintf("inactive for %v (max: %v)", time.Since(c.lastActivity), maxInactiveTime)
	}

	// Check for registration timeout
	if !c.registered && time.Since(c.connectTime) > 5*time.Minute {
		return false, fmt.Sprintf("registration timeout: connected %v ago but not registered", time.Since(c.connectTime))
	}

	return true, "healthy"
}

// ForceDisconnect forcefully disconnects the client with a reason
func (c *Client) ForceDisconnect(reason string) {
	log.Printf("Force disconnecting client %s: %s", c.getClientInfo(), reason)

	c.mu.Lock()
	c.disconnected = true
	c.mu.Unlock()

	// Send error message if possible
	if c.conn != nil {
		c.SendMessage(fmt.Sprintf("ERROR :%s", reason))
	}
}

func (c *Client) Handle() {
	defer func() {
		// Enhanced panic recovery with detailed logging
		if r := recover(); r != nil {
			log.Printf("PANIC in client handler for %s: %v", c.getClientInfo(), r)
			// Log stack trace for debugging
			log.Printf("Stack trace: %v", r)
		}

		// Enhanced cleanup with error handling
		c.cleanup()
	}()

	// Validate initial state
	if c.server == nil || c.server.config == nil {
		log.Printf("Client handler: server or config is nil for %s", c.getClientInfo())
		return
	}

	log.Printf("Starting client handler for %s", c.getClientInfo())

	scanner := bufio.NewScanner(c.conn)

	// Set maximum line length to prevent memory exhaustion
	const maxLineLength = 4096
	scanner.Buffer(make([]byte, maxLineLength), maxLineLength)

	// Set initial read deadline - be more generous during connection setup
	c.setReadDeadline(10 * time.Minute)

	// Set registration timeout
	registrationTimer := time.NewTimer(c.getRegistrationTimeout())
	defer registrationTimer.Stop()
	registrationActive := true

	// Initialize ping state
	c.mu.Lock()
	c.lastPong = time.Now()
	c.lastActivity = time.Now() // Set initial activity time
	c.waitingForPong = false
	c.mu.Unlock()

	connectionStartTime := time.Now()

	// Create a context for graceful shutdown
	done := make(chan bool, 1)
	defer close(done)

	// Main message processing loop with timeout
	for {
		// Check if client is marked as disconnected
		if !c.IsConnected() {
			log.Printf("Client %s marked as disconnected, ending handler", c.getClientInfo())
			return
		}

		select {
		case <-registrationTimer.C:
			if registrationActive && !c.IsRegistered() {
				log.Printf("Registration timeout for client %s after %v", c.getClientInfo(), time.Since(connectionStartTime))
				c.SendMessage("ERROR :Registration timeout")
				return
			}
		case <-done:
			return
		default:
			// Handle message reading with timeout and enhanced error recovery
			if !c.handleMessageRead(scanner, &registrationActive, registrationTimer, connectionStartTime) {
				return // Connection closed or error
			}
		}
	}
}

// Enhanced connection handling methods

// cleanup performs thorough cleanup of client resources
func (c *Client) cleanup() {
	log.Printf("Starting cleanup for client %s", c.getClientInfo())

	// Mark as disconnected to prevent further operations
	c.mu.Lock()
	c.disconnected = true
	c.mu.Unlock()

	// Close connection safely with timeout to prevent hanging
	if c.conn != nil {
		// Set a short deadline to force close if needed
		c.conn.SetDeadline(time.Now().Add(5 * time.Second))
		
		if err := c.conn.Close(); err != nil {
			log.Printf("Error closing connection for %s: %v", c.getClientInfo(), err)
		}
		
		// Clear the connection reference
		c.mu.Lock()
		c.conn = nil
		c.mu.Unlock()
	}

	// Part all channels with error handling in a separate goroutine to prevent blocking
	go func() {
		defer func() {
			if r := recover(); r != nil {
				log.Printf("Panic during channel cleanup for %s: %v", c.getClientInfo(), r)
			}
		}()
		
		channels := c.GetChannels()
		for channelName, channel := range channels {
			if channel != nil {
				channel.RemoveClient(c)
				// Clean up empty channels
				if len(channel.GetClients()) == 0 && c.server != nil {
					c.server.RemoveChannel(channelName)
				}
			}
		}
	}()

	// Remove from server in a separate goroutine to prevent deadlock
	go func() {
		defer func() {
			if r := recover(); r != nil {
				log.Printf("Panic during server cleanup for %s: %v", c.getClientInfo(), r)
			}
		}()
		
		if c.server != nil {
			c.server.RemoveClient(c)
		}
	}()

	log.Printf("Cleanup completed for client %s", c.getClientInfo())
}

// setReadDeadline safely sets read deadline on connection
func (c *Client) setReadDeadline(duration time.Duration) {
	if c.conn != nil && !c.isDisconnected() {
		if err := c.conn.SetReadDeadline(time.Now().Add(duration)); err != nil {
			log.Printf("Error setting read deadline for %s: %v", c.getClientInfo(), err)
		}
	}
}

// handleMessageRead handles reading and processing a single message
func (c *Client) handleMessageRead(scanner *bufio.Scanner, registrationActive *bool, registrationTimer *time.Timer, connectionStartTime time.Time) bool {
	// Set read deadline with timeout to prevent hanging
	readTimeout := 30 * time.Second
	if c.IsRegistered() {
		readTimeout = 5 * time.Minute
	}
	
	c.setReadDeadline(readTimeout)

	// Use a channel to make scanning non-blocking with timeout
	scanChan := make(chan bool, 1)
	var line string
	var scanErr error

	go func() {
		defer func() {
			if r := recover(); r != nil {
				log.Printf("Panic in scanner goroutine for %s: %v", c.getClientInfo(), r)
				scanChan <- false
			}
		}()
		
		if scanner.Scan() {
			line = strings.TrimSpace(scanner.Text())
			scanErr = scanner.Err()
			scanChan <- true
		} else {
			scanErr = scanner.Err()
			scanChan <- false
		}
	}()

	// Wait for scan result with timeout
	select {
	case scanResult := <-scanChan:
		if !scanResult {
			// Check for scanner error
			if scanErr != nil {
				log.Printf("Scanner error for client %s: %v", c.getClientInfo(), scanErr)
			} else {
				log.Printf("Client %s disconnected cleanly", c.getClientInfo())
			}
			return false
		}
	case <-time.After(readTimeout + 5*time.Second): // Additional buffer
		log.Printf("Read timeout for client %s after %v", c.getClientInfo(), readTimeout)
		c.SendMessage("ERROR :Read timeout")
		return false
	}

	if line == "" {
		return true // Continue processing
	}

	// Enhanced flood checking - be more lenient during initial connection
	if c.IsRegistered() && c.CheckFlood() {
		log.Printf("Flood protection triggered for %s", c.getClientInfo())
		c.SendMessage("ERROR :Excess Flood")
		return false
	}

	// Validate message content to prevent issues
	if len(line) > 4096 {
		log.Printf("Oversized message from %s, truncating", c.getClientInfo())
		line = line[:4096]
	}

	// Check for binary data that might cause issues
	if !isPrintableASCII(line) {
		log.Printf("Non-printable data from %s, skipping", c.getClientInfo())
		return true
	}

	// Update activity time for any valid message
	c.mu.Lock()
	c.lastActivity = time.Now()
	c.mu.Unlock()

	// Stop registration timer once registered
	if *registrationActive && c.IsRegistered() {
		registrationTimer.Stop()
		*registrationActive = false
		log.Printf("Client %s registration completed successfully", c.getClientInfo())
	}

	// Handle the message with error recovery
	func() {
		defer func() {
			if r := recover(); r != nil {
				log.Printf("Panic handling message from %s: %v (message: %.100s)", c.getClientInfo(), r, line)
			}
		}()

		if c.server != nil {
			c.server.HandleMessage(c, line)
		}
	}()

	return true
}

// isPrintableASCII checks if a string contains only printable ASCII characters
func isPrintableASCII(s string) bool {
	for _, r := range s {
		if r < 32 || r > 126 {
			// Allow common IRC control characters
			if r != '\r' && r != '\n' && r != '\t' {
				return false
			}
		}
	}
	return true
}

// broadcastToChannel sends an IRCv3 message to all users in a channel
func (c *Client) broadcastToChannel(channel *Channel, msg *IRCMessage) {
	for _, client := range channel.GetClients() {
		if client != c { // Don't send to sender
			// Create a copy of the message for each recipient
			clientMsg := &IRCMessage{
				Tags:    make(map[string]string),
				Prefix:  msg.Prefix,
				Command: msg.Command,
				Params:  msg.Params,
			}

			// Copy base tags
			for k, v := range msg.Tags {
				clientMsg.Tags[k] = v
			}

			// Add recipient-specific server-time if they support it
			if client.HasCapability("server-time") && clientMsg.Tags["time"] == "" {
				clientMsg.Tags["time"] = time.Now().UTC().Format(time.RFC3339Nano)
			}

			client.SendMessage(clientMsg.FormatMessage())
		}
	}
}
