package main

import (
	"fmt"
	"sort"
	"strings"
	"time"

	"go-ts/pkg/ts3client"

	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
)

// Focus indicates which panel has focus
type Focus int

const (
	FocusChannels Focus = iota
	FocusChat
	FocusInput
)

// ChatMessage represents a message in the chat
type ChatMessage struct {
	Time    time.Time
	Sender  string
	Content string
}

// ChannelNode represents a channel in the tree
type ChannelNode struct {
	ID       uint64
	Name     string
	Users    []UserNode
	Expanded bool
	Selected bool
}

// UserNode represents a user in a channel
type UserNode struct {
	ID       uint16
	Nickname string
	Talking  bool
	Muted    bool
	IsMe     bool
}

// Model is the Bubble Tea model for our TUI
type Model struct {
	// Connection
	serverAddr string
	nickname   string
	client     *ts3client.Client
	connected  bool
	connecting bool
	serverName string
	selfID     uint16
	ping       int

	// UI State
	focus         Focus
	width, height int
	channels      []ChannelNode
	selectedIdx   int
	chatMessages  []ChatMessage
	logMessages   []string // Debug logs shown in chat panel
	logFullscreen bool     // Toggle fullscreen log view
	inputText     string
	inputActive   bool

	// Error handling
	lastError string
}

// addLog adds a message to the log panel
func (m *Model) addLog(format string, args ...any) {
	msg := fmt.Sprintf(format, args...)
	m.logMessages = append(m.logMessages, msg)
	// Keep last 50 messages
	if len(m.logMessages) > 50 {
		m.logMessages = m.logMessages[1:]
	}
}

// NewModel creates a new TUI model
func NewModel(serverAddr, nickname string) *Model {
	return &Model{
		serverAddr:   serverAddr,
		nickname:     nickname,
		focus:        FocusChannels,
		channels:     []ChannelNode{},
		chatMessages: []ChatMessage{},
		logMessages:  []string{"Starting..."},
	}
}

// Init is called when the program starts
func (m *Model) Init() tea.Cmd {
	return tea.Batch(
		m.connectToServer(),
		tea.SetWindowTitle("TeamSpeak TUI"),
	)
}

// TS3Event wraps events from the TS3 client
type TS3Event struct {
	Type string
	Data map[string]any
}

// connectToServer initiates connection to TeamSpeak
func (m *Model) connectToServer() tea.Cmd {
	return func() tea.Msg {
		m.connecting = true

		client := ts3client.New(m.serverAddr, ts3client.Config{
			Nickname: m.nickname,
		})

		return ts3ClientMsg{client: client}
	}
}

type ts3ClientMsg struct {
	client *ts3client.Client
}

type connectedMsg struct {
	clientID   uint16
	serverName string
}

type channelListMsg struct {
	channels []*ts3client.Channel
}

type clientEnterMsg struct {
	clientID  uint16
	nickname  string
	channelID uint64
}

type clientLeftMsg struct {
	clientID uint16
}

type chatMsg struct {
	senderID   uint16
	senderName string
	message    string
}

type errorMsg struct {
	err string
}

type tickMsg time.Time

// Update handles messages and user input
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.WindowSizeMsg:
		m.width = msg.Width
		m.height = msg.Height
		return m, nil

	case tea.KeyMsg:
		return m.handleKeyPress(msg)

	case ts3ClientMsg:
		m.client = msg.client
		m.connecting = true

		// Connect asynchronously
		m.client.ConnectAsync()

		// Set up a ticker to poll for state changes
		return m, tea.Tick(500*time.Millisecond, func(t time.Time) tea.Msg {
			return tickMsg(t)
		})

	case tickMsg:
		// Poll client state
		if m.client != nil {
			// Check if connected
			if !m.connected {
				info := m.client.GetSelfInfo()
				serverInfo := m.client.GetServerInfo()
				m.addLog("Tick: selfInfo=%v, serverInfo=%v", info != nil, serverInfo != nil)
				if info != nil && serverInfo != nil {
					m.connected = true
					m.selfID = info.ClientID
					m.serverName = serverInfo.Name
					m.addLog("Connected! ClientID=%d, Server=%s", m.selfID, m.serverName)

					// Get channels
					channels := m.client.GetChannels()
					m.addLog("Got %d channels", len(channels))
					m.updateChannelList(channels)
				}
			} else {
				// Update channel list periodically
				channels := m.client.GetChannels()
				if len(channels) != len(m.channels) {
					m.addLog("Channel update: got %d channels (had %d)", len(channels), len(m.channels))
				}
				m.updateChannelList(channels)
			}
		}

		// Continue ticking
		return m, tea.Tick(500*time.Millisecond, func(t time.Time) tea.Msg {
			return tickMsg(t)
		})

	case connectedMsg:
		m.connected = true
		m.connecting = false
		m.selfID = msg.clientID
		m.serverName = msg.serverName
		return m, nil

	case channelListMsg:
		m.updateChannelList(msg.channels)
		return m, nil

	case errorMsg:
		m.lastError = msg.err
		return m, nil

	case logMsg:
		// External log message
		m.addLog("%s", string(msg))
		return m, nil
	}

	return m, nil
}

