package router

import (
	"context"
	"testing"
	"time"
)

func TestMentionsTime(t *testing.T) {
	for _, s := range []string{
		"напомни в семь вечера позвонить маме",
		"напомни в 19:30 позвонить маме",
		"напомни без четверти восемь выходить",
		"напомни в половине первого пообедать",
		"разбуди меня полвосьмого",
		"напомни завтра принять лекарство",
		"напомни в пятницу забрать заказ",
		"напомни через двадцать минут",
		"напомни утром выпить таблетку",
		"remind me at noon to stretch",
	} {
		if !MentionsTime(s) {
			t.Errorf("MentionsTime(%q) = false; this sentence names a time", s)
		}
	}
}

// TestNamesAnHour — the gate on the reminder's time slot (V-577, V-579). A day
// word is not an hour, and a sentence that names no hour is asked about rather
// than completed from the clock.
func TestNamesAnHour(t *testing.T) {
	for _, s := range []string{
		"в 11:00", "на 9", "в 9", "в девять", "в 9 утра", "завтра в 9",
		"через час", "через двадцать минут", "в половине восьмого",
		"без четверти восемь", "remind me at noon", "вечером",
	} {
		if !NamesAnHour(s) {
			t.Errorf("NamesAnHour(%q) = false; this names an hour or an interval", s)
		}
	}
	for _, s := range []string{
		"на завтра", "что у меня сегодня?", "напомни завтра позвонить маме",
		"в пятницу", "позвонить маме", "",
	} {
		if NamesAnHour(s) {
			t.Errorf("NamesAnHour(%q) = true; no hour was spoken, so she has to ask", s)
		}
	}
}

// TestHourIsAmbiguous — the owner's rule of 2026-08-06. A bare hour is either
// half of the day and gets asked about; a qualifier, a written clock, an hour
// above twelve or an interval settles it and goes straight through.
func TestHourIsAmbiguous(t *testing.T) {
	for _, s := range []string{
		"напомни завтра в 3 заказать цветы", "в 9", "на 9", "в девять", "в 11 позвонить маме",
	} {
		if !HourIsAmbiguous(s) {
			t.Errorf("HourIsAmbiguous(%q) = false; the hour could be either half of the day", s)
		}
	}
	for _, s := range []string{
		"напомни в 9 вечера разгрузить стиралку", "завтра в 15:00", "в 21", "в 11:00",
		"через час", "через 10 минут", "remind me at noon", "напомни позвонить маме",
	} {
		if HourIsAmbiguous(s) {
			t.Errorf("HourIsAmbiguous(%q) = true; this time reads only one way", s)
		}
	}
}

// TestNamesAnInterval — an interval resolves to one instant, so it answers the
// hour and the day at once and is never asked about.
func TestNamesAnInterval(t *testing.T) {
	for _, s := range []string{"через час", "через 10 минут", "через полчаса", "in 30 minutes"} {
		if !NamesAnInterval(s) {
			t.Errorf("NamesAnInterval(%q) = false", s)
		}
	}
	for _, s := range []string{"завтра в 15:00", "в 9 вечера", ""} {
		if NamesAnInterval(s) {
			t.Errorf("NamesAnInterval(%q) = true", s)
		}
	}
}

// TestReminderSlotRefusesAnHourNobodySaid — the same rule where it bites. The
// parser answers a bare day word with that day at the current minute, and the
// slot must stay empty so the daemon asks.
func TestReminderSlotRefusesAnHourNobodySaid(t *testing.T) {
	now := time.Date(2026, 8, 6, 1, 38, 0, 0, time.UTC)
	ex := Extractor{Time: clockEchoParser{}}
	if got := ex.Extract(context.Background(), IntentReminder, "на завтра", now); got.HasTime {
		t.Errorf("«на завтра» filled the time slot with %s, which is the clock", got.Time.Format("15:04"))
	}
	// "на 9" names an hour, and a parser that answered with the clock did not
	// read it (V-610). Naming one is necessary and reading it is what fills the
	// slot, so this echo is refused too and the daemon asks.
	if got := ex.Extract(context.Background(), IntentReminder, "на 9", now); got.HasTime {
		t.Errorf("«на 9» took %s from the clock; the parser never read the nine", got.Time.Format("15:04"))
	}
	// A parser that does read it fills the slot, which is the other half of the
	// same rule.
	real := Extractor{Time: StubDateTimeParser{}}
	if got := real.Extract(context.Background(), IntentReminder, "на 9", now); !got.HasTime || got.Time.Hour() != 9 {
		t.Errorf("«на 9» must fill the slot with nine o'clock, got %+v", got)
	}
}

// clockEchoParser stands in for what both real parsers do with a bare day word:
// it answers with the current time of day.
type clockEchoParser struct{}

func (clockEchoParser) Parse(_ context.Context, _ string, now time.Time) (time.Time, bool, error) {
	return now.AddDate(0, 0, 1), true, nil
}

// A sentence with no time in it must not read as one, or a real follow-up stops
// inheriting the hour it meant.
func TestMentionsTimeIgnoresSentencesWithoutOne(t *testing.T) {
	for _, s := range []string{
		"напомни позвонить маме",
		"и ещё полить цветы",
		"напомни про счёт за свет",
		"купить три яблока",
		"перезапусти докер",
		"счёт 3:2 в нашу пользу",
		"",
	} {
		if MentionsTime(s) {
			t.Errorf("MentionsTime(%q) = true; there is no time in it", s)
		}
	}
}
