package ui

import (
	"fmt"
	"os/exec"
	"strings"
	"time"
	"woke/db"
	"woke/player"
	"woke/scheduler"

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

// View modes
type viewMode int

const (
	viewMain viewMode = iota
	viewForm
	viewConfirmDelete
	viewConfig
)

// Messages
type tickMsg time.Time
type alarmFiredMsg scheduler.AlarmEvent
type alarmsLoadedMsg []db.Alarm
type snoozeFireMsg db.Alarm
type autoTimeoutMsg struct{}
type AlarmsChangedMsg struct{}
type pollTickMsg struct{}

// Model is the main bubbletea model.
type Model struct {
	store      *db.Store       // For settings (always local)
	alarmStore db.AlarmStore   // For alarm CRUD (local or remote)
	scheduler  *scheduler.Scheduler
	player     *player.Player
	clientMode bool

	// State
	alarms       []db.Alarm
	cursor       int
	mode         viewMode
	width        int
	height       int
	now          time.Time

	// Alarm firing state
	firingAlarm    *db.Alarm
	firingBlink    bool
	firingStart    time.Time
	snoozeCount    int
	snoozeUntil    time.Time
	snoozeName     string

	// Form state
	form         *formModel

	// Config state
	config       *configModel
	settings     db.Settings

	// Error/status
	statusMsg    string
}

func NewModel(store *db.Store, alarmStore db.AlarmStore, sched *scheduler.Scheduler, pl *player.Player, clientMode bool) Model {
	m := Model{
		store:      store,
		alarmStore: alarmStore,
		scheduler:  sched,
		player:     pl,
		clientMode: clientMode,
		now:        time.Now(),
		settings:   store.LoadSettings(),
	}
	m.applySettings()
	return m
}

// applySettings updates runtime styles and durations from current settings.
func (m *Model) applySettings() {
	ClockStyle = lipgloss.NewStyle().
		Foreground(lipgloss.Color(m.settings.ColorClock)).
		Bold(true)

	ClockAlarmStyle = lipgloss.NewStyle().
		Foreground(lipgloss.Color(m.settings.ColorAlarm)).
		Bold(true)

	AlarmEnabledStyle = lipgloss.NewStyle().
		Foreground(lipgloss.Color(m.settings.ColorClock))

	TitleStyle = lipgloss.NewStyle().
		Foreground(lipgloss.Color(m.settings.ColorClock)).
		Bold(true).
		Underline(true)

	FormActiveStyle = lipgloss.NewStyle().
		Foreground(lipgloss.Color(m.settings.ColorClock)).
		Bold(true)

	AlarmFiringStyle = lipgloss.NewStyle().
		Foreground(lipgloss.Color(m.settings.ColorAlarm)).
		Bold(true).
		Blink(true)

	FormErrorStyle = lipgloss.NewStyle().
		Foreground(lipgloss.Color(m.settings.ColorAlarm))
}

func (m Model) Init() tea.Cmd {
	cmds := []tea.Cmd{
		tick(),
		listenForAlarms(m.scheduler),
		loadAlarmsCmd(m.alarmStore),
	}
	if m.clientMode {
		cmds = append(cmds, pollTick(m.settings.PollSeconds))
	}
	return tea.Batch(cmds...)
}

func tick() tea.Cmd {
	return tea.Every(100*time.Millisecond, func(t time.Time) tea.Msg {
		return tickMsg(t)
	})
}

func listenForAlarms(sched *scheduler.Scheduler) tea.Cmd {
	return func() tea.Msg {
		event := <-sched.Events()
		return alarmFiredMsg(event)
	}
}

func loadAlarmsCmd(store db.AlarmStore) tea.Cmd {
	return func() tea.Msg {
		alarms, err := store.ListAlarms()
		if err != nil {
			return alarmsLoadedMsg(nil)
		}
		return alarmsLoadedMsg(alarms)
	}
}

func pollTick(seconds int) tea.Cmd {
	return tea.Tick(time.Duration(seconds)*time.Second, func(t time.Time) tea.Msg {
		return pollTickMsg{}
	})
}

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 alarmsLoadedMsg:
		if msg != nil {
			m.alarms = []db.Alarm(msg)
		}
		return m, nil

	case AlarmsChangedMsg:
		m.refreshAlarms()
		return m, nil

	case pollTickMsg:
		m.refreshAlarms()
		return m, pollTick(m.settings.PollSeconds)

	case tickMsg:
		m.now = time.Time(msg)
		if m.firingAlarm != nil {
			m.firingBlink = !m.firingBlink
		}
		return m, tick()

	case alarmFiredMsg:
		alarm := msg.Alarm
		m.startFiring(&alarm)
		m.refreshAlarms()
		return m, tea.Batch(
			listenForAlarms(m.scheduler),
			autoTimeoutCmd(m.autoTimeoutDuration()),
		)

	case snoozeFireMsg:
		alarm := db.Alarm(msg)
		m.snoozeCount++
		m.snoozeUntil = time.Time{}
		m.snoozeName = ""
		m.startFiring(&alarm)
		return m, autoTimeoutCmd(m.autoTimeoutDuration())

	case autoTimeoutMsg:
		if m.firingAlarm != nil {
			m.player.Stop()
			m.runCallback("timeout", m.firingAlarm.Name)
			m.statusMsg = fmt.Sprintf("Alarm auto-dismissed after %d minutes", m.settings.TimeoutMinutes)
			m.firingAlarm = nil
			m.snoozeCount = 0
		}
		return m, nil

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

	// Forward to form if active
	if m.mode == viewForm && m.form != nil {
		return m.form.Update(msg, &m)
	}

	return m, nil
}

