package ts3client

import (
	"fmt"
	"log"
	"sync"
	"time"

	"go-ts/internal/client"
)

// Client is the main TeamSpeak 3 client
type Client struct {
	address string
	config  Config

	// Internal client
	internal *client.Client

	// Event handlers
	handlers map[EventType][]any
	mu       sync.RWMutex

	// State
	connected  bool
	channels   map[uint64]*Channel
	clients    map[uint16]*ClientInfo
	serverInfo *ServerInfo
	selfInfo   *SelfInfo
	channelsMu sync.RWMutex
	clientsMu  sync.RWMutex

	// Voice activity tracking
	talkingClients map[uint16]time.Time // ClientID -> last voice packet time
	talkingMu      sync.RWMutex
}

// New creates a new TeamSpeak client
func New(address string, config Config) *Client {
	// Apply defaults
	if config.Nickname == "" {
		config.Nickname = "GoTS3Bot"
	}
	if config.SecurityLevel == 0 {
		config.SecurityLevel = 8
	}
	if config.Version == "" {
		config.Version = "3.6.2 [Build: 1690976575]"
	}
	if config.Platform == "" {
		config.Platform = "Windows"
	}
	if config.HWID == "" {
		config.HWID = "1234567890"
	}

	return &Client{
		address:        address,
		config:         config,
		handlers:       make(map[EventType][]any),
		channels:       make(map[uint64]*Channel),
		clients:        make(map[uint16]*ClientInfo),
		talkingClients: make(map[uint16]time.Time),
	}
}

// On registers an event handler
// The handler function signature must match the event type:
//   - EventConnected: func(*ConnectedEvent)
//   - EventMessage: func(*MessageEvent)
//   - EventAudio: func(*AudioEvent)
//   - etc.
func (c *Client) On(event EventType, handler any) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.handlers[event] = append(c.handlers[event], handler)
}

// emit calls all handlers registered for the given event
func (c *Client) emit(event EventType, data any) {
	c.mu.RLock()
	handlers := c.handlers[event]
	c.mu.RUnlock()

	for _, h := range handlers {
		switch event {
		case EventConnected:
			if fn, ok := h.(func(*ConnectedEvent)); ok {
				fn(data.(*ConnectedEvent))
			}
		case EventDisconnected:
			if fn, ok := h.(func(*DisconnectedEvent)); ok {
				fn(data.(*DisconnectedEvent))
			}
		case EventMessage:
			if fn, ok := h.(func(*MessageEvent)); ok {
				fn(data.(*MessageEvent))
			}
		case EventClientEnter:
			if fn, ok := h.(func(*ClientEnterEvent)); ok {
				fn(data.(*ClientEnterEvent))
			}
		case EventClientLeft:
			if fn, ok := h.(func(*ClientLeftEvent)); ok {
				fn(data.(*ClientLeftEvent))
			}
		case EventClientMoved:
			if fn, ok := h.(func(*ClientMovedEvent)); ok {
				fn(data.(*ClientMovedEvent))
			}
		case EventChannelList:
			if fn, ok := h.(func(*ChannelListEvent)); ok {
				fn(data.(*ChannelListEvent))
			}
		case EventAudio:
			if fn, ok := h.(func(*AudioEvent)); ok {
				fn(data.(*AudioEvent))
			}
		case EventError:
			if fn, ok := h.(func(*ErrorEvent)); ok {
				fn(data.(*ErrorEvent))
			}
		case EventTalkingStatus:
			if fn, ok := h.(func(*TalkingStatusEvent)); ok {
				fn(data.(*TalkingStatusEvent))
			}
		}
	}
}

// Connect establishes a connection to the TeamSpeak server
// This method blocks until disconnected or an error occurs
func (c *Client) Connect() error {
	c.internal = client.NewClient(c.config.Nickname)

	// Set event callback on internal client
	c.internal.SetEventHandler(c.handleInternalEvent)

	// Start talking timeout checker goroutine
	go c.talkingTimeoutChecker()

	log.Printf("Connecting to %s as %s...", c.address, c.config.Nickname)

	err := c.internal.Connect(c.address)
	if err != nil {
		c.emit(EventDisconnected, &DisconnectedEvent{Reason: err.Error()})
		log.Printf("[TS3Client] Connect returning with error: %v", err)
		return err
	}

	log.Printf("[TS3Client] Connect returning cleanly")
	return nil
}

