package feed

import (
	"sync"
	"time"

	"github.com/charmbracelet/bubbles/help"
	"github.com/charmbracelet/bubbles/key"
	"github.com/charmbracelet/bubbles/viewport"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/steveyegge/gastown/internal/beads"
)

// Panel represents which panel has focus
type Panel int

const (
	PanelTree Panel = iota
	PanelConvoy
	PanelFeed
)

// Event represents an activity event
type Event struct {
	Time     time.Time
	Type     string // create, update, complete, fail, delete
	Actor    string // who did it (e.g., "gastown/crew/joe")
	Target   string // what was affected (e.g., "gt-xyz")
	Message  string // human-readable description
	Rig      string // which rig
	Role     string // actor's role
	Raw      string // raw line for fallback display
}

// Agent represents an agent in the tree
type Agent struct {
	ID         string
	Name       string
	Role       string // mayor, witness, refinery, crew, polecat
	Rig        string
	Status     string // running, idle, working, dead
	LastEvent  *Event
	LastUpdate time.Time
	Expanded   bool
}

// Rig represents a rig with its agents
type Rig struct {
	Name     string
	Agents   map[string]*Agent // keyed by role/name
	Expanded bool
}

// Model is the main bubbletea model for the feed TUI
type Model struct {
	// Dimensions
	width  int
	height int

	// Panels
	focusedPanel   Panel
	treeViewport   viewport.Model
	convoyViewport viewport.Model
	feedViewport   viewport.Model

	// Data
	rigs        map[string]*Rig
	events      []Event
	convoyState *ConvoyState
	townRoot    string

	// UI state
	keys     KeyMap
	help     help.Model
	showHelp bool
	filter   string

	// Event source
	eventChan <-chan Event
	done      chan struct{}
	closeOnce sync.Once
}

// NewModel creates a new feed TUI model
func NewModel() *Model {
	h := help.New()
	h.ShowAll = false

	return &Model{
		focusedPanel:   PanelTree,
		treeViewport:   viewport.New(0, 0),
		convoyViewport: viewport.New(0, 0),
		feedViewport:   viewport.New(0, 0),
		rigs:           make(map[string]*Rig),
		events:         make([]Event, 0, 1000),
		keys:           DefaultKeyMap(),
		help:           h,
		done:           make(chan struct{}),
	}
}

// SetTownRoot sets the town root for convoy fetching
func (m *Model) SetTownRoot(townRoot string) {
	m.townRoot = townRoot
}

// Init initializes the model
func (m *Model) Init() tea.Cmd {
	return tea.Batch(
		m.listenForEvents(),
		m.fetchConvoys(),
		tea.SetWindowTitle("GT Feed"),
	)
}

// eventMsg is sent when a new event arrives
type eventMsg Event

// convoyUpdateMsg is sent when convoy data is refreshed
type convoyUpdateMsg struct {
	state *ConvoyState
}

// tickMsg is sent periodically to refresh the view
type tickMsg time.Time

// listenForEvents returns a command that listens for events
func (m *Model) listenForEvents() tea.Cmd {
	if m.eventChan == nil {
		return nil
	}
	// Capture channels to avoid race with Model mutations
	eventChan := m.eventChan
	done := m.done
	return func() tea.Msg {
		select {
		case event, ok := <-eventChan:
			if !ok {
				return nil
			}
			return eventMsg(event)
		case <-done:
			return nil
		}
	}
}

// tick returns a command for periodic refresh
func tick() tea.Cmd {
	return tea.Tick(time.Second, func(t time.Time) tea.Msg {
		return tickMsg(t)
	})
}

// fetchConvoys returns a command that fetches convoy data
func (m *Model) fetchConvoys() tea.Cmd {
	if m.townRoot == "" {
		return nil
	}
	townRoot := m.townRoot
	return func() tea.Msg {
		state, _ := FetchConvoys(townRoot)
		return convoyUpdateMsg{state: state}
	}
}

// convoyRefreshTick returns a command that schedules the next convoy refresh
func (m *Model) convoyRefreshTick() tea.Cmd {
	return tea.Tick(10*time.Second, func(t time.Time) tea.Msg {
		return convoyUpdateMsg{} // Empty state triggers a refresh
	})
}

// Update handles messages
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var cmds []tea.Cmd

	switch msg := msg.(type) {
	case tea.KeyMsg:
		return m.handleKey(msg)

	case tea.WindowSizeMsg:
		m.width = msg.Width
		m.height = msg.Height
		m.updateViewportSizes()

	case eventMsg:
		m.addEvent(Event(msg))
		cmds = append(cmds, m.listenForEvents())

	case convoyUpdateMsg:
		if msg.state != nil {
			// Fresh data arrived - update state and schedule next tick
			m.convoyState = msg.state
			m.updateViewContent()
			cmds = append(cmds, m.convoyRefreshTick())
		} else {
			// Tick fired - fetch new data
			cmds = append(cmds, m.fetchConvoys())
		}

	case tickMsg:
		cmds = append(cmds, tick())
	}

	// Update viewports
	var cmd tea.Cmd
	switch m.focusedPanel {
	case PanelTree:
		m.treeViewport, cmd = m.treeViewport.Update(msg)
	case PanelConvoy:
		m.convoyViewport, cmd = m.convoyViewport.Update(msg)
	case PanelFeed:
		m.feedViewport, cmd = m.feedViewport.Update(msg)
	}
	cmds = append(cmds, cmd)

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

