package main

import (
	"encoding/json"
	"log"
	"strings"

	"github.com/charmbracelet/bubbles/textinput"
	"github.com/charmbracelet/bubbles/viewport"
	tea "github.com/charmbracelet/bubbletea"
)

type focusArea int

const (
	focusServers focusArea = iota
	focusChannels
	focusChat
	focusInput
	focusMembers
)

type model struct {
	// Layout
	width  int
	height int

	// Data
	user     UserProfile
	servers  []Server
	channels []Channel
	messages []Message
	members  []MemberEntryData

	// Navigation indices
	currentServer  int
	currentChannel int // flat index in the grouped channel view
	hoverServer    int
	hoverChannel   int

	// Components
	viewport viewport.Model
	input    textinput.Model

	// State
	focus       focusArea
	showMembers bool
	showHelp    bool
	loading     bool
	err         string
	statusMsg   string
	typing      string // username currently typing

	// Networking
	api *APIClient
	ws  *WSConn

	// Voice
	voice VoiceState

	// Channel ID for the currently displayed channel (to match WS events)
	currentChannelID string
}

type initMsg struct{}
type serversLoadedMsg []Server
type channelsLoadedMsg []Channel
type messagesLoadedMsg []Message
type messageSentMsg struct{ msg Message }
type sendErrMsg struct{ err error }
type connectWSMsg struct {
	ws    *WSConn
	token string
}
type channelLoadedData struct {
	channels []Channel
	idx      int
}

func initialModel(api *APIClient) model {
	vp := viewport.New(80, 20)
	vp.SetContent(HelpStyle.Render("  Loading..."))

	input := NewInput()

	return model{
		api:         api,
		input:       input,
		viewport:    vp,
		focus:       focusServers,
		loading:     true,
		showMembers: true,
	}
}

func (m model) Init() tea.Cmd {
	return tea.Batch(
		m.loadInitData(),
	)
}

func (m model) loadInitData() tea.Cmd {
	return func() tea.Msg {
		// Load user
		user, err := m.api.GetMe()
		if err != nil {
			log.Printf("loadInitData: GetMe: %v", err)
		} else {
			m.user = *user
		}

		// Load servers
		servers, err := m.api.GetServers()
		if err != nil {
			return sendErrMsg{err}
		}

		return serversLoadedMsg(servers)
	}
}

func (m model) loadChannels(serverID string) tea.Cmd {
	return func() tea.Msg {
		channels, err := m.api.GetChannels(serverID)
		if err != nil {
			return sendErrMsg{err}
		}
		return channelsLoadedMsg(channels)
	}
}

func (m model) loadMessages(channelID string) tea.Cmd {
	return func() tea.Msg {
		messages, err := m.api.GetMessages(channelID, 50, "")
		if err != nil {
			return sendErrMsg{err}
		}
		return messagesLoadedMsg(messages)
	}
}

func (m model) connectWS() tea.Cmd {
	return func() tea.Msg {
		ws, err := ConnectWS(m.api.BaseURL, m.api.SessionToken)
		if err != nil {
			return sendErrMsg{err}
		}
		ws.StartPing()
		return connectWSMsg{ws: ws}
	}
}

func (m model) sendMessage() tea.Cmd {
	content := strings.TrimSpace(m.input.Value())
	if content == "" || m.currentChannelID == "" {
		return nil
	}

	channelID := m.currentChannelID
	m.input.SetValue("")

	return func() tea.Msg {
		msg, err := m.api.SendMessage(channelID, content)
		if err != nil {
			return sendErrMsg{err}
		}
		return messageSentMsg{msg: *msg}
	}
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var cmds []tea.Cmd

	switch msg := msg.(type) {
	case tea.WindowSizeMsg:
		m.width = msg.Width
		m.height = msg.Height
		m.updateLayout()
		return m, nil

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

	case serversLoadedMsg:
		m.servers = []Server(msg)
		m.loading = false
		if len(m.servers) > 0 {
			m.currentServer = 0
			cmds = append(cmds, m.loadChannels(m.servers[0].ID))
			// Connect websocket
			cmds = append(cmds, m.connectWS())
			// Fetch user
			cmds = append(cmds, func() tea.Msg {
				user, err := m.api.GetMe()
				if err != nil {
					return nil
				}
				return *user
			})
		}
		return m, tea.Batch(cmds...)

	case channelsLoadedMsg:
		m.channels = []Channel(msg)
		if len(m.channels) > 0 {
			m.currentChannel = 0
			ch := ChannelByFlatIndex(m.channels, 0)
			if ch != nil {
				m.currentChannelID = ch.ID
				return m, m.loadMessages(ch.ID)
			}
		}
		return m, nil

	case messagesLoadedMsg:
		m.messages = []Message(msg)
		// Render and scroll to bottom
		content := RenderMessages(m.messages, m.viewport.Width, m.user.Username)
		m.viewport.SetContent(content)
		m.viewport.GotoBottom()
		return m, nil

	case messageSentMsg:
		// Don't add the message here; it will come back via WebSocket
		return m, nil

	case UserProfile:
		m.user = msg
		return m, nil

	case connectWSMsg:
		m.ws = msg.ws
		// Start listening for WS events
		return m, m.ws.Listen()

	case wsEventMsg:
		return m.handleWSEvent(msg)

	case wsErrMsg:
		// Reconnect on error
		m.statusMsg = "Disconnected. Reconnecting..."
		return m, m.connectWS()

	case sendErrMsg:
		m.err = msg.err.Error()
		return m, nil
	}

	// Pass to input if focused
	if m.focus == focusInput {
		var cmd tea.Cmd
		m.input, cmd = m.input.Update(msg)
		if cmd != nil {
			cmds = append(cmds, cmd)
		}
	}

	return m, tea.Batch(cmds...)
}