type logMsg string

func (m *Model) updateChannelList(channels []*ts3client.Channel) {
	// Sort channels by ID for stable ordering
	sortedChannels := make([]*ts3client.Channel, len(channels))
	copy(sortedChannels, channels)
	sort.Slice(sortedChannels, func(i, j int) bool {
		return sortedChannels[i].ID < sortedChannels[j].ID
	})

	m.channels = make([]ChannelNode, 0, len(sortedChannels))
	for _, ch := range sortedChannels {
		node := ChannelNode{
			ID:       ch.ID,
			Name:     ch.Name,
			Users:    []UserNode{},
			Expanded: true,
		}

		// Get users in this channel
		for _, cl := range m.client.GetClients() {
			if cl.ChannelID == ch.ID {
				node.Users = append(node.Users, UserNode{
					ID:       cl.ID,
					Nickname: cl.Nickname,
					IsMe:     cl.ID == m.selfID,
				})
			}
		}

		// Sort users by ID for stable ordering
		sort.Slice(node.Users, func(i, j int) bool {
			return node.Users[i].ID < node.Users[j].ID
		})

		m.channels = append(m.channels, node)
	}
}

func (m *Model) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	// Global keys
	switch msg.String() {
	case "ctrl+c", "q":
		if m.client != nil {
			m.client.Disconnect()
		}
		return m, tea.Quit

	case "tab":
		// Cycle focus
		m.focus = (m.focus + 1) % 3
		return m, nil

	case "m":
		// Toggle mute (would need to implement)
		return m, nil
	}

	// Focus-specific keys
	switch m.focus {
	case FocusChannels:
		return m.handleChannelKeys(msg)
	case FocusInput:
		return m.handleInputKeys(msg)
	case FocusChat:
		return m.handleChatKeys(msg)
	}
	return m, nil
}

func (m *Model) handleChatKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	switch msg.String() {
	case "f":
		m.logFullscreen = !m.logFullscreen
	}
	return m, nil
}

func (m *Model) handleChannelKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	switch msg.String() {
	case "up", "k":
		if m.selectedIdx > 0 {
			m.selectedIdx--
		}
	case "down", "j":
		if m.selectedIdx < len(m.channels)-1 {
			m.selectedIdx++
		}
	case "enter":
		// Join selected channel
		if m.selectedIdx < len(m.channels) && m.client != nil {
			ch := m.channels[m.selectedIdx]
			m.addLog("Attempting to join channel: %s (ID=%d)", ch.Name, ch.ID)
			err := m.client.JoinChannel(ch.ID)
			if err != nil {
				m.addLog("Error joining channel: %v", err)
			}
		}
	}
	return m, nil
}

func (m *Model) handleInputKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	switch msg.String() {
	case "enter":
		if m.inputText != "" && m.client != nil {
			m.client.SendChannelMessage(m.inputText)
			m.inputText = ""
		}
	case "esc":
		m.focus = FocusChannels
		m.inputText = ""
	case "backspace":
		if len(m.inputText) > 0 {
			m.inputText = m.inputText[:len(m.inputText)-1]
		}
	default:
		// Add character to input
		if len(msg.String()) == 1 {
			m.inputText += msg.String()
		}
	}
	return m, nil
}

