// phraser/nudge_llm.go — what the model is told about one nudge, and what she
// says when it gives back nothing usable.
//
// The default nudge path is not this one: hand-written templates word every
// nudge unless Config.LLMNudges is on. See nudge_templates.go, and the note on
// that field for why.
package phraser

import (
	"fmt"
	"strings"
	"time"

	"github.com/kami/maven/internal/loop"
)

// ruleTopics — Russian gloss for each built-in rule name. The rule names are
// English identifiers; a 0.8B asked to nudge about "netdata_critical" writes
// about nothing. The daemon knows what its own rules mean, so it says so.
var ruleTopics = map[string]string{
	"water":            "он давно не пил воду",
	"meal":             "он давно не ел",
	"break":            "он давно без перерыва, пора встать и размяться",
	"service_down":     "сервис не отвечает, лежит",
	"netdata_critical": "критический алярм в netdata, проблема с диском или местом",
}

// ruleKeywords — the word the message must contain. The 0.8B drifts to
// whatever topic it saw last unless the required word is named outright.
var ruleKeywords = map[string]string{
	"water":            "воду",
	"meal":             "поешь",
	"break":            "перерыв",
	"service_down":     "сервис",
	"netdata_critical": "диск",
}

// ruleTopic turns a rule name into a Russian description of the situation.
// "routine:зарядка" and "morning:утро" carry their own Russian suffix.
func ruleTopic(rule string) string {
	if t, ok := ruleTopics[rule]; ok {
		return t
	}
	if i := strings.IndexByte(rule, ':'); i > 0 && i+1 < len(rule) {
		switch rule[:i] {
		case "morning":
			return "утро, пора начать день: " + rule[i+1:]
		default:
			return "пора сделать по распорядку: " + rule[i+1:]
		}
	}
	return rule
}

// ruleKeyword — the word the nudge must contain, or "" when the rule name's
// own Russian suffix already is that word.
func ruleKeyword(rule string) string {
	if k, ok := ruleKeywords[rule]; ok {
		return k
	}
	if i := strings.IndexByte(rule, ':'); i > 0 && i+1 < len(rule) {
		return rule[i+1:]
	}
	return ""
}

// ruDur — duration in Russian. humanDur is English and its output was landing
// verbatim in the message.
func ruDur(d time.Duration) string {
	if d < 0 {
		d = 0
	}
	h, m := int(d.Hours()), int(d.Minutes())%60
	switch {
	case h >= 2:
		return fmt.Sprintf("%d ч", h)
	case h == 1 && m >= 30:
		return "полтора часа"
	case h == 1:
		return "час"
	default:
		return fmt.Sprintf("%d мин", m)
	}
}

// fallbackNudge — plain Russian for when the model returns nothing parseable.
var fallbackNudges = map[string]string{
	"water":            "Ты давно не пил воду.",
	"meal":             "Ты давно не ел, поешь.",
	"break":            "Пора сделать перерыв.",
	"service_down":     "Сервис не отвечает.",
	"netdata_critical": "Критический алярм: проверь диск.",
}

func fallbackNudge(c loop.Candidate) string {
	if down := loop.DownServices(c.State); len(down) > 0 {
		return "Не отвечает: " + strings.Join(down, ", ") + "."
	}
	if s, ok := fallbackNudges[c.Rule.Name]; ok {
		return s
	}
	if kw := ruleKeyword(c.Rule.Name); kw != "" {
		return "Напоминаю: " + kw + "."
	}
	return "Напоминаю о деле."
}

func buildNudgePrompt(c loop.Candidate) string {
	var ctxParts []string
	ctxParts = append(ctxParts, "Ситуация: "+ruleTopic(c.Rule.Name))
	if f, ok := c.State.Facts[c.Rule.Name]; ok && f.Key != "" && f.Key != c.Rule.Name {
		ctxParts = append(ctxParts, "Что именно: "+f.Key)
	}
	if down := loop.DownServices(c.State); len(down) > 0 {
		// The names come from the same helper the rule fired on, so the model
		// is never handed a service that is actually up.
		ctxParts = append(ctxParts, "Какие сервисы лежат: "+strings.Join(down, ", "))
	}
	if d, ok := c.State.Since(c.Rule.Name); ok {
		ctxParts = append(ctxParts, "Прошло: "+ruDur(d))
	}
	switch sevLabel(c.Severity) {
	case "alarm":
		ctxParts = append(ctxParts, "Срочно, скажи прямо.")
	case "ops":
		ctxParts = append(ctxParts, "Это про сервер, не про здоровье.")
	}
	tail := "Напиши напоминание про эту ситуацию. Одно предложение, по-русски, в JSON."
	if kw := ruleKeyword(c.Rule.Name); kw != "" {
		// Last line on purpose: a 0.8B weights the end of the prompt hardest,
		// and without the required word it drifts back to the examples.
		tail += " Ответ ДОЛЖЕН содержать слово «" + kw + "»."
	}

	return strings.Join(ctxParts, "\n") + "\n\n" + tail
}
