package tui

import (
	"fmt"
	"strings"
	"time"

	"github.com/charmbracelet/lipgloss"
)

var sparkChars = []rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}

func parseHex(hex string) (r, g, b uint8) {
	if len(hex) == 7 && hex[0] == '#' {
		_, _ = fmt.Sscanf(hex[1:], "%02x%02x%02x", &r, &g, &b)
	}
	return
}

func trueColorHex(c lipgloss.TerminalColor) string {
	switch v := c.(type) {
	case lipgloss.CompleteColor:
		return v.TrueColor
	case lipgloss.Color:
		return string(v)
	default:
		return ""
	}
}

func dimColor(hex string, brightness float64) lipgloss.Color {
	r, g, b := parseHex(hex)
	f := 0.3 + brightness*0.7
	return lipgloss.Color(fmt.Sprintf("#%02x%02x%02x",
		uint8(float64(r)*f),
		uint8(float64(g)*f),
		uint8(float64(b)*f),
	))
}

func withBg(s lipgloss.Style, bg lipgloss.TerminalColor) lipgloss.Style {
	if bg != nil {
		return s.Background(bg)
	}
	return s
}

func (m Model) latencyStyle(ms int64, bg lipgloss.TerminalColor) lipgloss.Style {
	var base lipgloss.TerminalColor
	var t float64
	switch {
	case ms < 200:
		base = m.st.sparkSuccess
		t = float64(ms) / 200
	case ms < 500:
		base = m.st.sparkWarning
		t = float64(ms-200) / 300
	default:
		base = m.st.sparkDanger
		t = float64(ms-500) / 1500
		if t > 1 {
			t = 1
		}
	}
	hex := trueColorHex(base)
	s := lipgloss.NewStyle().Foreground(dimColor(hex, t))
	return withBg(s, bg)
}

func (m Model) latencySparkline(latencies []time.Duration, statuses []bool, width int, bg lipgloss.TerminalColor) string {
	if len(latencies) == 0 {
		return withBg(m.st.subtleStyle, bg).Render(strings.Repeat("·", width))
	}

	samples := latencies
	sampledStatuses := statuses
	if len(samples) > width {
		samples = samples[len(samples)-width:]
		if len(sampledStatuses) > width {
			sampledStatuses = sampledStatuses[len(sampledStatuses)-width:]
		}
	}

	minL, maxL := samples[0], samples[0]
	for _, l := range samples {
		if l < minL {
			minL = l
		}
		if l > maxL {
			maxL = l
		}
	}
	spread := maxL - minL

	var sb strings.Builder
	if remaining := width - len(samples); remaining > 0 {
		sb.WriteString(withBg(m.st.subtleStyle, bg).Render(strings.Repeat("·", remaining)))
	}
	for i, l := range samples {
		idx := 0
		if spread > 0 {
			idx = int(float64(l-minL) / float64(spread) * 7)
			if idx > 7 {
				idx = 7
			}
		}
		ch := string(sparkChars[idx])
		isDown := i < len(sampledStatuses) && !sampledStatuses[i]
		if isDown {
			sb.WriteString(withBg(m.st.dangerStyle, bg).Render(ch))
		} else {
			sb.WriteString(m.latencyStyle(l.Milliseconds(), bg).Render(ch))
		}
	}
	return sb.String()
}

func (m Model) heartbeatSparkline(statuses []bool, width int, bg lipgloss.TerminalColor) string {
	if len(statuses) == 0 {
		return withBg(m.st.subtleStyle, bg).Render(strings.Repeat("·", width))
	}

	samples := statuses
	if len(samples) > width {
		samples = samples[len(samples)-width:]
	}

	var sb strings.Builder
	if remaining := width - len(samples); remaining > 0 {
		sb.WriteString(withBg(m.st.subtleStyle, bg).Render(strings.Repeat("·", remaining)))
	}
	for _, up := range samples {
		if up {
			sb.WriteString(withBg(m.st.specialStyle, bg).Render("▁"))
		} else {
			sb.WriteString(withBg(m.st.dangerStyle, bg).Render("█"))
		}
	}
	return sb.String()
}

func resolveSparklineIndex(x, sparkWidth, dataLen int) int {
	visible := dataLen
	if visible > sparkWidth {
		visible = sparkWidth
	}
	padding := sparkWidth - visible
	if x < padding {
		return -1
	}
	offset := 0
	if dataLen > sparkWidth {
		offset = dataLen - sparkWidth
	}
	return offset + (x - padding)
}

func (m Model) groupSparkline(groupID int, width int, bg lipgloss.TerminalColor) string {
	allSites := m.engine.GetAllSites()
	var childStatuses [][]bool
	for _, s := range allSites {
		if s.ParentID == groupID && !s.Paused && !m.isMonitorInMaintenance(s.ID) {
			hist, _ := m.engine.GetHistory(s.ID)
			if len(hist.Statuses) > 0 {
				childStatuses = append(childStatuses, hist.Statuses)
			}
		}
	}

	if len(childStatuses) == 0 {
		return withBg(m.st.subtleStyle, bg).Render(strings.Repeat("·", width))
	}

	maxLen := 0
	for _, s := range childStatuses {
		if len(s) > maxLen {
			maxLen = len(s)
		}
	}
	if maxLen > width {
		maxLen = width
	}

	aggregated := make([]bool, maxLen)
	for i := 0; i < maxLen; i++ {
		allUp := true
		for _, statuses := range childStatuses {
			idx := len(statuses) - maxLen + i
			if idx >= 0 && !statuses[idx] {
				allUp = false
				break
			}
		}
		aggregated[i] = allUp
	}

	var sb strings.Builder
	if remaining := width - len(aggregated); remaining > 0 {
		sb.WriteString(withBg(m.st.subtleStyle, bg).Render(strings.Repeat("·", remaining)))
	}
	for _, up := range aggregated {
		if up {
			sb.WriteString(withBg(m.st.subtleStyle, bg).Render("·"))
		} else {
			sb.WriteString(withBg(m.st.dangerStyle, bg).Render("•"))
		}
	}
	return sb.String()
}

func (m Model) groupUptime(groupID int) string {
	allSites := m.engine.GetAllSites()
	var allStatuses [][]bool
	for _, s := range allSites {
		if s.ParentID == groupID && !s.Paused && !m.isMonitorInMaintenance(s.ID) {
			hist, _ := m.engine.GetHistory(s.ID)
			if len(hist.Statuses) > 0 {
				allStatuses = append(allStatuses, hist.Statuses)
			}
		}
	}
	if len(allStatuses) == 0 {
		return m.st.subtleStyle.Render("—")
	}
	total, up := 0, 0
	for _, statuses := range allStatuses {
		for _, s := range statuses {
			total++
			if s {
				up++
			}
		}
	}
	return m.fmtUptime(func() []bool {
		out := make([]bool, total)
		idx := 0
		for _, statuses := range allStatuses {
			copy(out[idx:], statuses)
			idx += len(statuses)
		}
		return out
	}())
}
