package cli

import (
	"encoding/json"
	"testing"
)

func TestParseOpencodeRunArgs(t *testing.T) {
	args := []string{
		"--attach", "http://127.0.0.1:9876",
		"--model", "ollama-dgx/qwen3.6:35b-a3b-bf16",
		"--agent", "build",
		"--variant", "fast",
		"--title", "demo",
		"--dir", "/work",
		"--dangerously-skip-permissions",
	}
	opts := parseOpencodeRunArgs(args)
	if opts.AttachURL != "http://127.0.0.1:9876" {
		t.Errorf("attach: got %q", opts.AttachURL)
	}
	if opts.Model != "ollama-dgx/qwen3.6:35b-a3b-bf16" {
		t.Errorf("model: got %q", opts.Model)
	}
	if opts.Agent != "build" {
		t.Errorf("agent: got %q", opts.Agent)
	}
	if opts.Variant != "fast" {
		t.Errorf("variant: got %q", opts.Variant)
	}
	if opts.Title != "demo" {
		t.Errorf("title: got %q", opts.Title)
	}
	if opts.Dir != "/work" {
		t.Errorf("dir: got %q", opts.Dir)
	}
	if !opts.DangerouslySkipPermissions {
		t.Error("dangerously-skip-permissions should be true")
	}
}

func TestParseOpencodeRunArgs_NoAttach(t *testing.T) {
	args := []string{"--title", "untitled", "--model", "m"}
	opts := parseOpencodeRunArgs(args)
	if opts.AttachURL != "" {
		t.Errorf("expected empty attach, got %q", opts.AttachURL)
	}
	if opts.Title != "untitled" || opts.Model != "m" {
		t.Errorf("unexpected: %+v", opts)
	}
}

func TestOpencodeModelPayload(t *testing.T) {
	cases := []struct {
		in       string
		wantProv string
		wantMod  string
		wantErr  bool
	}{
		{"ollama-dgx/qwen3.6:35b-a3b-bf16", "ollama-dgx", "qwen3.6:35b-a3b-bf16", false},
		{"anthropic/claude-sonnet-4-6", "anthropic", "claude-sonnet-4-6", false},
		{"openrouter/google/gemini-2.5-pro", "openrouter", "google/gemini-2.5-pro", false},
		{"missing-slash", "", "", true},
		{"/missing-provider", "", "", true},
		{"missing-model/", "", "", true},
	}
	for _, tc := range cases {
		got, err := opencodeModelPayload(tc.in)
		if tc.wantErr {
			if err == nil {
				t.Errorf("opencodeModelPayload(%q): expected error, got nil", tc.in)
			}
			continue
		}
		if err != nil {
			t.Errorf("opencodeModelPayload(%q): unexpected error %v", tc.in, err)
			continue
		}
		if got["providerID"] != tc.wantProv {
			t.Errorf("opencodeModelPayload(%q) providerID = %v, want %q", tc.in, got["providerID"], tc.wantProv)
		}
		if got["modelID"] != tc.wantMod {
			t.Errorf("opencodeModelPayload(%q) modelID = %v, want %q", tc.in, got["modelID"], tc.wantMod)
		}
	}
}

func TestOpencodeStatusIdle(t *testing.T) {
	cases := []struct {
		name  string
		props map[string]any
		want  bool
	}{
		{"string idle", map[string]any{"status": "idle"}, true},
		{"object idle", map[string]any{"status": map[string]any{"type": "idle"}}, true},
		{"object busy", map[string]any{"status": map[string]any{"type": "busy"}}, false},
		{"string busy", map[string]any{"status": "busy"}, false},
		{"missing", map[string]any{}, false},
		{"non-map number", map[string]any{"status": 1.0}, false},
		{"nil props", nil, false},
	}
	for _, tc := range cases {
		if got := opencodeStatusIdle(tc.props); got != tc.want {
			t.Errorf("opencodeStatusIdle(%s) = %v, want %v", tc.name, got, tc.want)
		}
	}
}