func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
	key := msg.String()

	// Global: quit
	if key == "ctrl+c" {
		return m, tea.Quit
	}

	// If alarm is firing, handle dismiss/snooze
	if m.firingAlarm != nil {
		switch key {
		case "enter", " ", "d":
			m.player.Stop()
			alarm := m.firingAlarm
			m.runCallback("dismiss", alarm.Name)
			m.firingAlarm = nil
			m.snoozeCount = 0
			m.statusMsg = fmt.Sprintf("Alarm '%s' dismissed", alarm.Name)
		case "s":
			m.player.Stop()
			alarm := *m.firingAlarm
			m.runCallback("snooze", alarm.Name)
			dur := m.snoozeDuration()
			m.firingAlarm = nil
			m.snoozeUntil = time.Now().Add(dur)
			m.snoozeName = alarm.Name
			m.statusMsg = fmt.Sprintf("Snoozed '%s' for %d minutes", alarm.Name, m.settings.SnoozeMinutes)
			return m, snoozeCmd(alarm, dur)
		}
		return m, nil
	}

	// Form mode
	if m.mode == viewForm && m.form != nil {
		return m.form.HandleKey(msg, &m)
	}

	// Config mode
	if m.mode == viewConfig && m.config != nil {
		return m.config.HandleKey(msg, &m)
	}

	// Confirm delete mode
	if m.mode == viewConfirmDelete {
		switch key {
		case "y", "Y":
			if m.cursor < len(m.alarms) {
				_ = m.alarmStore.DeleteAlarm(m.alarms[m.cursor].ID)
				m.refreshAlarms()
				if m.cursor >= len(m.alarms) && m.cursor > 0 {
					m.cursor--
				}
				m.statusMsg = "Alarm deleted"
			}
			m.mode = viewMain
		case "n", "N", "escape", "esc":
			m.mode = viewMain
			m.statusMsg = ""
		}
		return m, nil
	}

	// Main view keys
	switch key {
	case "q":
		return m, tea.Quit
	case "j", "down":
		if m.cursor < len(m.alarms)-1 {
			m.cursor++
		}
	case "k", "up":
		if m.cursor > 0 {
			m.cursor--
		}
	case "g":
		m.cursor = 0
	case "G":
		if len(m.alarms) > 0 {
			m.cursor = len(m.alarms) - 1
		}
	case " ":
		// Toggle enabled
		if m.cursor < len(m.alarms) {
			_ = m.alarmStore.ToggleAlarm(m.alarms[m.cursor].ID)
			m.refreshAlarms()
		}
	case "a":
		m.form = newFormModel(nil)
		m.mode = viewForm
		m.statusMsg = ""
	case "e":
		if m.cursor < len(m.alarms) {
			alarm := m.alarms[m.cursor]
			m.form = newFormModel(&alarm)
			m.mode = viewForm
			m.statusMsg = ""
		}
	case "d":
		if m.cursor < len(m.alarms) {
			m.mode = viewConfirmDelete
			m.statusMsg = fmt.Sprintf("Delete '%s'? (y/n)", m.alarms[m.cursor].Name)
		}
	case "c":
		m.config = newConfigModel(m.settings)
		m.mode = viewConfig
		m.statusMsg = ""
	}
	return m, nil
}