// handleKey processes key presses
func (m *Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	switch {
	case key.Matches(msg, m.keys.Quit):
		m.closeOnce.Do(func() { close(m.done) })
		return m, tea.Quit

	case key.Matches(msg, m.keys.Help):
		m.showHelp = !m.showHelp
		m.help.ShowAll = m.showHelp
		return m, nil

	case key.Matches(msg, m.keys.Tab):
		// Cycle: Tree -> Convoy -> Feed -> Tree
		switch m.focusedPanel {
		case PanelTree:
			m.focusedPanel = PanelConvoy
		case PanelConvoy:
			m.focusedPanel = PanelFeed
		case PanelFeed:
			m.focusedPanel = PanelTree
		}
		return m, nil

	case key.Matches(msg, m.keys.FocusTree):
		m.focusedPanel = PanelTree
		return m, nil

	case key.Matches(msg, m.keys.FocusFeed):
		m.focusedPanel = PanelFeed
		return m, nil

	case key.Matches(msg, m.keys.FocusConvoy):
		m.focusedPanel = PanelConvoy
		return m, nil

	case key.Matches(msg, m.keys.Refresh):
		m.updateViewContent()
		return m, nil
	}

	// Pass to focused viewport
	var cmd tea.Cmd
	switch m.focusedPanel {
	case PanelTree:
		m.treeViewport, cmd = m.treeViewport.Update(msg)
	case PanelConvoy:
		m.convoyViewport, cmd = m.convoyViewport.Update(msg)
	case PanelFeed:
		m.feedViewport, cmd = m.feedViewport.Update(msg)
	}
	return m, cmd
}

// updateViewportSizes recalculates viewport dimensions
func (m *Model) updateViewportSizes() {
	// Reserve space: header (1) + borders (6 for 3 panels) + status bar (1) + help (1-2)
	headerHeight := 1
	statusHeight := 1
	helpHeight := 1
	if m.showHelp {
		helpHeight = 3
	}
	borderHeight := 6 // top and bottom borders for 3 panels

	availableHeight := m.height - headerHeight - statusHeight - helpHeight - borderHeight
	if availableHeight < 6 {
		availableHeight = 6
	}

	// Split: 30% tree, 25% convoy, 45% feed
	treeHeight := availableHeight * 30 / 100
	convoyHeight := availableHeight * 25 / 100
	feedHeight := availableHeight - treeHeight - convoyHeight

	// Ensure minimum heights
	if treeHeight < 3 {
		treeHeight = 3
	}
	if convoyHeight < 3 {
		convoyHeight = 3
	}
	if feedHeight < 3 {
		feedHeight = 3
	}

	contentWidth := m.width - 4 // borders and padding
	if contentWidth < 20 {
		contentWidth = 20
	}

	m.treeViewport.Width = contentWidth
	m.treeViewport.Height = treeHeight
	m.convoyViewport.Width = contentWidth
	m.convoyViewport.Height = convoyHeight
	m.feedViewport.Width = contentWidth
	m.feedViewport.Height = feedHeight

	m.updateViewContent()
}

// updateViewContent refreshes the content of all viewports
func (m *Model) updateViewContent() {
	m.treeViewport.SetContent(m.renderTree())
	m.convoyViewport.SetContent(m.renderConvoys())
	m.feedViewport.SetContent(m.renderFeed())
}

// addEvent adds an event and updates the agent tree
func (m *Model) addEvent(e Event) {
	// Update agent tree first (always do this for status tracking)
	if e.Rig != "" {
		rig, ok := m.rigs[e.Rig]
		if !ok {
			rig = &Rig{
				Name:     e.Rig,
				Agents:   make(map[string]*Agent),
				Expanded: true,
			}
			m.rigs[e.Rig] = rig
		}

		if e.Actor != "" {
			agent, ok := rig.Agents[e.Actor]
			if !ok {
				agent = &Agent{
					ID:   e.Actor,
					Name: e.Actor,
					Role: e.Role,
					Rig:  e.Rig,
				}
				rig.Agents[e.Actor] = agent
			}
			agent.LastEvent = &e
			agent.LastUpdate = e.Time
		}
	}

	// Filter out events with empty bead IDs (malformed mutations)
	if e.Type == "update" && e.Target == "" {
		return
	}

	// Filter out noisy agent session updates from the event feed.
	// Agent session molecules (like gt-gastown-crew-joe) update frequently
	// for status tracking. These updates are visible in the agent tree,
	// so we don't need to clutter the event feed with them.
	// We still show create/complete/fail/delete events for agent sessions.
	if e.Type == "update" && beads.IsAgentSessionBead(e.Target) {
		// Skip adding to event feed, but still refresh the view
		// (agent tree was updated above)
		m.updateViewContent()
		return
	}

	// Deduplicate rapid updates to the same bead within 2 seconds.
	// This prevents spam when multiple deps/labels are added to one issue.
	if e.Type == "update" && e.Target != "" && len(m.events) > 0 {
		lastEvent := m.events[len(m.events)-1]
		if lastEvent.Type == "update" && lastEvent.Target == e.Target {
			// Same bead updated within 2 seconds - skip duplicate
			if e.Time.Sub(lastEvent.Time) < 2*time.Second {
				return
			}
		}
	}

	// Add to event feed
	m.events = append(m.events, e)

	// Keep max 1000 events
	if len(m.events) > 1000 {
		m.events = m.events[len(m.events)-1000:]
	}

	m.updateViewContent()
}

// SetEventChannel sets the channel to receive events from
func (m *Model) SetEventChannel(ch <-chan Event) {
	m.eventChan = ch
}

// View renders the TUI
func (m *Model) View() string {
	return m.render()
}
