package cli

import (
	"context"
	"errors"
	"iop/apps/node/internal/runtime"
	"strings"
	"testing"
)

func TestDriveJSONLines_DispatchesEmitterEvents(t *testing.T) {
	input := `{"type":"message","role":"assistant","content":"hello"}
not-json
{"type":"message","role":"assistant","content":"world"}`
	outBuf := &strings.Builder{}
	sink := &testSink{}
	outReader := strings.NewReader(input + "\n")

	mockEmitter := &mockLineEmitter{
		name: "mock",
		emitFn: func(line string) ([]runtime.RuntimeEvent, error) {
			return []runtime.RuntimeEvent{
				{Type: runtime.EventTypeDelta, Delta: "a:" + line},
				{Type: runtime.EventTypeDelta, Delta: "b:" + line},
			}, nil
		},
	}

	outputTokens, err := driveJSONLines(context.Background(), outReader, sink, "run-1", outBuf, mockEmitter, 4*1024*1024)
	if err != nil {
		t.Fatalf("driveJSONLines: %v", err)
	}
	if got := len(sink.events); got != 4 {
		t.Fatalf("expected 4 events, got %d", got)
	}
	for i, ev := range sink.events {
		if ev.RunID != "run-1" {
			t.Errorf("event %d: RunID = %q, want %q", i, ev.RunID, "run-1")
		}
		if ev.Timestamp.IsZero() {
			t.Errorf("event %d: Timestamp is zero", i)
		}
		if ev.Type == runtime.EventTypeDelta {
			outputTokens += len(strings.Fields(ev.Delta))
		}
	}
	raw := outBuf.String()
	if !strings.Contains(raw, `{"type":"message"`) {
		t.Fatalf("outBuf missing JSON line: %q", raw)
	}
}

func TestDriveJSONLines_StopsOnEmitterError(t *testing.T) {
	input := `{"type":"text"}
{"type":"error"}`
	outBuf := &strings.Builder{}
	sink := &testSink{}
	outReader := strings.NewReader(input + "\n")

	callCount := 0
	mockEmitter := &mockLineEmitter{
		name: "mock",
		emitFn: func(line string) ([]runtime.RuntimeEvent, error) {
			callCount++
			if callCount == 2 {
				return nil, errors.New("emitter failure")
			}
			return []runtime.RuntimeEvent{{Type: runtime.EventTypeDelta, Delta: "ok"}}, nil
		},
	}

	_, err := driveJSONLines(context.Background(), outReader, sink, "run-2", outBuf, mockEmitter, 4*1024*1024)
	if err == nil {
		t.Fatal("expected error, got nil")
	}
	if err.Error() != "emitter failure" {
		t.Fatalf("unexpected error: %v", err)
	}
	if callCount != 2 {
		t.Fatalf("expected emitter called 2 times, got %d", callCount)
	}
}

func TestDriveJSONLines_AccumulatesRawOutput(t *testing.T) {
	lines := `{"type":"text","part":{"type":"text","text":"a"}}
{"type":"text","part":{"type":"text","text":"b"}}`
	outBuf := &strings.Builder{}
	sink := &testSink{}
	outReader := strings.NewReader(lines + "\n")

	outputTokens, err := driveJSONLines(context.Background(), outReader, sink, "run-int", outBuf, opencodeJSONEmitter{}, 4*1024*1024)
	if err != nil {
		t.Fatalf("driveJSONLines: %v", err)
	}
	raw := outBuf.String()
	if !strings.Contains(raw, `{"type":"text"`) {
		t.Fatalf("outBuf missing expected line: %q", raw)
	}
	if newlineCount := strings.Count(raw, "\n"); newlineCount != 2 {
		t.Fatalf("expected 2 newlines in outBuf, got %d", newlineCount)
	}
	if outputTokens != 2 {
		t.Fatalf("expected 2 outputTokens, got %d", outputTokens)
	}
}

func TestDriveJSONLines_ScannerBufferMax(t *testing.T) {
	longLine := strings.Repeat("x", 100) + `{"type":"text"}`
	outBuf := &strings.Builder{}
	sink := &testSink{}
	outReader := strings.NewReader(longLine + "\n")

	outputTokens, err := driveJSONLines(context.Background(), outReader, sink, "run-buf", outBuf, &mockLineEmitter{name: "buf"}, 50)
	if err != nil {
		t.Fatalf("driveJSONLines: %v", err)
	}
	if outputTokens != 0 {
		t.Fatalf("expected 0 outputTokens for line too long for buffer, got %d", outputTokens)
	}
}

func TestDriveJSONLines_SkipsEmptyAndNonJSONLines(t *testing.T) {
	input := "\n\nnot json at all\n   \n{\"type\":\"text\"}\n"
	outBuf := &strings.Builder{}
	sink := &testSink{}
	outReader := strings.NewReader(input)

	callCount := 0
	mockEmitter := &mockLineEmitter{
		name: "mock",
		emitFn: func(line string) ([]runtime.RuntimeEvent, error) {
			callCount++
			return []runtime.RuntimeEvent{{Type: runtime.EventTypeDelta, Delta: line}}, nil
		},
	}

	_, err := driveJSONLines(context.Background(), outReader, sink, "run-skip", outBuf, mockEmitter, 4*1024*1024)
	if err != nil {
		t.Fatalf("driveJSONLines: %v", err)
	}
	if callCount != 1 {
		t.Fatalf("expected emitter called 1 time, got %d", callCount)
	}
}