// talkingTimeoutChecker runs in background and emits TalkingStatus=false when clients stop talking
func (c *Client) talkingTimeoutChecker() {
	ticker := time.NewTicker(200 * time.Millisecond)
	defer ticker.Stop()

	const talkingTimeout = 300 * time.Millisecond

	for range ticker.C {
		if !c.connected {
			continue
		}

		now := time.Now()
		var stoppedTalking []uint16

		c.talkingMu.Lock()
		for clientID, lastTime := range c.talkingClients {
			if now.Sub(lastTime) > talkingTimeout {
				stoppedTalking = append(stoppedTalking, clientID)
				delete(c.talkingClients, clientID)
			}
		}
		c.talkingMu.Unlock()

		// Emit events for clients who stopped talking
		for _, clientID := range stoppedTalking {
			c.emit(EventTalkingStatus, &TalkingStatusEvent{
				ClientID: clientID,
				Talking:  false,
			})
		}
	}
}

// ConnectAsync connects in the background and returns immediately
func (c *Client) ConnectAsync() <-chan error {
	errChan := make(chan error, 1)
	go func() {
		if err := c.Connect(); err != nil {
			errChan <- err
		}
		close(errChan)
	}()
	// Give it a moment to start
	time.Sleep(100 * time.Millisecond)
	return errChan
}

// Disconnect closes the connection gracefully
func (c *Client) Disconnect() {
	log.Println("[Disconnect] Starting disconnect sequence...")
	if c.internal != nil {
		// Send disconnect command to server
		log.Println("[Disconnect] Sending disconnect command...")
		c.sendDisconnect("leaving")
		// Wait for packet to be sent and ACKed - the internal loop must still be running
		log.Println("[Disconnect] Waiting for disconnect to be processed...")
		time.Sleep(1000 * time.Millisecond)
		// Stop the internal loop
		log.Println("[Disconnect] Stopping internal loop...")
		c.internal.Stop()
		if c.internal.Conn != nil {
			log.Println("[Disconnect] Closing connection...")
			c.internal.Conn.Close()
		}
	}
	c.connected = false
	log.Println("[Disconnect] Done")
	c.emit(EventDisconnected, &DisconnectedEvent{Reason: "client disconnect"})
}

// sendDisconnect sends the disconnect command to the server
func (c *Client) sendDisconnect(reason string) {
	if c.internal == nil {
		return
	}
	// Use internal client's SendCommand
	cmd := "clientdisconnect reasonid=8 reasonmsg=" + escapeTS3(reason)
	log.Printf("Sending disconnect: %s", cmd)
	if err := c.internal.SendCommandString(cmd); err != nil {
		log.Printf("Error sending disconnect: %v", err)
	}
}

type disconnectCommand struct {
	reason string
}

func (d *disconnectCommand) encode() string {
	return "clientdisconnect reasonid=8 reasonmsg=" + escapeTS3(d.reason)
}

func escapeTS3(s string) string {
	// Basic escape for TS3 protocol
	result := ""
	for _, r := range s {
		switch r {
		case '\\':
			result += "\\\\"
		case '/':
			result += "\\/"
		case ' ':
			result += "\\s"
		case '|':
			result += "\\p"
		default:
			result += string(r)
		}
	}
	return result
}

// IsConnected returns true if the client is connected
func (c *Client) IsConnected() bool {
	return c.connected
}

// GetPing returns the current RTT in milliseconds
func (c *Client) GetPing() float64 {
	if c.internal == nil {
		return 0
	}
	return c.internal.PingRTT
}