// View renders the UI
func (m *Model) View() string {
	if m.width == 0 {
		return "Loading..."
	}

	// Fullscreen Log Mode
	if m.logFullscreen {
		style := lipgloss.NewStyle().
			Border(lipgloss.RoundedBorder()).
			BorderForeground(lipgloss.Color("212")). // Active color
			Padding(0, 1).
			Width(m.width - 2).
			Height(m.height - 2)

		return style.Render(m.renderChat())
	}

	// Styles
	headerStyle := lipgloss.NewStyle().
		Bold(true).
		Foreground(lipgloss.Color("229")).
		Background(lipgloss.Color("57")).
		Padding(0, 1).
		Width(m.width)

	channelPanelStyle := lipgloss.NewStyle().
		Border(lipgloss.RoundedBorder()).
		BorderForeground(lipgloss.Color("63")).
		Padding(0, 1).
		Width(m.width/3 - 2).
		Height(m.height - 6)

	chatPanelStyle := lipgloss.NewStyle().
		Border(lipgloss.RoundedBorder()).
		BorderForeground(lipgloss.Color("63")).
		Padding(0, 1).
		Width(m.width*2/3 - 2).
		Height(m.height - 6)

	inputStyle := lipgloss.NewStyle().
		Border(lipgloss.NormalBorder()).
		BorderForeground(lipgloss.Color("63")).
		Padding(0, 1).
		Width(m.width - 4)

	// Header
	status := "Connecting..."
	if m.connected {
		status = fmt.Sprintf("Server: %s │ You: %s (ID: %d)", m.serverName, m.nickname, m.selfID)
	}
	header := headerStyle.Render(status)

	// Channel panel
	channelContent := m.renderChannels()
	if m.focus == FocusChannels {
		channelPanelStyle = channelPanelStyle.BorderForeground(lipgloss.Color("212"))
	}
	channelPanel := channelPanelStyle.Render(channelContent)

	// Chat panel
	chatContent := m.renderChat()
	if m.focus == FocusChat {
		chatPanelStyle = chatPanelStyle.BorderForeground(lipgloss.Color("212"))
	}
	chatPanel := chatPanelStyle.Render(chatContent)

	// Input
	inputContent := "> " + m.inputText
	if m.focus == FocusInput {
		inputStyle = inputStyle.BorderForeground(lipgloss.Color("212"))
		inputContent += "█"
	}
	input := inputStyle.Render(inputContent)

	// Footer help
	help := lipgloss.NewStyle().Faint(true).Render("↑↓ navigate │ Enter join │ Tab switch │ q quit")

	// Combine panels
	panels := lipgloss.JoinHorizontal(lipgloss.Top, channelPanel, chatPanel)

	return lipgloss.JoinVertical(lipgloss.Left,
		header,
		panels,
		input,
		help,
	)
}

func (m *Model) renderChannels() string {
	if len(m.channels) == 0 {
		return "No channels..."
	}

	var lines []string
	lines = append(lines, lipgloss.NewStyle().Bold(true).Render("CHANNELS"))
	lines = append(lines, "")

	for i, ch := range m.channels {
		prefix := "  "
		if i == m.selectedIdx {
			prefix = "► "
		}

		style := lipgloss.NewStyle()
		if i == m.selectedIdx {
			style = style.Bold(true).Foreground(lipgloss.Color("212"))
		}

		lines = append(lines, style.Render(prefix+ch.Name))

		// Show users in channel
		for _, user := range ch.Users {
			userPrefix := "    └─ "
			userStyle := lipgloss.NewStyle().Faint(true)
			if user.IsMe {
				userStyle = userStyle.Foreground(lipgloss.Color("82"))
				userPrefix = "    └─ ► "
			}
			lines = append(lines, userStyle.Render(userPrefix+user.Nickname))
		}
	}

	return lipgloss.JoinVertical(lipgloss.Left, lines...)
}

func (m *Model) renderChat() string {
	var lines []string
	lines = append(lines, lipgloss.NewStyle().Bold(true).Render("LOG"))
	lines = append(lines, "")

	if len(m.logMessages) == 0 {
		lines = append(lines, lipgloss.NewStyle().Faint(true).Render("No logs yet..."))
	} else {
		// Limit to last N messages that fit in the panel
		maxLines := m.height - 10
		if maxLines < 5 {
			maxLines = 5
		}

		start := 0
		if len(m.logMessages) > maxLines {
			start = len(m.logMessages) - maxLines
		}

		panelWidth := (m.width / 2) - 4
		if m.logFullscreen {
			panelWidth = m.width - 6
		}
		if panelWidth < 10 {
			panelWidth = 10
		}

		for _, msg := range m.logMessages[start:] {
			// Truncate if too long to prevent wrapping breaking the layout
			displayMsg := msg
			if len(displayMsg) > panelWidth {
				displayMsg = displayMsg[:panelWidth-3] + "..."
			}
			// Replace newlines just in case
			displayMsg = strings.ReplaceAll(displayMsg, "\n", " ")

			lines = append(lines, lipgloss.NewStyle().Faint(true).Render(displayMsg))
		}
	}

	return lipgloss.JoinVertical(lipgloss.Left, lines...)
}
