package logview

import (
	"fmt"
	"strings"

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

// LogMessage is a message containing a line from a service's log.
type LogMessage struct {
	ServiceName string
	Line        string
	Color       string
}

type model struct {
	services  []string
	logChan   <-chan LogMessage
	logs      map[string][]string // Key: service name, Value: log lines
	allLogs   []string
	viewports map[string]viewport.Model
	activeTab int
	ready     bool
	width     int
	height    int
}

var (
	tabStyle         = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), false, false, true, false).Padding(0, 1)
	activeTabStyle   = tabStyle.Copy().Border(lipgloss.ThickBorder(), false, false, true, false).Foreground(lipgloss.Color("62"))
	inactiveTabStyle = tabStyle.Copy()
	windowStyle      = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).Padding(1, 0)
)

func NewModel(logChan <-chan LogMessage, services []string) model {
	m := model{
		services:  append([]string{"All"}, services...),
		logChan:   logChan,
		logs:      make(map[string][]string),
		viewports: make(map[string]viewport.Model),
		activeTab: 0,
	}
	for _, s := range m.services {
		m.logs[s] = []string{}
	}
	return m
}

func (m model) Init() tea.Cmd {
	return m.waitForLogs()
}

// waitForLogs waits for the next log message from the channel.
func (m model) waitForLogs() tea.Cmd {
	return func() tea.Msg {
		return <-m.logChan
	}
}

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

	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "ctrl+c", "q":
			return m, tea.Quit
		case "right", "l":
			m.activeTab = (m.activeTab + 1) % len(m.services)
			m.updateViewportContent()
		case "left", "h":
			m.activeTab--
			if m.activeTab < 0 {
				m.activeTab = len(m.services) - 1
			}
			m.updateViewportContent()
		}

	case tea.WindowSizeMsg:
		m.width = msg.Width
		m.height = msg.Height
		headerHeight := lipgloss.Height(m.renderHeader())
		vpHeight := m.height - headerHeight - windowStyle.GetVerticalFrameSize()

		if !m.ready {
			for _, serviceName := range m.services {
				m.viewports[serviceName] = viewport.New(m.width-windowStyle.GetHorizontalFrameSize(), vpHeight)
			}
			m.ready = true
		} else {
			for _, serviceName := range m.services {
				vp := m.viewports[serviceName]
				vp.Width = m.width - windowStyle.GetHorizontalFrameSize()
				vp.Height = vpHeight
				m.viewports[serviceName] = vp
			}
		}
		m.updateViewportContent()

	case LogMessage:
		logLine := fmt.Sprintf("%s%s%s", msg.Color, msg.Line, "\033[0m")
		allLogLine := fmt.Sprintf("%s[%-10s]%s %s", msg.Color, msg.ServiceName, "\033[0m", msg.Line)

		m.logs[msg.ServiceName] = append(m.logs[msg.ServiceName], logLine)
		m.allLogs = append(m.allLogs, allLogLine)

		m.updateViewportContent()
		cmds = append(cmds, m.waitForLogs())
	}

	activeViewport := m.viewports[m.services[m.activeTab]]
	activeViewport, cmd = activeViewport.Update(msg)
	m.viewports[m.services[m.activeTab]] = activeViewport
	cmds = append(cmds, cmd)

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

func (m *model) updateViewportContent() {
	if !m.ready {
		return
	}
	activeServiceName := m.services[m.activeTab]
	var content string
	if activeServiceName == "All" {
		content = strings.Join(m.allLogs, "\n")
	} else {
		content = strings.Join(m.logs[activeServiceName], "\n")
	}

	vp := m.viewports[activeServiceName]
	vp.SetContent(content)
	vp.GotoBottom()
	m.viewports[activeServiceName] = vp
}

func (m model) View() string {
	if !m.ready {
		return "Initializing..."
	}
	header := m.renderHeader()
	activeService := m.services[m.activeTab]
	viewport := m.viewports[activeService]

	return fmt.Sprintf("%s\n%s", header, windowStyle.Render(viewport.View()))
}

func (m model) renderHeader() string {
	var tabs []string
	for i, service := range m.services {
		if i == m.activeTab {
			tabs = append(tabs, activeTabStyle.Render(service))
		} else {
			tabs = append(tabs, inactiveTabStyle.Render(service))
		}
	}
	return lipgloss.JoinHorizontal(lipgloss.Top, tabs...)
}

// Start runs the log viewer program.
func Start(logChan <-chan LogMessage, services []string) error {
	m := NewModel(logChan, services)
	p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion())
	return p.Start()
}