func (m *Model) snoozeDuration() time.Duration {
	return time.Duration(m.settings.SnoozeMinutes) * time.Minute
}

func (m *Model) autoTimeoutDuration() time.Duration {
	return time.Duration(m.settings.TimeoutMinutes) * time.Minute
}

func (m *Model) startFiring(alarm *db.Alarm) {
	m.firingAlarm = alarm
	m.firingBlink = true
	m.firingStart = time.Now()
	sound := alarm.SoundPath
	if sound == "default" || sound == "" {
		sound = m.settings.DefaultSound
	}

	// Use crescendo if enabled
	cresc := player.CrescendoConfig{
		Enabled:   m.settings.CrescendoEnabled,
		StartPct:  m.settings.CrescendoStartPct,
		EndPct:    m.settings.CrescendoEndPct,
		DurationS: m.settings.CrescendoDurationS,
	}
	m.player.PlayLoopWithCrescendo(sound, cresc)

	// Fire callback
	m.runCallback("start", alarm.Name)
}

// runCallback executes the configured callback script with the event type and alarm name.
func (m *Model) runCallback(event, alarmName string) {
	script := m.settings.CallbackScript
	if script == "" {
		return
	}
	// Run in background, don't block
	go func() {
		_ = exec.Command(script, event, alarmName).Run()
	}()
}

func snoozeCmd(alarm db.Alarm, after time.Duration) tea.Cmd {
	return func() tea.Msg {
		time.Sleep(after)
		return snoozeFireMsg(alarm)
	}
}

func autoTimeoutCmd(dur time.Duration) tea.Cmd {
	return func() tea.Msg {
		time.Sleep(dur)
		return autoTimeoutMsg{}
	}
}

func (m *Model) refreshAlarms() {
	alarms, err := m.alarmStore.ListAlarms()
	if err == nil {
		m.alarms = alarms
	}
}

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

	var sections []string

	// Big clock
	clockStr := RenderBigClock(m.now, m.settings.BlinkOnMs, m.settings.BlinkOffMs, m.settings.ShowSeconds)
	if m.firingAlarm != nil && m.firingBlink {
		clockStr = ClockAlarmStyle.Render(clockStr)
	} else {
		clockStr = ClockStyle.Render(clockStr)
	}
	clockStr = lipgloss.PlaceHorizontal(m.width, lipgloss.Center, clockStr)
	sections = append(sections, clockStr)

	// Spacer
	sections = append(sections, "")

	// Date line
	dateLine := StatusStyle.Render(m.now.Format("Monday, 02 January 2006"))
	dateLine = lipgloss.PlaceHorizontal(m.width, lipgloss.Center, dateLine)
	sections = append(sections, dateLine)

	// Connection status
	var connStatus string
	if m.clientMode {
		connStatus = fmt.Sprintf("[Client: %s]", m.settings.ServerURL)
	} else {
		connStatus = "[Local]"
	}
	connLine := HelpStyle.Render(connStatus)
	connLine = lipgloss.PlaceHorizontal(m.width, lipgloss.Center, connLine)
	sections = append(sections, connLine)

	// Snooze indicator
	if !m.snoozeUntil.IsZero() && m.now.Before(m.snoozeUntil) {
		snoozeText := fmt.Sprintf("[Snoozing %s until %s]", m.snoozeName, m.snoozeUntil.Format("15:04:05"))
		snoozeLine := AlarmFiringStyle.Render(snoozeText)
		snoozeLine = lipgloss.PlaceHorizontal(m.width, lipgloss.Center, snoozeLine)
		sections = append(sections, snoozeLine)
	}

	// Firing alarm overlay
	if m.firingAlarm != nil {
		firingText := fmt.Sprintf("ALARM: %s", m.firingAlarm.Name)
		if m.firingAlarm.Description != "" {
			firingText += "\n" + m.firingAlarm.Description
		}
		if m.snoozeCount > 0 {
			firingText += fmt.Sprintf("\n(snoozed %d time(s))", m.snoozeCount)
		}
		firingText += fmt.Sprintf("\n\n[Enter/Space/d] Dismiss  [s] Snooze %dmin", m.settings.SnoozeMinutes)
		styled := AlarmFiringStyle.Render(firingText)
		styled = lipgloss.PlaceHorizontal(m.width, lipgloss.Center, styled)
		sections = append(sections, "", styled)
	}

	// Divider
	divider := DividerStyle.Render(strings.Repeat("─", min(m.width, 80)))
	divider = lipgloss.PlaceHorizontal(m.width, lipgloss.Center, divider)
	sections = append(sections, "", divider, "")

	// Form / Config view
	if m.mode == viewConfig && m.config != nil {
		configView := m.config.View()
		configView = lipgloss.PlaceHorizontal(m.width, lipgloss.Center, configView)
		sections = append(sections, configView)
	} else if m.mode == viewForm && m.form != nil {
		formView := m.form.View()
		formView = lipgloss.PlaceHorizontal(m.width, lipgloss.Center, formView)
		sections = append(sections, formView)
	} else {
		// Alarm list
		alarmList := m.renderAlarmList()
		alarmList = lipgloss.PlaceHorizontal(m.width, lipgloss.Center, alarmList)
		sections = append(sections, alarmList)
	}

	// Status message
	if m.statusMsg != "" {
		status := StatusStyle.Render(m.statusMsg)
		sections = append(sections, "", lipgloss.PlaceHorizontal(m.width, lipgloss.Center, status))
	}

	// Help bar at the bottom
	help := m.renderHelp()
	sections = append(sections, "", lipgloss.PlaceHorizontal(m.width, lipgloss.Center, help))

	return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center,
		strings.Join(sections, "\n"),
	)
}

