package tool

import (
	"context"
	"errors"
	"testing"

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

func TestRiskOfReadsTheRow(t *testing.T) {
	cases := []struct {
		name string
		tool ipc.Tool
		want Risk
	}{
		{"a plain read", ipc.Tool{Cmd: []string{"systemctl", "status"}}, TierSafe},
		{"the checkbox", ipc.Tool{Cmd: []string{"systemctl", "restart"}, Destructive: true}, TierDestructive},
		{"a wipe", ipc.Tool{Cmd: []string{"rm", "-rf"}}, TierIrreversible},
		{"a wipe behind a wrapper", ipc.Tool{Cmd: []string{"sudo", "/bin/rm"}}, TierIrreversible},
		{"a prune behind a subcommand", ipc.Tool{Cmd: []string{"docker", "volume", "prune"}}, TierIrreversible},
		{"the house", ipc.Tool{Cmd: []string{"smarthome", "light.kitchen", "turn_off"}}, TierDestructive},
		{"the house with the box unticked", ipc.Tool{Cmd: []string{"smarthome", "lock.front", "unlock"}}, TierDestructive},
		{"an mcp read", ipc.Tool{Cmd: []string{"mcp", "vikunja", "list_tasks"}}, TierSafe},
		{"an mcp write", ipc.Tool{Cmd: []string{"mcp", "vikunja", "delete_task"}, Destructive: true}, TierDestructive},
		{"a shape nobody wrote yet", ipc.Tool{}, TierDestructive},
	}
	for _, c := range cases {
		if got := RiskOf(c.tool); got != c.want {
			t.Errorf("%s: RiskOf = %q; want %q", c.name, got, c.want)
		}
	}
}

// The default is the confirm turn. A tier this file does not know is not a
// tier that runs freely.
func TestPolicyForDefaultsToConfirming(t *testing.T) {
	for _, r := range []Risk{TierDestructive, Risk("whatever-lands-here-next")} {
		p := PolicyFor(r)
		if !p.Confirm || !p.VoiceMayRun {
			t.Errorf("PolicyFor(%q) = %+v; want a confirm turn she may run", r, p)
		}
	}
	if p := PolicyFor(TierSafe); p.Confirm || !p.VoiceMayRun {
		t.Errorf("PolicyFor(safe) = %+v; want it to run", p)
	}
	if p := PolicyFor(TierIrreversible); !p.Confirm || p.VoiceMayRun {
		t.Errorf("PolicyFor(irreversible) = %+v; want voice refused", p)
	}
}

// An irreversible act is refused whether or not he said "да", because there is
// no second answer that changes what it would do.
func TestExecRefusesIrreversibleEvenConfirmed(t *testing.T) {
	api := fakeAPI{tools: map[string]ipc.Tool{
		"wipe": {Name: "wipe", Status: "enabled", Cmd: []string{"rm", "-rf"}, Destructive: true},
	}}
	e := NewExecutor(api, 0)
	ran := false
	e.run = func(context.Context, []string) (string, error) { ran = true; return "", nil }
	for _, confirmed := range []bool{false, true} {
		if _, err := e.Exec(context.Background(), "wipe", []string{"/data"}, confirmed); !errors.Is(err, ErrNeedsAuthedSurface) {
			t.Errorf("confirmed=%v: %v; want ErrNeedsAuthedSurface", confirmed, err)
		}
	}
	if ran {
		t.Fatal("an irreversible act ran from the voice path")
	}
}

// A row with no cmd at all is not a shape this file reads, and it must not
// slide through as safe.
func TestExecConfirmsAnUnreadableRow(t *testing.T) {
	api := fakeAPI{tools: map[string]ipc.Tool{
		"mystery": {Name: "mystery", Status: "enabled"},
	}}
	e := NewExecutor(api, 0)
	if _, err := e.Exec(context.Background(), "mystery", nil, false); !errors.Is(err, ErrNeedsConfirm) {
		t.Errorf("%v; want ErrNeedsConfirm", err)
	}
}

// Hexis owns the tier of a Hexis capability, so this reads rather than derives
// (Vikunja #523). The cases that matter are the ones where the three fields
// disagree, or where the tier is a word this package has never seen: every one
// of those goes up to a confirm, never down to running freely.
func TestRiskOfCapabilityReadsHexis(t *testing.T) {
	for _, c := range []struct {
		name    string
		risk    string
		ro      bool
		confirm bool
		want    Risk
	}{
		{"hexis says irreversible", "irreversible", false, true, TierIrreversible},
		{"case and space do not change the tier", "  Irreversible ", false, true, TierIrreversible},
		{"hexis says destructive", "destructive", false, true, TierDestructive},
		{"a read hexis calls safe", "safe", true, false, TierSafe},
		{"safe but mutating is a contradiction", "safe", false, false, TierDestructive},
		{"safe but wants a confirm is a contradiction", "safe", true, true, TierDestructive},
		{"no tier, read-only, no confirm", "", true, false, TierSafe},
		{"no tier and mutating", "", false, false, TierDestructive},
		{"no tier but hexis wants a confirm", "", true, true, TierDestructive},
		{"a word we have never seen", "spicy", true, false, TierDestructive},
	} {
		if got := RiskOfCapability(c.risk, c.ro, c.confirm); got != c.want {
			t.Errorf("%s: RiskOfCapability(%q, ro=%v, confirm=%v) = %q; want %q",
				c.name, c.risk, c.ro, c.confirm, got, c.want)
		}
	}
}
