package points

import (
	"testing"
	"time"

	"git.vakhrushev.me/av/healthlog/internal/catalog"
	"git.vakhrushev.me/av/healthlog/internal/store"
)

func day(d int) time.Time { return time.Date(2026, 6, d, 0, 0, 0, 0, time.UTC) }

func span(layer string, first, last time.Time) store.LayerSpan {
	return store.LayerSpan{Layer: layer, First: first, Last: last}
}

// Правило выбора слоя целиком: охват, равенство охватов, выбывание по пустому
// пересечению и вырожденный вход.
func TestВыборСлояПоОхвату(t *testing.T) {
	from, to := day(1), day(30)

	cases := []struct {
		name  string
		spans []store.LayerSpan
		want  string
	}{
		{
			// «Вес за год»: нижний слой плотнее, но короче. Победа по числу
			// точек вернула бы три дня вместо периода и не сказала бы ни слова.
			name: "мелкий слой охватывает меньше крупного",
			spans: []store.LayerSpan{
				span("raw", day(1), day(4)),
				span("hour", day(1), day(29)),
			},
			want: "hour",
		},
		{
			name: "охваты равны — побеждает мелкий",
			spans: []store.LayerSpan{
				span("hour", day(1), day(29)),
				span("minute", day(1), day(29)),
			},
			want: "minute",
		},
		{
			// Слой, чьи данные лежат целиком вне периода, выбывает — иначе он
			// выиграл бы охват и отдал пустой ряд.
			name: "слой лежит вне периода",
			spans: []store.LayerSpan{
				span("hour", day(1).Add(-48*time.Hour), day(1).Add(-24*time.Hour)),
				span("minute", day(2), day(3)),
			},
			want: "minute",
		},
		{
			// Пересечение считается по ПЕРИОДУ, а не по данным: слой, торчащий
			// за обе границы, не получает бесконечного охвата.
			name: "слой шире периода",
			spans: []store.LayerSpan{
				span("hour", day(1).Add(-240*time.Hour), day(30).Add(240*time.Hour)),
				span("minute", day(1), day(30)),
			},
			want: "minute",
		},
		{
			name:  "слоёв нет вовсе",
			spans: nil,
			want:  "",
		},
		{
			// Единственная точка ровно на границе периода: охват нулевой, но
			// слой пригоден — точка в ответ попадёт.
			name:  "единственная точка на границе",
			spans: []store.LayerSpan{span("raw", from, from)},
			want:  "raw",
		},
		{
			// Точка ровно на `to` в период не входит (полуинтервал), значит и
			// слой из выбора выбывает.
			name:  "единственная точка на правой границе",
			spans: []store.LayerSpan{span("raw", to, to)},
			want:  "",
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			if got := pickLayer(from, to, c.spans); got != c.want {
				t.Errorf("выбран слой %q, ожидался %q", got, c.want)
			}
		})
	}
}

// Порядок слоёв в выборке не должен влиять на исход: иначе ответ стал бы
// функцией порядка строк, который задаёт SQLite, а не правило.
func TestВыборСлояНеЗависитОтПорядка(t *testing.T) {
	from, to := day(1), day(30)
	a := span("hour", day(1), day(29))
	b := span("minute", day(1), day(29))

	if pickLayer(from, to, []store.LayerSpan{a, b}) != pickLayer(from, to, []store.LayerSpan{b, a}) {
		t.Error("выбор слоя зависит от порядка охватов")
	}
}

// Применимость рода к ОТДАННОМУ ряду. Разряд, ради которого поле заведено:
// накопительная метрика на нижнем слое HAE неприменима, потому что нижний слой
// это интерполяция, а не сэмплы, и сумма по нему завышает втрое.
func TestПрименимостьРодаКРяду(t *testing.T) {
	cases := []struct {
		style catalog.Style
		layer string
		want  bool
	}{
		{catalog.Cumulative, "raw", false},
		{catalog.Cumulative, "minute", true},
		{catalog.Cumulative, "hour", true},
		{catalog.Cumulative, "sample", true},
		{catalog.Instant, "raw", true},
		{catalog.Unknown, "minute", false},
		{catalog.Unknown, "raw", false},
		{catalog.Cumulative, "", false},
		{catalog.Instant, "", false},
	}

	for _, c := range cases {
		if got := applicable(c.style, c.layer); got != c.want {
			t.Errorf("применимость %s на слое %q = %v, ожидалось %v", c.style, c.layer, got, c.want)
		}
	}
}