func TestDriveJSONLines_OutputTokensCountedForDeltaOnly(t *testing.T) {
	outBuf := &strings.Builder{}
	sink := &testSink{}
	outReader := strings.NewReader(`{"type":"error"}
{"type":"delta"}` + "\n")

	mockEmitter := &mockLineEmitter{
		name: "mock",
		emitFn: func(line string) ([]runtime.RuntimeEvent, error) {
			if strings.Contains(line, "error") {
				return []runtime.RuntimeEvent{{Type: runtime.EventTypeError, Error: "bad"}}, nil
			}
			return []runtime.RuntimeEvent{{Type: runtime.EventTypeDelta, Delta: "one two three"}}, nil
		},
	}

	outputTokens, err := driveJSONLines(context.Background(), outReader, sink, "run-tokens", outBuf, mockEmitter, 4*1024*1024)
	if err != nil {
		t.Fatalf("driveJSONLines: %v", err)
	}
	if outputTokens != 3 {
		t.Fatalf("expected 3 outputTokens, got %d", outputTokens)
	}
}

func TestStreamJSONEmitter_SkipsNonAssistantRoles(t *testing.T) {
	e := streamJSONEmitter{}
	events, _ := e.Emit(`{"type":"message","role":"user","content":"hi"}`)
	if len(events) != 0 {
		t.Fatalf("expected 0 events for user role, got %d", len(events))
	}
}

func TestStreamJSONEmitter_ErrorEvent(t *testing.T) {
	e := streamJSONEmitter{}
	events, _ := e.Emit(`{"type":"error","error":"something broke"}`)
	if len(events) != 1 || events[0].Type != runtime.EventTypeError || events[0].Error != "something broke" {
		t.Fatalf("unexpected events: %+v", events)
	}
}

func TestStreamJSONEmitter_ToolUseBecomesVisibleDelta(t *testing.T) {
	e := streamJSONEmitter{}
	events, _ := e.Emit(`{"type":"tool_use","tool_name":"run_shell_command","tool_id":"tool-1","parameters":{"command":"go test ./..."}}`)
	if len(events) != 1 || events[0].Type != runtime.EventTypeDelta {
		t.Fatalf("unexpected events: %+v", events)
	}
	if events[0].Delta != "[tool] run_shell_command: go test ./...\n" {
		t.Fatalf("unexpected delta: %q", events[0].Delta)
	}
	if events[0].Metadata["kind"] != "tool_use" || events[0].Metadata["tool_name"] != "run_shell_command" {
		t.Fatalf("unexpected metadata: %+v", events[0].Metadata)
	}
}

func TestStreamJSONEmitter_ToolResultBecomesVisibleDelta(t *testing.T) {
	e := streamJSONEmitter{}
	events, _ := e.Emit(`{"type":"tool_result","tool_id":"tool-1","status":"success","output":"ok\n"}`)
	if len(events) != 1 || events[0].Type != runtime.EventTypeDelta {
		t.Fatalf("unexpected events: %+v", events)
	}
	if events[0].Delta != "[tool-result] success: ok\n" {
		t.Fatalf("unexpected delta: %q", events[0].Delta)
	}
	if events[0].Metadata["kind"] != "tool_result" {
		t.Fatalf("unexpected metadata: %+v", events[0].Metadata)
	}
}

func TestStreamJSONEmitter_ResultError(t *testing.T) {
	e := streamJSONEmitter{}
	events, _ := e.Emit(`{"type":"result","status":"error","error":{"message":"quota exceeded"}}`)
	if len(events) != 1 || events[0].Type != runtime.EventTypeError || events[0].Error != "quota exceeded" {
		t.Fatalf("unexpected events: %+v", events)
	}
}

func TestStreamJSONEmitter_EmptyContentSkipped(t *testing.T) {
	e := streamJSONEmitter{}
	events, _ := e.Emit(`{"type":"message","role":"assistant","content":""}`)
	if len(events) != 0 {
		t.Fatalf("expected 0 events for empty content, got %d", len(events))
	}
}

func TestClaudeJSONEmitter_ErrorResult(t *testing.T) {
	e := claudeJSONEmitter{}
	events, _ := e.Emit(`{"type":"result","is_error":true,"result":"API timeout"}`)
	if len(events) != 1 || events[0].Type != runtime.EventTypeError || events[0].Error != "API timeout" {
		t.Fatalf("unexpected events: %+v", events)
	}
}

func TestClaudeJSONEmitter_NonTextDeltaSkipped(t *testing.T) {
	e := claudeJSONEmitter{}
	events, _ := e.Emit(`{"type":"stream_event","event":{"type":"content_block_delta","delta":{"type":"image_delta","data":"base64"}}}`)
	if len(events) != 0 {
		t.Fatalf("expected 0 events for non-text delta, got %d", len(events))
	}
}