const (
	alarmColTrigger = 18 // enough for cron like "30 7 * * 1-5"
	alarmColName    = 22
	alarmColDesc    = 30
)

// Total row width: 6 (prefix) + 18 (trigger) + 22 (name) + 30 (desc) = 76
const alarmRowWidth = 6 + alarmColTrigger + alarmColName + alarmColDesc

func (m Model) renderAlarmList() string {
	if len(m.alarms) == 0 {
		return StatusStyle.Render("No alarms. Press 'a' to add one.")
	}

	title := TitleStyle.Render("Alarms")

	header := fmt.Sprintf("      %-*s %-*s %-*s",
		alarmColTrigger, "Time/Trigger",
		alarmColName, "Name",
		alarmColDesc, "Description")

	var lines []string
	lines = append(lines, fmt.Sprintf("%-*s", alarmRowWidth, title), "")
	lines = append(lines, ColumnHeaderStyle.Render(fmt.Sprintf("%-*s", alarmRowWidth, header)))

	for i, a := range m.alarms {
		cursor := "  "
		if i == m.cursor {
			cursor = "▸ "
		}

		status := " ● "
		if !a.Enabled {
			status = " ○ "
		}

		var style lipgloss.Style
		if i == m.cursor {
			style = AlarmSelectedStyle
		} else if a.Enabled {
			style = AlarmEnabledStyle
		} else {
			style = AlarmDisabledStyle
		}

		trigger := a.Time
		if a.Trigger != "once" {
			trigger = a.Trigger
		}

		name := a.Name
		if len(name) > alarmColName-1 {
			name = name[:alarmColName-2] + "~"
		}
		desc := a.Description
		if len(desc) > alarmColDesc-1 {
			desc = desc[:alarmColDesc-2] + "~"
		}

		// Build line and pad to fixed total width so centering treats all rows equally
		raw := fmt.Sprintf("%s%s %-*s %-*s %-*s",
			cursor, status,
			alarmColTrigger, trigger,
			alarmColName, name,
			alarmColDesc, desc)
		line := fmt.Sprintf("%-*s", alarmRowWidth, raw)

		lines = append(lines, style.Render(line))
	}

	return strings.Join(lines, "\n")
}

func (m Model) renderHelp() string {
	switch m.mode {
	case viewForm, viewConfig:
		return HelpStyle.Render("Tab: next field  Shift+Tab: prev field  Enter: save  Esc: cancel")
	case viewConfirmDelete:
		return HelpStyle.Render("y: confirm delete  n: cancel")
	default:
		return HelpStyle.Render("j/k: navigate  a: add  e: edit  d: delete  space: toggle  c: config  q: quit")
	}
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
