package ident_test

import (
	"errors"
	"strings"
	"testing"
	"time"

	"git.vakhrushev.me/av/healthlog/internal/ident"
)

func TestNewIDIsLowercaseULID(t *testing.T) {
	id := ident.NewID()

	if len(id) != 26 {
		t.Errorf("длина = %d, ожидалось 26: %q", len(id), id)
	}
	if id != strings.ToLower(id) {
		t.Errorf("id не в нижнем регистре: %q", id)
	}
	if ident.NewID() == id {
		t.Error("два вызова вернули один и тот же id")
	}
}

// Идентификаторы сортируемы по времени создания — на этом держится
// хронология выборок и корреляция в логах.
func TestNewIDSortsByCreationTime(t *testing.T) {
	prev := ident.NewID()
	for range 10 {
		next := ident.NewID()
		if next <= prev {
			t.Fatalf("порядок нарушен: %q после %q", next, prev)
		}
		prev = next
	}
}

// Внешний id приводится к каноническому виду: сравнение строк в SQLite
// побайтовое, а base32 ULID при декодировании нечувствителен к регистру.
func TestParseNormalizesCase(t *testing.T) {
	id := ident.NewID()

	got, err := ident.Parse(strings.ToUpper(id))
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	if got != id {
		t.Errorf("Parse вернул %q, ожидалось %q", got, id)
	}
}

func TestParseRejectsGarbage(t *testing.T) {
	for _, s := range []string{"", "не-id", "01k1", strings.Repeat("z", 26)} {
		if _, err := ident.Parse(s); !errors.Is(err, ident.ErrInvalid) {
			t.Errorf("Parse(%q): ошибка = %v, ожидалась ErrInvalid", s, err)
		}
	}
}

// Время из ULID нужно телу, лежащему в архиве без учётной записи: другого
// источника метки приёма у него нет.
func TestTimeOfВUTCИСекундах(t *testing.T) {
	t.Parallel()

	id := ident.NewID()
	at, err := ident.TimeOf(id)
	if err != nil {
		t.Fatalf("TimeOf(%q): %v", id, err)
	}

	// UTC, а не локальная зона: ulid.Time собирает время через time.Unix, то
	// есть в зоне машины, и метка подобранного тела сравнивалась бы с меткой из
	// БД по-разному на разных машинах.
	if at.Location() != time.UTC {
		t.Errorf("зона %v, ожидался UTC", at.Location())
	}
	// Секундная точность — та же, что у store.Now: метка уезжает в колонку
	// фиксированной ширины, где лексикографический порядок равен хронологии.
	if at.Nanosecond() != 0 {
		t.Errorf("метка %v несёт доли секунды", at)
	}
	if d := time.Since(at); d < 0 || d > time.Minute {
		t.Errorf("метка %v далека от настоящего времени (%v)", at, d)
	}
}

// Монотонность ULID — то, на чём стоит порядок проигрывания журнала для
// подобранных тел.
func TestTimeOfСохраняетПорядок(t *testing.T) {
	t.Parallel()

	first, err := ident.TimeOf(ident.NewID())
	if err != nil {
		t.Fatalf("TimeOf: %v", err)
	}
	time.Sleep(1100 * time.Millisecond)
	second, err := ident.TimeOf(ident.NewID())
	if err != nil {
		t.Fatalf("TimeOf: %v", err)
	}
	if !second.After(first) {
		t.Errorf("порядок меток не сохранился: %v, затем %v", first, second)
	}
}

func TestTimeOfОтвергаетНеИдентификатор(t *testing.T) {
	t.Parallel()

	for _, s := range []string{"", "не-ulid", "01kyzbb5zy4cbkbc0agb6ad07", "readme.txt"} {
		if _, err := ident.TimeOf(s); !errors.Is(err, ident.ErrInvalid) {
			t.Errorf("TimeOf(%q) дал %v, ожидался ErrInvalid", s, err)
		}
	}
}