func TestOpencodeEventEnvelope(t *testing.T) {
	cases := []struct {
		name string
		in   string
		want string
		ok   bool
	}{
		{
			name: "plain event",
			in:   `{"id":"evt_1","type":"session.idle","properties":{"sessionID":"ses_1"}}`,
			want: "session.idle",
			ok:   true,
		},
		{
			name: "global payload event",
			in:   `{"directory":"/work","payload":{"id":"evt_1","type":"message.part.delta","properties":{"sessionID":"ses_1"}}}`,
			want: "message.part.delta",
			ok:   true,
		},
		{
			name: "sync event ignored",
			in:   `{"directory":"/work","payload":{"type":"sync","syncEvent":{"type":"message.part.delta.1"}}}`,
			ok:   false,
		},
	}
	for _, tc := range cases {
		got, ok := opencodeEventEnvelope([]byte(tc.in))
		if ok != tc.ok {
			t.Fatalf("%s: ok=%v, want %v", tc.name, ok, tc.ok)
		}
		if ok && got.Type != tc.want {
			t.Fatalf("%s: type=%q, want %q", tc.name, got.Type, tc.want)
		}
	}
}

func TestOpencodeMessagePartDeltaText(t *testing.T) {
	partTypes := map[string]string{
		"prt_text":      "text",
		"prt_reasoning": "reasoning",
	}
	cases := []struct {
		name  string
		props map[string]any
		want  string
	}{
		{"text part", map[string]any{"partID": "prt_text", "field": "text", "delta": "OK"}, "OK"},
		{"reasoning part ignored", map[string]any{"partID": "prt_reasoning", "field": "text", "delta": "thinking"}, ""},
		// Unknown part: message.part.updated may have been missed on a new SSE
		// connection; allow the delta rather than silently dropping it.
		{"unknown part allowed when field=text", map[string]any{"partID": "prt_missing", "field": "text", "delta": "allowed"}, "allowed"},
		{"non text field ignored", map[string]any{"partID": "prt_text", "field": "metadata", "delta": "lost"}, ""},
		{"empty partID field=text allowed", map[string]any{"partID": "", "field": "text", "delta": "bare"}, "bare"},
	}
	for _, tc := range cases {
		if got := opencodeMessagePartDeltaText(tc.props, partTypes); got != tc.want {
			t.Fatalf("%s: got %q, want %q", tc.name, got, tc.want)
		}
	}
}

func TestOpencodeStepTokensFromPart(t *testing.T) {
	var props map[string]any
	if err := json.Unmarshal([]byte(`{"part":{"type":"step-finish","tokens":{"input":7,"output":1}}}`), &props); err != nil {
		t.Fatal(err)
	}
	in, out, ok := opencodeStepTokens(props)
	if !ok || in != 7 || out != 1 {
		t.Fatalf("tokens: got %d/%d ok=%v, want 7/1 true", in, out, ok)
	}
}

func TestDecodeSSEDataLine(t *testing.T) {
	cases := []struct {
		in        string
		want      string
		shouldGet bool
	}{
		{"data: {\"a\":1}", `{"a":1}`, true},
		{"data:{\"a\":1}", `{"a":1}`, true},
		{"event: ping", "", false},
		{": comment", "", false},
		{"", "", false},
		{"data:", "", false},
		{"data:   ", "", false},
	}
	for _, tc := range cases {
		got, ok := decodeSSEDataLine([]byte(tc.in))
		if ok != tc.shouldGet {
			t.Errorf("decodeSSEDataLine(%q) ok=%v, want %v", tc.in, ok, tc.shouldGet)
			continue
		}
		if ok && string(got) != tc.want {
			t.Errorf("decodeSSEDataLine(%q) = %q, want %q", tc.in, string(got), tc.want)
		}
	}
}
