package app

import (
	"encoding/json"
	"strings"
	"testing"

	"github.com/correx/tui-go/internal/protocol"
	"github.com/correx/tui-go/internal/ws"
)

func proposeModel() (Model, *ws.Client) {
	client := ws.New("", 0) // unconnected; Send just buffers, Drain reads it back
	m := NewModel(client)
	m.width, m.height = 120, 40
	m.theme = NewTheme(SoftBlue)
	m.selectedID = "s1"
	m.sessionEntered = true
	m.applyServer(protocol.ServerMessage{
		Type:            protocol.TypeWorkflowProposed,
		SessionID:       "s1",
		ProposalID:      "prop-1",
		Prompt:          "Run one of these?",
		OriginalRequest: "find papers on event sourcing",
		Candidates: []protocol.ProposedWorkflowDto{
			{WorkflowID: "research", Reason: "gather + report"},
			{WorkflowID: "role_pipeline", Reason: "full build"},
		},
	})
	return m, client
}

func TestProposeEntersStateAndRenders(t *testing.T) {
	m, _ := proposeModel()
	if m.displayState() != StateWorkflowPropose {
		t.Fatalf("want StateWorkflowPropose, got %v", m.displayState())
	}
	out := m.proposeModal()
	for _, want := range []string{"workflow suggestion", "Run one of these?", "research", "gather + report", "role_pipeline", "something else"} {
		if !strings.Contains(out, want) {
			t.Fatalf("modal missing %q:\n%s", want, out)
		}
	}
}

func TestProposePickLaunchesChosenWorkflow(t *testing.T) {
	m, client := proposeModel()
	// move cursor to the second candidate (role_pipeline) and launch it
	m = applyProposeKey(m, keyMsg{Type: keyDown})
	m = applyProposeKey(m, keyMsg{Type: keyEnter})

	if s := m.session("s1"); s == nil || s.Propose != nil {
		t.Fatalf("expected proposal cleared after launch")
	}
	if !m.pendingWorkflowFocus {
		t.Fatalf("expected pendingWorkflowFocus set so the launched session is focused")
	}
	wf, input := decodeStart(t, client)
	if wf != "role_pipeline" {
		t.Fatalf("launched wrong workflow: %q", wf)
	}
	if input != "find papers on event sourcing" {
		t.Fatalf("workflow not seeded with original request: %q", input)
	}
}

func TestProposeFirstCandidateIsDefault(t *testing.T) {
	m, client := proposeModel()
	m = applyProposeKey(m, keyMsg{Type: keyEnter}) // no nav → first candidate
	wf, _ := decodeStart(t, client)
	if wf != "research" {
		t.Fatalf("expected first candidate launched by default, got %q", wf)
	}
}

func TestProposeCustomAnswerContinuesChat(t *testing.T) {
	m, client := proposeModel()
	m = applyProposeKey(m, keyMsg{Type: keyRunes, Runes: []rune("e")})
	if !m.proposeTyping {
		t.Fatalf("expected typing mode after 'e'")
	}
	for _, r := range "do it manually" {
		m = applyProposeKey(m, keyMsg{Type: keyRunes, Runes: []rune{r}})
	}
	m = applyProposeKey(m, keyMsg{Type: keyEnter})

	if s := m.session("s1"); s == nil || s.Propose != nil {
		t.Fatalf("expected proposal cleared after custom answer")
	}
	if m.pendingWorkflowFocus {
		t.Fatalf("a custom answer must not launch a workflow")
	}
	frames := client.Drain()
	if len(frames) != 1 {
		t.Fatalf("expected exactly one frame, got %d", len(frames))
	}
	var raw map[string]any
	if err := json.Unmarshal(frames[0], &raw); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if raw["type"] != "com.correx.apps.server.protocol.ClientMessage.ChatInput" {
		t.Fatalf("expected ChatInput, got %v", raw["type"])
	}
	if raw["text"] != "do it manually" {
		t.Fatalf("custom text not sent: %v", raw["text"])
	}
}

func TestProposeEscDismisses(t *testing.T) {
	m, _ := proposeModel()
	m = applyProposeKey(m, keyMsg{Type: keyEsc})
	if m.displayState() != StateInSession {
		t.Fatalf("expected StateInSession after esc, got %v", m.displayState())
	}
	if s := m.session("s1"); s == nil || s.Propose == nil {
		t.Fatalf("esc should peek away, not discard the proposal")
	}
}

func applyProposeKey(m Model, k keyMsg) Model {
	updated, _ := m.handleProposeKey(k)
	return updated.(Model)
}

func decodeStart(t *testing.T, client *ws.Client) (workflowID, input string) {
	t.Helper()
	frames := client.Drain()
	if len(frames) != 1 {
		t.Fatalf("expected exactly one frame, got %d", len(frames))
	}
	var raw map[string]any
	if err := json.Unmarshal(frames[0], &raw); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if raw["type"] != "com.correx.apps.server.protocol.ClientMessage.StartSession" {
		t.Fatalf("expected StartSession, got %v", raw["type"])
	}
	wf, _ := raw["workflowId"].(string)
	in, _ := raw["input"].(string)
	return wf, in
}
