package metricstui

import (
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	"github.com/NimbleMarkets/ntcharts/canvas"
	tslc "github.com/NimbleMarkets/ntcharts/linechart/timeserieslinechart"
	"github.com/charmbracelet/lipgloss"
)

var (
	appStyle = lipgloss.NewStyle().
			Padding(0, 1)

	panelStyle = lipgloss.NewStyle().
			Border(lipgloss.RoundedBorder()).
			Padding(0, 1)

	titleStyle = lipgloss.NewStyle().
			Bold(true)

	mutedStyle = lipgloss.NewStyle().
			Faint(true)
)

func renderRangeView(m rangeModel) string {
	series, ok := m.selectedSeries()
	if !ok {
		return renderEmptyStateView(m.width, m.height)
	}

	if useStackedLayout(m.width) {
		return appStyle.Width(m.width).Render(renderStackedLayout(m, series))
	}

	return appStyle.Width(m.width).Render(renderSplitLayout(m, series))
}

func renderSplitLayout(m rangeModel, series QueryRangeSeries) string {
	listWidth, _ := calculateListSize(m.width, m.height)
	rightWidth := maxInt(m.width-listWidth-1, 1)

	left := panelStyle.
		Width(listWidth).
		Height(maxInt(m.height-2, 1)).
		Render(lipgloss.JoinVertical(lipgloss.Left,
			titleStyle.Render("Series"),
			m.list.View(),
		))

	right := renderDetailPanel(series, rightWidth, maxInt(m.height-2, 1))
	return lipgloss.JoinHorizontal(lipgloss.Top, left, right)
}

func renderStackedLayout(m rangeModel, series QueryRangeSeries) string {
	listWidth, listHeight := calculateListSize(m.width, m.height)

	left := panelStyle.
		Width(listWidth).
		Height(maxInt(listHeight, 1)).
		Render(lipgloss.JoinVertical(lipgloss.Left,
			titleStyle.Render("Series"),
			m.list.View(),
		))

	rightHeight := maxInt(m.height-listHeight-1, 1)
	right := renderDetailPanel(series, maxInt(m.width-2, 1), rightHeight)
	return lipgloss.JoinVertical(lipgloss.Left, left, right)
}

func renderDetailPanel(series QueryRangeSeries, width, height int) string {
	contentWidth := maxInt(width-4, 1)
	chartHeight := maxInt(height-12, minChartHeight)

	chartView := renderChart(series, contentWidth, chartHeight)
	statsView := renderStats(series, contentWidth)

	return panelStyle.
		Width(maxInt(width, 1)).
		Height(maxInt(height, 1)).
		Render(lipgloss.JoinVertical(lipgloss.Left,
			titleStyle.Render("Selected Series"),
			lipgloss.NewStyle().Width(contentWidth).Render(series.Label),
			chartView,
			statsView,
		))
}

func renderEmptyStateView(width, height int) string {
	body := lipgloss.JoinVertical(lipgloss.Center,
		titleStyle.Render("No series returned for this query range"),
		mutedStyle.Render("Press q, esc, or ctrl+c to quit."),
	)

	panelWidth := maxInt(width-2, 1)
	panelHeight := maxInt(height-2, 1)

	return appStyle.Width(maxInt(width, 1)).Render(
		panelStyle.
			Width(panelWidth).
			Height(panelHeight).
			Align(lipgloss.Center, lipgloss.Center).
			Render(body),
	)
}

func renderChart(series QueryRangeSeries, width, height int) string {
	if width < minChartWidth || height < minChartHeight {
		return mutedStyle.Render("Chart unavailable at this terminal size.")
	}
	if len(series.Samples) == 0 {
		return mutedStyle.Render("No valid samples available for charting.")
	}

	chart := newSeriesChart(width, height, series)
	minTime, maxTime := displayedTimeRange(series)
	if !maxTime.After(minTime) {
		maxTime = minTime.Add(time.Minute)
	}

	minValue, maxValue := valueRange(series)
	if minValue == maxValue {
		if minValue == 0 {
			maxValue = 1
		} else {
			delta := minValue * 0.1
			if delta < 0 {
				delta = -delta
			}
			if delta == 0 {
				delta = 1
			}
			minValue -= delta
			maxValue += delta
		}
	}

	chart.SetTimeRange(minTime, maxTime)
	chart.SetViewTimeRange(minTime, maxTime)
	chart.SetYRange(minValue, maxValue)
	chart.SetViewYRange(minValue, maxValue)
	for _, sample := range series.Samples {
		chart.Push(tslc.TimePoint{
			Time:  sample.Timestamp.UTC(),
			Value: sample.Value,
		})
	}
	chart.DrawAll()
	overlaySampleMarkers(&chart, series, minTime, maxTime, minValue, maxValue)
	return chart.View()
}

