package picker

import (
	"strings"
	"testing"
	"time"

	"github.com/charmbracelet/lipgloss"

	"gitea.apointless.space/bsncubed/dial/internal/sshconf"
	"gitea.apointless.space/bsncubed/dial/internal/store"
)

func sampleModel() *model {
	hosts := []sshconf.Host{
		{Alias: "web1", HostName: "10.0.0.5", User: "deploy", Port: "2222", ProxyJump: "bastion", Tags: []string{"prod", "riedel"}},
		{Alias: "db-primary", HostName: "db.internal", User: "postgres", Port: "22", Tags: []string{"prod"}},
		{Alias: "bastion", HostName: "bastion.example.com", User: "admin"},
		{Alias: "staging-web", HostName: "10.1.0.9", User: "deploy", Tags: []string{"staging"}},
	}
	data := store.DefaultData()
	data.Stats["web1"] = &store.HostStat{Count: 9, LastUsed: time.Now()}
	data.Stats["bastion"] = &store.HostStat{Count: 3, LastUsed: time.Now().Add(-time.Hour)}
	data.Favorites = []string{"bastion"}

	settings := store.DefaultSettings()

	m := newModelForTest(hosts, data, settings)
	m.width, m.height = 90, 30
	m.ensureVisible()
	return m
}

// newModelForTest mirrors the setup Run does, without starting a tea program.
func newModelForTest(hosts []sshconf.Host, data store.Data, settings store.Settings) *model {
	res, _ := buildModel(hosts, data, settings)
	return res
}

func TestRenderSnapshot(t *testing.T) {
	m := sampleModel()
	out := m.View()
	if !strings.Contains(out, "dial") {
		t.Error("header missing")
	}
	if !strings.Contains(out, "web1") || !strings.Contains(out, "bastion") {
		t.Error("hosts missing from render")
	}
	// bastion is a favourite, so it should sort to the top of a recent-sort list.
	t.Logf("\n%s", out)
}

func TestFilterNarrows(t *testing.T) {
	m := sampleModel()
	m.query = "staging"
	m.applyFilter()
	if len(m.view) != 1 {
		t.Fatalf("expected 1 match for 'staging', got %d", len(m.view))
	}
	if m.rows[m.view[0]].host.Alias != "staging-web" {
		t.Errorf("wrong match: %s", m.rows[m.view[0]].host.Alias)
	}
}

func TestFavoriteSortsFirst(t *testing.T) {
	m := sampleModel()
	if len(m.view) == 0 {
		t.Fatal("no rows")
	}
	if !m.rows[m.view[0]].fav {
		t.Errorf("favourite should be first, got %s", m.rows[m.view[0]].host.Alias)
	}
}

func TestAlphaSort(t *testing.T) {
	m := sampleModel()
	m.sortM = store.SortAlpha
	m.sortAll()
	m.applyFilter()
	// Favourite (bastion) still pins first; the rest are alphabetical.
	got := []string{}
	for _, vi := range m.view {
		got = append(got, m.rows[vi].host.Alias)
	}
	want := []string{"bastion", "db-primary", "staging-web", "web1"}
	if strings.Join(got, ",") != strings.Join(want, ",") {
		t.Errorf("alpha order = %v, want %v", got, want)
	}
}

func TestSortCycle(t *testing.T) {
	s := store.SortRecent
	seq := []store.SortMode{}
	for i := 0; i < 3; i++ {
		s = s.Next()
		seq = append(seq, s)
	}
	want := []store.SortMode{store.SortFrequent, store.SortAlpha, store.SortRecent}
	for i := range want {
		if seq[i] != want[i] {
			t.Errorf("cycle[%d] = %s, want %s", i, seq[i], want[i])
		}
	}
}

func TestGrouping(t *testing.T) {
	m := sampleModel()
	m.grouped = true
	m.applyFilter()

	// Expect a Favourites header first, then tag sections, then no untagged
	// (bastion, the only untagged host, is a favourite).
	if h, ok := m.headers[0]; !ok || !strings.Contains(h.title, "favourites") {
		t.Fatalf("first section should be favourites, got %+v", m.headers[0])
	}
	// bastion (fav) appears once, only in the favourites section.
	count := 0
	for _, vi := range m.view {
		if m.rows[vi].host.Alias == "bastion" {
			count++
		}
	}
	if count != 1 {
		t.Errorf("favourite host should appear exactly once, got %d", count)
	}
	// Every non-header host is reachable; view length equals host count (no dupes).
	if len(m.view) != len(m.rows) {
		t.Errorf("grouped view should contain each host once: got %d, want %d", len(m.view), len(m.rows))
	}
	// A tag section header should carry the tag as its colour key.
	foundProd := false
	for _, h := range m.headers {
		if h.title == "prod" && h.tag == "prod" {
			foundProd = true
		}
	}
	if !foundProd {
		t.Error("expected a 'prod' tag section header")
	}
}

func TestNoOverflow(t *testing.T) {
	for _, grouped := range []bool{false, true} {
		for _, w := range []int{40, 60, 90, 140} {
			for _, h := range []int{18, 24, 40} {
				m := sampleModel()
				m.grouped = grouped
				m.applyFilter()
				m.width, m.height = w, h
				out := m.View()
				lines := strings.Split(out, "\n")
				if len(lines) > h {
					t.Errorf("grouped=%v %dx%d: %d lines > height", grouped, w, h, len(lines))
				}
				for _, ln := range lines {
					if lw := lipgloss.Width(ln); lw > w {
						t.Errorf("grouped=%v %dx%d: line %d cols > width", grouped, w, h, lw)
					}
				}
			}
		}
	}
}