func TestCodexJSONEmitter_ItemDeltaBecomesDelta(t *testing.T) {
	e := codexJSONEmitter{}
	events, _ := e.Emit(`{"type":"item.delta","item":{"type":"agent_message","delta":"Doing."}}`)
	if len(events) != 1 || events[0].Delta != "Doing." {
		t.Fatalf("unexpected events: %+v", events)
	}
}

func TestCodexJSONEmitter_OutputTextDeltaBecomesDelta(t *testing.T) {
	e := codexJSONEmitter{}
	events, _ := e.Emit(`{"type":"response.output_text.delta","delta":"chunk"}`)
	if len(events) != 1 || events[0].Delta != "chunk" {
		t.Fatalf("unexpected events: %+v", events)
	}
}

func TestCodexJSONEmitter_ContentOutputTextFallsBackToDelta(t *testing.T) {
	e := codexJSONEmitter{}
	events, _ := e.Emit(`{"type":"item.updated","item":{"type":"agent_message","message":{"content":[{"type":"output_text","text":"partial text"}]}}}`)
	if len(events) != 1 || events[0].Delta != "partial text" {
		t.Fatalf("unexpected events: %+v", events)
	}
}

func TestCodexJSONEmitter_TurnFailedBecomesError(t *testing.T) {
	e := codexJSONEmitter{}
	events, _ := e.Emit(`{"type":"turn.failed","error":{"message":"quota exceeded"}}`)
	if len(events) != 1 || events[0].Type != runtime.EventTypeError || events[0].Error != "quota exceeded" {
		t.Fatalf("unexpected events: %+v", events)
	}
}

func TestCodexJSONEmitter_StandardErrorEvent(t *testing.T) {
	e := codexJSONEmitter{}
	events, _ := e.Emit(`{"type":"error","message":"network timeout"}`)
	if len(events) != 1 || events[0].Error != "network timeout" {
		t.Fatalf("unexpected events: %+v", events)
	}
}

func TestCodexJSONEmitter_NonAgentMessageSkipped(t *testing.T) {
	e := codexJSONEmitter{}
	events, _ := e.Emit(`{"type":"item.completed","item":{"type":"tool_call","text":"ls -la"}}`)
	if len(events) != 0 {
		t.Fatalf("expected 0 events for non-agent_message item, got %d", len(events))
	}
}

func TestClineJSONEmitter_CompletionError(t *testing.T) {
	e := clineJSONEmitter{}
	events, _ := e.Emit(`{"type":"completion","status":"error","error":"task failed"}`)
	if len(events) != 1 || events[0].Error != "task failed" {
		t.Fatalf("unexpected events: %+v", events)
	}
}

func TestClineJSONEmitter_SayErrorWithFallback(t *testing.T) {
	e := clineJSONEmitter{}
	events, _ := e.Emit(`{"type":"say","say":"error","message":"fallback msg"}`)
	if len(events) != 1 || events[0].Error != "fallback msg" {
		t.Fatalf("unexpected events: %+v", events)
	}
}

func TestClineJSONEmitter_CompletionErrorWithoutErrorField(t *testing.T) {
	e := clineJSONEmitter{}
	events, _ := e.Emit(`{"type":"completion","status":"error"}`)
	if len(events) != 1 || events[0].Error != "cline task failed" {
		t.Fatalf("unexpected events: %+v", events)
	}
}

func TestEmitters_HaveDistinctNames(t *testing.T) {
	testCases := []struct {
		emitter lineEmitter
		want    string
	}{
		{streamJSONEmitter{}, "stream-json"},
		{claudeJSONEmitter{}, "claude-json"},
		{codexJSONEmitter{}, "codex-json"},
		{opencodeJSONEmitter{}, "opencode-json"},
		{clineJSONEmitter{}, "cline-json"},
	}
	for _, tc := range testCases {
		if got := tc.emitter.Name(); got != tc.want {
			t.Errorf("%T.Name() = %q, want %q", tc.emitter, got, tc.want)
		}
	}
}

func TestJsonEmitters_RegistryMatchesImpls(t *testing.T) {
	expectedKeys := []string{"stream-json", "claude-json", "codex-json", "opencode-json", "cline-json"}
	for _, key := range expectedKeys {
		reg, ok := jsonEmitters[key]
		if !ok {
			t.Fatalf("jsonEmitters[%q] not found in registry", key)
		}
		if reg.emitter.Name() != key {
			t.Errorf("jsonEmitters[%q].emitter.Name() = %q, want %q", key, reg.emitter.Name(), key)
		}
		if reg.scanBufMax < 1024 {
			t.Errorf("jsonEmitters[%q].scanBufMax = %d, expected at least 1024", key, reg.scanBufMax)
		}
	}
	if len(jsonEmitters) != len(expectedKeys) {
		t.Errorf("expected %d registered emitters, got %d", len(expectedKeys), len(jsonEmitters))
	}
}