func renderStats(series QueryRangeSeries, width int) string {
	stats := summarizeSeries(series)
	lines := []string{
		titleStyle.Render("Summary"),
		fmt.Sprintf("Latest: %s", formatFloat(stats.Latest)),
		fmt.Sprintf("Min: %s", formatFloat(stats.Min)),
		fmt.Sprintf("Max: %s", formatFloat(stats.Max)),
		fmt.Sprintf("Avg: %s", formatFloat(stats.Avg)),
		fmt.Sprintf("Sample count: %d", stats.SampleCount),
		fmt.Sprintf("Displayed range: %s", formatTimeRange(stats.Start, stats.End)),
	}
	if series.InvalidSamples > 0 {
		lines = append(lines, fmt.Sprintf("Skipped invalid samples: %d", series.InvalidSamples))
	}

	return lipgloss.NewStyle().
		Width(maxInt(width, 1)).
		Render(strings.Join(lines, "\n"))
}

type seriesSummary struct {
	Latest      float64
	Min         float64
	Max         float64
	Avg         float64
	SampleCount int
	Start       time.Time
	End         time.Time
}

func summarizeSeries(series QueryRangeSeries) seriesSummary {
	if len(series.Samples) == 0 {
		return seriesSummary{}
	}

	start := series.Samples[0].Timestamp.UTC()
	end := series.Samples[len(series.Samples)-1].Timestamp.UTC()
	minValue := series.Samples[0].Value
	maxValue := series.Samples[0].Value
	total := 0.0
	for _, sample := range series.Samples {
		if sample.Value < minValue {
			minValue = sample.Value
		}
		if sample.Value > maxValue {
			maxValue = sample.Value
		}
		total += sample.Value
	}

	return seriesSummary{
		Latest:      series.Samples[len(series.Samples)-1].Value,
		Min:         minValue,
		Max:         maxValue,
		Avg:         total / float64(len(series.Samples)),
		SampleCount: len(series.Samples),
		Start:       start,
		End:         end,
	}
}

func displayedTimeRange(series QueryRangeSeries) (time.Time, time.Time) {
	stats := summarizeSeries(series)
	return stats.Start, stats.End
}

func valueRange(series QueryRangeSeries) (float64, float64) {
	stats := summarizeSeries(series)
	return stats.Min, stats.Max
}

func formatFloat(value float64) string {
	return strconv.FormatFloat(value, 'f', 4, 64)
}

func newSeriesChart(width, height int, series QueryRangeSeries) tslc.Model {
	options := []tslc.Option{}

	start, end := displayedTimeRange(series)
	if !start.IsZero() && !end.IsZero() && end.After(start) && end.Sub(start) <= 24*time.Hour {
		options = append(options, tslc.WithXLabelFormatter(tslc.HourTimeLabelFormatter()))
	}

	return tslc.New(width, height, options...)
}

func overlaySampleMarkers(
	chart *tslc.Model,
	series QueryRangeSeries,
	minTime, maxTime time.Time,
	minValue, maxValue float64,
) {
	if chart == nil || len(series.Samples) == 0 {
		return
	}

	origin := chart.Origin()
	graphWidth := chart.GraphWidth()
	if graphWidth <= 0 || origin.Y <= 0 {
		return
	}

	minX := float64(minTime.Unix())
	maxX := float64(maxTime.Unix())
	xRange := maxX - minX
	yRange := maxValue - minValue
	markerStyle := lipgloss.NewStyle().Bold(true)

	for _, sample := range series.Samples {
		x := origin.X
		if xRange > 0 {
			ratioX := (float64(sample.Timestamp.UTC().Unix()) - minX) / xRange
			x = origin.X + int(math.Round(ratioX*float64(graphWidth)))
		}
		y := origin.Y
		if yRange > 0 {
			ratioY := (sample.Value - minValue) / yRange
			y = origin.Y - int(math.Round(ratioY*float64(origin.Y)))
		}

		if x < origin.X {
			x = origin.X
		}
		maxXCoord := origin.X + graphWidth
		if x > maxXCoord {
			x = maxXCoord
		}
		if y < 0 {
			y = 0
		}
		if y > origin.Y {
			y = origin.Y
		}

		chart.Canvas.SetCell(canvas.Point{X: x, Y: y}, canvas.NewCellWithStyle('*', markerStyle))
	}
}

func formatTimeRange(start, end time.Time) string {
	if start.IsZero() || end.IsZero() {
		return "n/a"
	}
	return fmt.Sprintf("%s -> %s", start.Format(time.RFC3339), end.Format(time.RFC3339))
}