func (m model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	// Global keys
	switch msg.String() {
	case "ctrl+c":
		if m.ws != nil {
			m.ws.Close()
		}
		return m, tea.Quit
	}

	// When input is focused, handle input-specific keys
	if m.focus == focusInput {
		switch msg.String() {
		case "enter":
			return m, m.sendMessage()
		case "esc":
			m.focus = focusChat
			m.input.Blur()
			return m, nil
		default:
			var cmd tea.Cmd
			m.input, cmd = m.input.Update(msg)
			return m, cmd
		}
	}

	// Navigation keys when not in input
	switch msg.String() {
	case "q":
		if m.ws != nil {
			m.ws.Close()
		}
		return m, tea.Quit

	case "?":
		m.showHelp = !m.showHelp
		return m, nil

	case "tab":
		m.cycleFocus(1)
		return m, nil

	case "shift+tab":
		m.cycleFocus(-1)
		return m, nil

	case "i":
		if m.focus != focusInput {
			m.focus = focusInput
			m.input.Focus()
			return m, nil
		}
		return m, nil

	case "m":
		if m.voice.Connected {
			m.voice.ToggleMute()
			m.statusMsg = muteStatus(m.voice)
		} else {
			m.showMembers = !m.showMembers
		}
		return m, nil

	case "j", "down":
		return m, m.navigateDown()

	case "k", "up":
		return m, m.navigateUp()

	case "g":
		return m, m.navigateTop()

	case "G":
		return m, m.navigateBottom()

	case "enter":
		if m.focus == focusServers {
			m.currentServer = m.hoverServer
			if m.currentServer < len(m.servers) {
				return m, m.loadChannels(m.servers[m.currentServer].ID)
			}
		} else if m.focus == focusChannels {
			m.currentChannel = m.hoverChannel
			ch := ChannelByFlatIndex(m.channels, m.currentChannel)
			if ch != nil {
				m.currentChannelID = ch.ID
				return m, m.loadMessages(ch.ID)
			}
		}
		return m, nil
	}

	return m, nil
}

