package dialogue

import (
	"testing"
	"time"
)

var pendingBase = time.Date(2026, 8, 6, 9, 0, 0, 0, time.UTC)

func TestCapabilityForCoversEveryIntent(t *testing.T) {
	for _, in := range []Intent{
		IntentAct, IntentReminder, IntentFact, IntentNote,
		IntentQuery, IntentChat, IntentSystem,
	} {
		if CapabilityFor(in) == "" {
			t.Errorf("intent %q maps to no capability", in)
		}
	}
	if got := CapabilityFor(Intent("nonsense")); got != "" {
		t.Errorf("unknown intent became capability %q, want empty", got)
	}
}

// A parked question must read as the action it is assembling, without the
// caller having to name the capability twice.
func TestPendingQuestionActionDerivesCapability(t *testing.T) {
	q := &PendingQuestion{
		Intent:      IntentReminder,
		Slots:       Slots{Text: "позвонить маме"},
		Missing:     []Slot{SlotTime},
		Utterance:   "напомни позвонить маме",
		Asked:       pendingBase,
		TTL:         time.Minute,
		Attempts:    1,
		MaxAttempts: 2,
	}
	a := q.Action()
	if a.Capability != CapReminderCreate {
		t.Errorf("capability = %q, want %q", a.Capability, CapReminderCreate)
	}
	if a.Utterance != q.Utterance || a.Attempts != 1 || a.MaxAttempts != 2 || !a.Asked.Equal(pendingBase) {
		t.Errorf("action did not carry the question's fields: %+v", a)
	}
}

func TestPendingActionGaps(t *testing.T) {
	for _, tc := range []struct {
		name     string
		action   PendingAction
		want     []Slot
		complete bool
	}{
		{
			name:     "time still missing",
			action:   PendingAction{Missing: []Slot{SlotTime}, Slots: Slots{Text: "позвонить маме"}},
			want:     []Slot{SlotTime},
			complete: false,
		},
		{
			name:     "asked slot now filled",
			action:   PendingAction{Missing: []Slot{SlotTime}, Slots: Slots{Time: pendingBase, HasTime: true}},
			want:     nil,
			complete: true,
		},
		{
			name:     "two gaps reported in ask order",
			action:   PendingAction{Missing: []Slot{SlotText, SlotTime}},
			want:     []Slot{SlotText, SlotTime},
			complete: false,
		},
		{
			name:     "nothing asked is complete",
			action:   PendingAction{},
			want:     nil,
			complete: true,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			got := tc.action.Gaps()
			if len(got) != len(tc.want) {
				t.Fatalf("gaps = %v, want %v", got, tc.want)
			}
			for i := range got {
				if got[i] != tc.want[i] {
					t.Fatalf("gaps = %v, want %v", got, tc.want)
				}
			}
			if tc.action.Complete() != tc.complete {
				t.Errorf("Complete() = %v, want %v", tc.action.Complete(), tc.complete)
			}
		})
	}
}

// The typed action must answer the TTL and attempt-cap questions the same way
// the parked question always did — this is a widening, not new behaviour.
func TestPendingActionTTLAndAttempts(t *testing.T) {
	a := PendingAction{Asked: pendingBase, TTL: time.Minute}
	if a.IsExpired(pendingBase.Add(30 * time.Second)) {
		t.Error("expired inside the TTL")
	}
	if !a.IsExpired(pendingBase.Add(2 * time.Minute)) {
		t.Error("not expired past the TTL")
	}
	a.Attempts = DefaultMaxAttempts - 1
	if !a.CanAsk() {
		t.Error("cannot ask with an attempt left")
	}
	a.Attempts = DefaultMaxAttempts
	if a.CanAsk() {
		t.Error("asked past the default cap")
	}
	a = PendingAction{Asked: pendingBase, TTL: time.Minute, MaxAttempts: 1, Attempts: 1}
	if a.CanAsk() {
		t.Error("asked past an explicit cap of 1")
	}
}
