package main

import (
	"context"
	"testing"
	"time"

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

// quietFakeAPI records the WriteFact the toggle performs.
type quietFakeAPI struct {
	ipc.UnimplementedCoreAPI
	got  ipc.WriteFactReq
	call int
}

func (a *quietFakeAPI) WriteFact(_ context.Context, req ipc.WriteFactReq) (int64, error) {
	a.got, a.call = req, a.call+1
	return 1, nil
}

// quietVerdict — what a phrase should do to the setting.
type quietVerdict int

const (
	quietNone quietVerdict = iota
	quietOn
	quietOff
)

func TestResolveQuietToggle(t *testing.T) {
	cases := []struct {
		text string
		want quietVerdict
	}{
		// ON vocabulary.
		{"quiet on", quietOn},
		{"quiet mode", quietOn},
		{"тихий режим", quietOn},
		{"тихий", quietOn},
		{"не шуми", quietOn},
		{"не беспокоить", quietOn},
		{"тихо", quietOn},
		// ON, inflected / embedded in a sentence.
		{"включи тихий режим", quietOn},
		{"побудь в тихом режиме", quietOn},
		{"Тихий Режим!", quietOn},
		{"тихая", quietOn},
		// The noun form and the comparative.
		{"включи режим тишины", quietOn},
		{"режим тишины", quietOn},
		{"сделай потише", quietOn},
		{"сделай тише", quietOn},
		{"потише", quietOn},
		// English, as the fixture phrases it.
		{"turn quiet mode back on", quietOn},
		{"enable quiet mode", quietOn},

		// OFF vocabulary — all seven, incl. the three that used to say ON.
		{"quiet off", quietOff},
		{"quiet end", quietOff},
		{"громкий режим", quietOff},
		{"шумный режим", quietOff},
		{"отмени тихий", quietOff},
		{"выключи тихий", quietOff},
		{"не тихо", quietOff},
		// OFF wins over the ON words it contains.
		{"выключи тихий режим", quietOff},
		{"отмени тихий режим пожалуйста", quietOff},
		{"верни громкий режим", quietOff},
		{"выключи режим тишины", quietOff},
		{"хватит тишины", quietOff},
		{"turn off quiet mode", quietOff},
		{"quiet mode off", quietOff},
		{"stop quiet mode", quietOff},
		{"disable quiet mode", quietOff},

		// False positives: "тихо"/"тихий" as ordinary Russian.
		{"очень тихий сегодня день", quietNone},
		{"в комнате тихо", quietNone},
		{"тихонько напомни", quietNone},
		{"потихоньку", quietNone},
		{"тихонько", quietNone},
		{"он говорил тихим голосом весь вечер", quietNone},
		{"в тишине лучше думается", quietNone},
		{"на улице стало потише", quietNone},

		// Unrelated.
		{"напомни завтра позвонить маме", quietNone},
		{"какая погода", quietNone},
		{"", quietNone},
	}

	for _, tc := range cases {
		t.Run(tc.text, func(t *testing.T) {
			api := &quietFakeAPI{}
			h := &reactiveHandler{api: api, now: func() time.Time { return time.Unix(0, 0).UTC() }}
			reply, handled := h.resolveQuietToggle(context.Background(), tc.text, sourceVoice)

			if tc.want == quietNone {
				if handled || reply != "" {
					t.Fatalf("%q: got (%q, %v), want no match", tc.text, reply, handled)
				}
				if api.call != 0 {
					t.Fatalf("%q: wrote a fact on a non-match", tc.text)
				}
				return
			}
			if !handled {
				t.Fatalf("%q: not handled, want %v", tc.text, tc.want)
			}
			wantReply, wantVal := "тихий режим выключен.", "false"
			if tc.want == quietOn {
				wantReply, wantVal = "тихий режим включён. буду реже напоминать.", "true"
			}
			if reply != wantReply {
				t.Errorf("%q: reply = %q, want %q", tc.text, reply, wantReply)
			}
			if api.call != 1 {
				t.Fatalf("%q: WriteFact called %d times, want 1", tc.text, api.call)
			}
			if api.got.Kind != "config" || api.got.Key != "quiet_hours" || api.got.Source != "tap:voice" || api.got.Confidence != 1.0 {
				t.Errorf("%q: request shape = %+v", tc.text, api.got)
			}
			if api.got.Value != wantVal {
				t.Errorf("%q: value = %q, want %q", tc.text, api.got.Value, wantVal)
			}
		})
	}
}

// TestQuietToggleNegationIsNotAdjacency — negation used to be an adjacency
// pattern ({"не","тих"} in the OFF list), so any word between the negator and
// the quiet word made the ON pattern win and asking for quiet mode to STOP
// turned it on. Negation is scanned over the whole utterance now.
func TestQuietToggleNegationIsNotAdjacency(t *testing.T) {
	off := []string{
		"не надо тихий режим",
		"не хочу тихий режим",
		"тихий режим выключи",
		"убери тихий режим",
		"хватит тихого режима",
		"прекрати тихий режим",
		"тихий режим отмени пожалуйста",
	}
	for _, text := range off {
		t.Run(text, func(t *testing.T) {
			on, isOff := classifyQuietToggle(text)
			if on || !isOff {
				t.Fatalf("%q: want OFF, got on=%v off=%v", text, on, isOff)
			}
		})
	}

	// The two ON phrases that are themselves built on "не" must stay ON: they
	// are requests FOR quiet, not negations of one.
	for _, text := range []string{"не шуми", "не беспокоить"} {
		t.Run(text, func(t *testing.T) {
			on, isOff := classifyQuietToggle(text)
			if !on || isOff {
				t.Fatalf("%q: want ON, got on=%v off=%v", text, on, isOff)
			}
		})
	}
}

// TestQuietToggleRecordsTheChannelItArrivedOn — the toggle is reachable from
// mavweb /api/chat and telegram, not only the microphone. Every write used to
// be stamped "tap:voice", so a toggle typed into the web UI claimed a mic wrote
// it and the provenance column lied about a daemon-wide setting.
func TestQuietToggleRecordsTheChannelItArrivedOn(t *testing.T) {
	for _, src := range []turnSource{sourceVoice, sourceText} {
		t.Run(string(src), func(t *testing.T) {
			api := &quietFakeAPI{}
			h := &reactiveHandler{api: api, now: func() time.Time { return time.Unix(0, 0).UTC() }}
			if _, handled := h.resolveQuietToggle(context.Background(), "тихий режим", src); !handled {
				t.Fatal("expected the toggle to match")
			}
			if api.got.Source != string(src) {
				t.Errorf("source = %q, want %q", api.got.Source, src)
			}
		})
	}
}
