package httpapi

import (
	"encoding/json"
	"testing"
)

// TestFlexBoolUnmarshal covers the exact production failure: the LLM emitted
// `"privileged":0` / `"nesting":1` (numbers) and the strict bool field made the
// already-approved pct_create execution fail to parse, so the LXC was never
// created.
func TestFlexBoolUnmarshal(t *testing.T) {
	type cfg struct {
		Privileged flexBool `json:"privileged"`
		Nesting    flexBool `json:"nesting"`
	}
	cases := []struct {
		in         string
		privileged bool
		nesting    bool
		wantErr    bool
	}{
		{`{"privileged":0,"nesting":1}`, false, true, false},        // the prod payload
		{`{"privileged":false,"nesting":true}`, false, true, false}, // canonical
		{`{"privileged":"1","nesting":"0"}`, true, false, false},    // stringified
		{`{"privileged":"true","nesting":"no"}`, true, false, false},
		{`{}`, false, false, false}, // absent → zero
		{`{"privileged":"maybe"}`, false, false, true},
	}
	for _, c := range cases {
		var out cfg
		err := json.Unmarshal([]byte(c.in), &out)
		if (err != nil) != c.wantErr {
			t.Fatalf("%s: err=%v wantErr=%v", c.in, err, c.wantErr)
		}
		if c.wantErr {
			continue
		}
		if bool(out.Privileged) != c.privileged || bool(out.Nesting) != c.nesting {
			t.Errorf("%s: got priv=%v nest=%v want priv=%v nest=%v",
				c.in, bool(out.Privileged), bool(out.Nesting), c.privileged, c.nesting)
		}
	}
}

func TestResolveTemplate(t *testing.T) {
	avail := []string{
		"debian-12-standard_12.7-1_amd64.tar.zst",
		"debian-13-standard_13.0-1_amd64.tar.zst",
		"ubuntu-24.04-standard_24.04-2_amd64.tar.zst",
	}
	cases := []struct {
		requested string
		want      string
	}{
		{"debian-13-standard_13.0-1_amd64.tar.zst", "debian-13-standard_13.0-1_amd64.tar.zst"}, // exact
		{"debian-13", "debian-13-standard_13.0-1_amd64.tar.zst"},                               // prefix
		{"", "debian-13-standard_13.0-1_amd64.tar.zst"},                                        // auto newest debian
		{"debian-99", "debian-13-standard_13.0-1_amd64.tar.zst"},                               // miss prefix → auto debian
	}
	for _, c := range cases {
		if got := resolveTemplate(c.requested, avail); got != c.want {
			t.Errorf("resolveTemplate(%q): got %q want %q", c.requested, got, c.want)
		}
	}
	if got := resolveTemplate("debian-13", nil); got != "" {
		t.Errorf("empty cache should yield empty, got %q", got)
	}
}

// TestJSONErrValidForNastyOutput guards the bug where command output with
// quotes/backslashes/newlines produced invalid JSON, failing the ::jsonb cast
// and silently dropping the execution's final status update.
func TestJSONErrValidForNastyOutput(t *testing.T) {
	nasty := "CT 132 already exists on node \"hubris\"\n\tpath C:\\x\r\n\x00 100%"
	for _, payload := range [][]byte{
		jsonErr("%s", nasty),
		jsonErr("list templates on %s: %s", "host:strong", nasty),
	} {
		var m map[string]any
		if err := json.Unmarshal(payload, &m); err != nil {
			t.Fatalf("jsonErr produced invalid JSON: %v\npayload=%s", err, payload)
		}
		if _, ok := m["error"]; !ok {
			t.Errorf("missing error key: %s", payload)
		}
	}
}

// TestGatewayPreflightPassed guards the exact bug found live: "UNREACHABLE"
// contains "REACHABLE" as a substring, so a strings.Contains(out,"REACHABLE")
// check is true for BOTH outcomes and can never fail. Exact-match only.
func TestGatewayPreflightPassed(t *testing.T) {
	cases := []struct {
		out  string
		want bool
	}{
		{"PREFLIGHT_OK", true},
		{"PREFLIGHT_OK\n", true},
		{"  PREFLIGHT_OK  ", true},
		{"PREFLIGHT_FAIL", false},
		{"PREFLIGHT_FAIL\n", false},
		{"", false},
		{"some garbage output", false},
		// the specific historical bug: a naive substring check on the old
		// REACHABLE/UNREACHABLE markers would have called this true.
		{"UNREACHABLE", false},
	}
	for _, c := range cases {
		if got := gatewayPreflightPassed(c.out); got != c.want {
			t.Errorf("gatewayPreflightPassed(%q) = %v, want %v", c.out, got, c.want)
		}
	}
}

// provisionScript and sanitizePkgs were removed when pct_create was made
// atomic (create + start + register only) — installing packages and running
// setup scripts is now the agent's own job via follow-up `run` calls, which
// already has its own classifier/sanitization tests in internal/policy.
