package metricstui

import (
	"strings"
	"testing"
	"time"

	tea "github.com/charmbracelet/bubbletea"
)

func TestRangeModelSelectionUpdates(t *testing.T) {
	t.Parallel()

	model := newRangeModel(&QueryRangeResponse{
		Series: []QueryRangeSeries{
			testSeries("cpu_usage{instance=\"a\"}", 1, 2),
			testSeries("cpu_usage{instance=\"b\"}", 3, 4),
			testSeries("cpu_usage{instance=\"c\"}", 5, 6),
		},
	})

	model = updateRangeModel(t, model, tea.WindowSizeMsg{Width: 100, Height: 30})
	if got, want := model.list.Index(), 0; got != want {
		t.Fatalf("initial list index = %d, want %d", got, want)
	}

	model = updateRangeModel(t, model, tea.KeyMsg{Type: tea.KeyDown})
	if got, want := model.list.Index(), 1; got != want {
		t.Fatalf("after down list index = %d, want %d", got, want)
	}

	model = updateRangeModel(t, model, tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")})
	if got, want := model.list.Index(), 2; got != want {
		t.Fatalf("after j list index = %d, want %d", got, want)
	}

	model = updateRangeModel(t, model, tea.KeyMsg{Type: tea.KeyUp})
	if got, want := model.list.Index(), 1; got != want {
		t.Fatalf("after up list index = %d, want %d", got, want)
	}

	model = updateRangeModel(t, model, tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("k")})
	if got, want := model.list.Index(), 0; got != want {
		t.Fatalf("after k list index = %d, want %d", got, want)
	}
}

func TestRangeModelQuitKeys(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name string
		key  tea.KeyMsg
	}{
		{
			name: "q",
			key:  tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("q")},
		},
		{
			name: "esc",
			key:  tea.KeyMsg{Type: tea.KeyEsc},
		},
		{
			name: "ctrl+c",
			key:  tea.KeyMsg{Type: tea.KeyCtrlC},
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			model := newRangeModel(&QueryRangeResponse{
				Series: []QueryRangeSeries{testSeries("cpu_usage{instance=\"a\"}", 1, 2)},
			})

			next, cmd := model.Update(tt.key)
			if cmd == nil {
				t.Fatal("Update() cmd = nil, want tea.Quit")
			}

			msg := cmd()
			if _, ok := msg.(tea.QuitMsg); !ok {
				t.Fatalf("Update() cmd() = %T, want tea.QuitMsg", msg)
			}

			nextModel, ok := next.(rangeModel)
			if !ok {
				t.Fatalf("Update() model = %T, want rangeModel", next)
			}

			if got, want := nextModel.quitting, true; got != want {
				t.Fatalf("quitting = %v, want %v", got, want)
			}
		})
	}
}

func TestRangeModelEmptyStateView(t *testing.T) {
	t.Parallel()

	model := newRangeModel(&QueryRangeResponse{})
	model = updateRangeModel(t, model, tea.WindowSizeMsg{Width: 60, Height: 12})

	view := model.View()
	if !strings.Contains(view, "No series returned for this query range") {
		t.Fatalf("View() = %q, want empty-state message", view)
	}
	if !strings.Contains(view, "Press q, esc, or ctrl+c to quit.") {
		t.Fatalf("View() = %q, want quit hint", view)
	}
}

func testSeries(label string, values ...float64) QueryRangeSeries {
	samples := make([]QueryRangeSample, 0, len(values))
	start := time.Unix(1712123000, 0).UTC()
	for i, value := range values {
		samples = append(samples, QueryRangeSample{
			Timestamp: start.Add(time.Minute * time.Duration(i)),
			Value:     value,
		})
	}

	return QueryRangeSeries{
		Label:   label,
		Samples: samples,
	}
}

func updateRangeModel(t *testing.T, model rangeModel, msg tea.Msg) rangeModel {
	t.Helper()

	next, _ := model.Update(msg)
	nextModel, ok := next.(rangeModel)
	if !ok {
		t.Fatalf("Update() model = %T, want rangeModel", next)
	}
	return nextModel
}
