package main

import (
	"encoding/json"
	"testing"
)

func TestIsAssent_Positive(t *testing.T) {
	cases := []string{
		"go ahead", "Go ahead.", "yes", "Yes!", "yeah", "yep", "do it",
		"proceed", "approve", "ship it", "sounds good", "lgtm", "please do",
		"ok go ahead and run it",
	}
	for _, c := range cases {
		if !isAssent(c) {
			t.Errorf("isAssent(%q) = false, want true", c)
		}
	}
}

func TestIsAssent_Negative(t *testing.T) {
	cases := []string{
		"no", "no, don't", "wait", "hold on", "not yet", "cancel that",
		"nevermind", "what's the plan for tomorrow?", "how many CPUs does strong have?",
		"maybe later", "",
	}
	for _, c := range cases {
		if isAssent(c) {
			t.Errorf("isAssent(%q) = true, want false", c)
		}
	}
}

func TestIsAssent_NegationBeatsAssentWord(t *testing.T) {
	// Contains "yes" as a substring pattern risk word but is clearly not
	// assent — negation must win.
	cases := []string{
		"no, don't do it yet",
		"wait, not yet please",
	}
	for _, c := range cases {
		if isAssent(c) {
			t.Errorf("isAssent(%q) = true, want false (negation should block)", c)
		}
	}
}

// TestIsAssent_WholeWordBoundary regression-tests a real false positive found
// live: the old substring check matched "yes" inside "yesterday" (and would
// equally match "confirm" inside "confirmed"/"unconfirmed" for
// isTypedConfirmation below) because only negation used a word-boundary
// check — assent/confirm words used a bare strings.Contains. Confirmed via a
// throwaway probe before being fixed; kept here permanently so a future
// change can't silently reintroduce it.
func TestIsAssent_WholeWordBoundary(t *testing.T) {
	cases := []string{
		"not sure, maybe yesterday's logs show something useful",
		"my eyesight isn't great, what does that say",
	}
	for _, c := range cases {
		if isAssent(c) {
			t.Errorf("isAssent(%q) = true, want false (word-boundary: 'yes' must not match inside 'yesterday'/'eyesight')", c)
		}
	}
}

// TestIsTypedConfirmation_ContractedNegation regression-tests the other real
// false positive: isTypedConfirmation gates DESTRUCTIVE actions, and
// "confirm" matching inside "confirmed" combined with contracted negatives
// ("haven't") not being in negationWords meant a message that explicitly
// says the operator has NOT confirmed something could read as confirming it.
func TestIsTypedConfirmation_ContractedNegation(t *testing.T) {
	cases := []string{
		"I haven't confirmed anything yet, let me think",
		"that isn't confirmed on my end",
		"we can't confirm that until tomorrow",
	}
	for _, c := range cases {
		if isTypedConfirmation(c) {
			t.Errorf("isTypedConfirmation(%q) = true, want false (contracted negation should block)", c)
		}
	}
}

func TestIsTypedConfirmation(t *testing.T) {
	positive := []string{
		"I confirm destroy 135 in strong",
		"confirm",
		"Confirmed.",
		"yes I confirm",
	}
	for _, c := range positive {
		if !isTypedConfirmation(c) {
			t.Errorf("isTypedConfirmation(%q) = false, want true", c)
		}
	}
	negative := []string{
		"yes", "go ahead", "do it", "proceed", "lgtm", // loose assent must NOT satisfy this
		"no, don't confirm yet", "wait", "",
	}
	for _, c := range negative {
		if isTypedConfirmation(c) {
			t.Errorf("isTypedConfirmation(%q) = true, want false (only explicit confirm should pass)", c)
		}
	}
}

func TestExtractPendingApprovals(t *testing.T) {
	mkCall := func(text string) persistedCall {
		b, _ := json.Marshal(text)
		return persistedCall{id: "x", name: "run", result: json.RawMessage(b)}
	}
	calls := []persistedCall{
		mkCall("run on host:strong requires approval (risk: config_mutation) — execution 019f4930-e22b-7c47-8c6e-715dcd59df19 queued. Present the command..."),
		mkCall("some unrelated read-only result, no approval here"),
		mkCall("run on lxc:caddy requires approval (risk: destructive) — execution 019f4931-aaaa-7c47-8c6e-715dcd59df20 queued. This is classified DESTRUCTIVE — flag that clearly."),
	}
	got := extractPendingApprovals(calls)
	if len(got) != 2 {
		t.Fatalf("expected 2 pending approvals, got %d: %+v", len(got), got)
	}
	if got[0].execID != "019f4930-e22b-7c47-8c6e-715dcd59df19" || got[0].destructive {
		t.Errorf("first approval wrong: %+v", got[0])
	}
	if got[1].execID != "019f4931-aaaa-7c47-8c6e-715dcd59df20" || !got[1].destructive {
		t.Errorf("second approval should be flagged destructive: %+v", got[1])
	}
}

func TestExtractPendingApprovals_NoneWhenNoneQueued(t *testing.T) {
	b, _ := json.Marshal("fleet is healthy, nothing to report")
	calls := []persistedCall{{id: "x", result: json.RawMessage(b)}}
	if got := extractPendingApprovals(calls); len(got) != 0 {
		t.Errorf("expected no pending approvals, got %+v", got)
	}
}