func (m model) handleWSEvent(event wsEventMsg) (tea.Model, tea.Cmd) {
	switch event.Type {
	case "MESSAGE_CREATE":
		msg, err := parseWSEventMessage(event.Data)
		if err == nil && msg.ChannelID == m.currentChannelID {
			m.messages = append(m.messages, *msg)
			content := RenderMessages(m.messages, m.viewport.Width, m.user.Username)
			m.viewport.SetContent(content)
			m.viewport.GotoBottom()
		}
		return m, m.ws.Listen()

	case "MESSAGE_UPDATE":
		msg, err := parseWSEventMessage(event.Data)
		if err == nil {
			for i, existing := range m.messages {
				if existing.ID == msg.ID {
					m.messages[i] = *msg
					break
				}
			}
			if msg.ChannelID == m.currentChannelID {
				content := RenderMessages(m.messages, m.viewport.Width, m.user.Username)
				m.viewport.SetContent(content)
			}
		}
		return m, m.ws.Listen()

	case "MESSAGE_DELETE":
		id, channelID := parseWSDeleteData(event.Data)
		if channelID == m.currentChannelID {
			for i, existing := range m.messages {
				if existing.ID == id {
					m.messages = append(m.messages[:i], m.messages[i+1:]...)
					break
				}
			}
			content := RenderMessages(m.messages, m.viewport.Width, m.user.Username)
			m.viewport.SetContent(content)
		}
		return m, m.ws.Listen()

	case "TYPING_START":
		username, channelID := parseWSTyping(event.Data)
		if channelID == m.currentChannelID && username != m.user.Username {
			m.typing = username
			// Clear after a brief moment would need a tick; for MVP just show it
		}
		return m, m.ws.Listen()

	case "VOICE_JOIN":
		var data wsVoiceData
		if err := json.Unmarshal(event.Data, &data); err == nil {
			m.voice.Participants = append(m.voice.Participants, VoiceParticipant{
				UserID:   data.UserID,
				Username: data.Username,
				State:    "connected",
			})
		}
		return m, m.ws.Listen()

	case "VOICE_LEAVE":
		var data wsVoiceData
		if err := json.Unmarshal(event.Data, &data); err == nil {
			for i, p := range m.voice.Participants {
				if p.UserID == data.UserID {
					m.voice.Participants = append(m.voice.Participants[:i], m.voice.Participants[i+1:]...)
					break
				}
			}
		}
		return m, m.ws.Listen()

	default:
		return m, m.ws.Listen()
	}
}

func (m *model) cycleFocus(direction int) {
	areas := []focusArea{focusServers, focusChannels, focusChat}
	if m.showMembers {
		areas = append(areas, focusMembers)
	}

	current := 0
	for i, a := range areas {
		if a == m.focus {
			current = i
			break
		}
	}

	next := (current + direction + len(areas)) % len(areas)
	m.focus = areas[next]
}

func (m *model) navigateDown() tea.Cmd {
	switch m.focus {
	case focusServers:
		if m.hoverServer < len(m.servers)-1 {
			m.hoverServer++
		}
	case focusChannels:
		if m.hoverChannel < ChannelCount(m.channels)-1 {
			m.hoverChannel++
		}
	case focusChat:
		m.viewport.ScrollDown(1)
	case focusMembers:
		// no-op for MVP
	}
	return nil
}

func (m *model) navigateUp() tea.Cmd {
	switch m.focus {
	case focusServers:
		if m.hoverServer > 0 {
			m.hoverServer--
		}
	case focusChannels:
		if m.hoverChannel > 0 {
			m.hoverChannel--
		}
	case focusChat:
		m.viewport.ScrollUp(1)
	case focusMembers:
		// no-op for MVP
	}
	return nil
}

func (m *model) navigateTop() tea.Cmd {
	switch m.focus {
	case focusServers:
		m.hoverServer = 0
	case focusChannels:
		m.hoverChannel = 0
	case focusChat:
		m.viewport.GotoTop()
	}
	return nil
}

func (m *model) navigateBottom() tea.Cmd {
	switch m.focus {
	case focusServers:
		m.hoverServer = len(m.servers) - 1
	case focusChannels:
		m.hoverChannel = ChannelCount(m.channels) - 1
	case focusChat:
		m.viewport.GotoBottom()
	}
	return nil
}

func (m *model) updateLayout() {
	// Server bar: 6 wide
	// Channel list: 22 wide
	// Member list: 20 wide (optional)
	// Chat: remainder
	// Input: 3 lines at bottom
	// Status bar: 1 line

	serverBarWidth := 8
	channelWidth := 24
	memberWidth := 20
	inputHeight := 3
	statusHeight := 1

	chatHeight := m.height - inputHeight - statusHeight - 2
	if chatHeight < 5 {
		chatHeight = 5
	}

	chatWidth := m.width - serverBarWidth - channelWidth - 2
	if m.showMembers {
		chatWidth -= memberWidth
	}
	if chatWidth < 20 {
		chatWidth = 20
	}

	m.viewport.Width = chatWidth - 2
	m.viewport.Height = chatHeight - 1
	m.input.Width = m.width - serverBarWidth - channelWidth - 6
}

func muteStatus(v VoiceState) string {
	if v.Muted {
		return "🔇 Muted"
	}
	return "🔊 Unmuted"
}

func (m model) View() string {
	if m.width == 0 || m.height == 0 {
		return "Loading..."
	}

	if m.loading {
		return TitleStyle.Width(m.width).Height(m.height).Render(
			"🗑  Dumpster Chat\n\n" + HelpStyle.Render("Loading..."))
	}

	return renderLayout(m)
}