// handleInternalEvent processes events from the internal client
func (c *Client) handleInternalEvent(eventType string, data map[string]any) {
	switch eventType {
	case "connected":
		c.connected = true
		clientID := uint16(0)
		serverName := ""
		if v, ok := data["clientID"].(uint16); ok {
			clientID = v
		}
		if v, ok := data["serverName"].(string); ok {
			serverName = v
		}
		c.selfInfo = &SelfInfo{ClientID: clientID, Nickname: c.config.Nickname}
		c.serverInfo = &ServerInfo{Name: serverName}
		c.emit(EventConnected, &ConnectedEvent{
			ClientID:   clientID,
			ServerName: serverName,
		})

	case "message":
		targetMode := MessageTarget(1)
		if v, ok := data["targetMode"].(int); ok {
			targetMode = MessageTarget(v)
		}
		c.emit(EventMessage, &MessageEvent{
			SenderID:   getUint16(data, "senderID"),
			SenderName: getString(data, "senderName"),
			Message:    getString(data, "message"),
			TargetMode: targetMode,
		})

	case "client_enter":
		info := &ClientInfo{
			ID:        getUint16(data, "clientID"),
			Nickname:  getString(data, "nickname"),
			ChannelID: getUint64(data, "channelID"),
		}
		c.clientsMu.Lock()
		c.clients[info.ID] = info
		c.clientsMu.Unlock()

		c.emit(EventClientEnter, &ClientEnterEvent{
			ClientID:  info.ID,
			Nickname:  info.Nickname,
			ChannelID: info.ChannelID,
		})

	case "client_left":
		clientID := getUint16(data, "clientID")
		c.clientsMu.Lock()
		delete(c.clients, clientID)
		c.clientsMu.Unlock()

		c.emit(EventClientLeft, &ClientLeftEvent{
			ClientID: clientID,
			Reason:   getString(data, "reason"),
		})

	case "client_moved":
		clientID := getUint16(data, "clientID")
		channelID := getUint64(data, "channelID")

		c.clientsMu.Lock()
		if client, ok := c.clients[clientID]; ok {
			client.ChannelID = channelID
		}
		c.clientsMu.Unlock()

		// Update selfInfo if it's us
		if c.selfInfo != nil && c.selfInfo.ClientID == clientID {
			c.selfInfo.ChannelID = channelID
		}

		c.emit(EventClientMoved, &ClientMovedEvent{
			ClientID:  clientID,
			ChannelID: channelID,
		})

	case "channel_list":
		if channels, ok := data["channels"].([]*client.Channel); ok {
			c.channelsMu.Lock()
			var chList []*Channel
			for _, ch := range channels {
				converted := &Channel{
					ID:       ch.ID,
					ParentID: ch.ParentID,
					Name:     ch.Name,
					Order:    ch.Order,
				}
				c.channels[ch.ID] = converted
				chList = append(chList, converted)
			}
			c.channelsMu.Unlock()
			c.emit(EventChannelList, &ChannelListEvent{Channels: chList})

			// Subscribe to all channels to see users
			// This fixes the issue where users are invisible until joining the channel
			if c.internal != nil {
				log.Println("Received channel list. Subscribing to all channels...")
				c.internal.SendCommandString("channelsubscribeall")
			}
		}

	case "audio":
		senderID := getUint16(data, "senderID")

		// Track talking status
		c.talkingMu.Lock()
		_, wasTalking := c.talkingClients[senderID]
		c.talkingClients[senderID] = time.Now()
		c.talkingMu.Unlock()

		// Emit talking start event if this is a new speaker
		if !wasTalking {
			c.emit(EventTalkingStatus, &TalkingStatusEvent{
				ClientID: senderID,
				Talking:  true,
			})
		}

		c.emit(EventAudio, &AudioEvent{
			SenderID: senderID,
			Codec:    AudioCodec(getInt(data, "codec")),
			PCM:      getPCM(data, "pcm"),
			Channels: getInt(data, "channels"),
		})

	case "error":
		c.emit(EventError, &ErrorEvent{
			ID:      getString(data, "id"),
			Message: getString(data, "message"),
		})
	}
}

// Helper functions for type conversion
func getString(m map[string]any, key string) string {
	if v, ok := m[key].(string); ok {
		return v
	}
	return ""
}

func getUint16(m map[string]any, key string) uint16 {
	if v, ok := m[key].(uint16); ok {
		return v
	}
	if v, ok := m[key].(int); ok {
		return uint16(v)
	}
	return 0
}

func getUint64(m map[string]any, key string) uint64 {
	if v, ok := m[key].(uint64); ok {
		return v
	}
	if v, ok := m[key].(int); ok {
		return uint64(v)
	}
	return 0
}

func getInt(m map[string]any, key string) int {
	if v, ok := m[key].(int); ok {
		return v
	}
	return 0
}

func getPCM(m map[string]any, key string) []int16 {
	if v, ok := m[key].([]int16); ok {
		return v
	}
	return nil
}

// WaitForConnection waits until the client is connected or timeout
func (c *Client) WaitForConnection(timeout time.Duration) error {
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		if c.connected {
			return nil
		}
		time.Sleep(100 * time.Millisecond)
	}
	return fmt.Errorf("connection timeout")
}
