package cli

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"strings"
	"time"

	"iop/apps/node/internal/runtime"
)

// lineEmitter parses one stdout line (already trimmed of trailing newline) and
// returns the resulting RuntimeEvents to push to the sink. Empty events are
// skipped. Returning a non-nil error aborts the run.
type lineEmitter interface {
	Name() string
	Emit(line string) ([]runtime.RuntimeEvent, error)
}

// registeredEmitter wraps a lineEmitter with its maximum scanner buffer size.
type registeredEmitter struct {
	emitter    lineEmitter
	scanBufMax int
}

// jsonEmitters maps OutputFormat names to their registered emitter + buffer config.
var jsonEmitters = map[string]registeredEmitter{
	"stream-json":   {emitter: streamJSONEmitter{}, scanBufMax: 4 * 1024 * 1024},
	"claude-json":   {emitter: claudeJSONEmitter{}, scanBufMax: 8 * 1024 * 1024},
	"codex-json":    {emitter: codexJSONEmitter{}, scanBufMax: 4 * 1024 * 1024},
	"opencode-json": {emitter: opencodeJSONEmitter{}, scanBufMax: 4 * 1024 * 1024},
	"cline-json":    {emitter: clineJSONEmitter{}, scanBufMax: 8 * 1024 * 1024},
}

// driveJSONLines runs a shared scanner loop over stdout, accumulates raw output
// into outBuf, dispatches each line through the emitter, and forwards events
// to sink. Returns total OutputTokens approximation.
func driveJSONLines(
	ctx context.Context,
	stdout io.Reader,
	sink runtime.EventSink,
	runID string,
	outBuf *strings.Builder,
	emitter lineEmitter,
	scanBufMax int,
) (int, error) {
	scanner := bufio.NewScanner(stdout)
	scanner.Buffer(make([]byte, 64*1024), scanBufMax)
	outputTokens := 0

	for scanner.Scan() {
		line := scanner.Bytes()
		outBuf.Write(line)
		outBuf.WriteByte('\n')

		trimmed := strings.TrimSpace(string(line))
		if trimmed == "" || trimmed[0] != '{' {
			continue
		}

		events, err := emitter.Emit(trimmed)
		if err != nil {
			return outputTokens, err
		}

		for _, ev := range events {
			ev.RunID = runID
			if ev.Timestamp.IsZero() {
				ev.Timestamp = time.Now()
			}
			if ev.Type == runtime.EventTypeDelta {
				outputTokens += len(strings.Fields(ev.Delta))
			}
			_ = sink.Emit(ctx, ev)
		}
	}

	if err := scanner.Err(); err != nil {
		return outputTokens, err
	}
	return outputTokens, nil
}

// --- stream-json emitter ---

type streamJSONEmitter struct{}

func (streamJSONEmitter) Name() string { return "stream-json" }

func (e streamJSONEmitter) Emit(line string) ([]runtime.RuntimeEvent, error) {
	var ev struct {
		Type       string         `json:"type"`
		Role       string         `json:"role"`
		Content    string         `json:"content"`
		Error      any            `json:"error"`
		Message    string         `json:"message"`
		Status     string         `json:"status"`
		ToolName   string         `json:"tool_name"`
		ToolID     string         `json:"tool_id"`
		Parameters map[string]any `json:"parameters"`
		Output     string         `json:"output"`
	}
	if err := json.Unmarshal([]byte(line), &ev); err != nil {
		return nil, nil
	}
	switch ev.Type {
	case "message":
		if ev.Role != "assistant" || ev.Content == "" {
			return nil, nil
		}
		return []runtime.RuntimeEvent{{
			Type:  runtime.EventTypeDelta,
			Delta: ev.Content,
		}}, nil
	case "tool_use":
		if delta := streamToolUseDelta(ev.ToolName, ev.Parameters); delta != "" {
			return []runtime.RuntimeEvent{{
				Type:  runtime.EventTypeDelta,
				Delta: delta,
				Metadata: map[string]string{
					"kind":      "tool_use",
					"tool_name": ev.ToolName,
					"tool_id":   ev.ToolID,
				},
			}}, nil
		}
		return nil, nil
	case "tool_result":
		if delta := streamToolResultDelta(ev.Status, ev.Output); delta != "" {
			return []runtime.RuntimeEvent{{
				Type:  runtime.EventTypeDelta,
				Delta: delta,
				Metadata: map[string]string{
					"kind":    "tool_result",
					"tool_id": ev.ToolID,
				},
			}}, nil
		}
		return nil, nil
	case "result":
		if ev.Status != "" && ev.Status != "success" {
			msg := ev.Message
			if msg == "" {
				msg = streamErrorString(ev.Error)
			}
			if msg == "" {
				msg = ev.Status
			}
			return []runtime.RuntimeEvent{{
				Type:  runtime.EventTypeError,
				Error: msg,
			}}, nil
		}
		return nil, nil
	case "error":
		return []runtime.RuntimeEvent{{
			Type:  runtime.EventTypeError,
			Error: streamErrorString(ev.Error),
		}}, nil
	}
	return nil, nil
}

func streamToolUseDelta(toolName string, params map[string]any) string {
	if toolName == "" {
		return ""
	}
	summary := streamToolParamSummary(params)
	if summary == "" {
		return fmt.Sprintf("[tool] %s\n", toolName)
	}
	return fmt.Sprintf("[tool] %s: %s\n", toolName, summary)
}

func streamToolParamSummary(params map[string]any) string {
	if params == nil {
		return ""
	}
	for _, key := range []string{"command", "description", "title", "summary"} {
		if s, ok := params[key].(string); ok && strings.TrimSpace(s) != "" {
			return truncateStreamToolText(strings.TrimSpace(s), 180)
		}
	}
	return ""
}

func streamToolResultDelta(status, output string) string {
	status = strings.TrimSpace(status)
	output = strings.TrimSpace(output)
	if status == "" && output == "" {
		return ""
	}
	if status == "" {
		status = "done"
	}
	if output == "" {
		return fmt.Sprintf("[tool-result] %s\n", status)
	}
	return fmt.Sprintf("[tool-result] %s: %s\n", status, truncateStreamToolText(output, 180))
}

func streamErrorString(v any) string {
	switch err := v.(type) {
	case string:
		return err
	case map[string]any:
		if msg, ok := err["message"].(string); ok {
			return msg
		}
		if data, ok := err["data"].(map[string]any); ok {
			if msg, ok := data["message"].(string); ok {
				return msg
			}
		}
		if name, ok := err["name"].(string); ok {
			return name
		}
	}
	return ""
}

func truncateStreamToolText(s string, max int) string {
	s = strings.Join(strings.Fields(s), " ")
	if len(s) <= max {
		return s
	}
	if max <= 3 {
		return s[:max]
	}
	return s[:max-3] + "..."
}

// --- claude-json emitter ---

type claudeJSONEmitter struct{}

func (claudeJSONEmitter) Name() string { return "claude-json" }

func (e claudeJSONEmitter) Emit(line string) ([]runtime.RuntimeEvent, error) {
	var ev struct {
		Type  string `json:"type"`
		Event struct {
			Type  string `json:"type"`
			Delta struct {
				Type string `json:"type"`
				Text string `json:"text"`
			} `json:"delta"`
		} `json:"event"`
		IsError bool   `json:"is_error"`
		Result  string `json:"result"`
	}
	if err := json.Unmarshal([]byte(line), &ev); err != nil {
		return nil, nil
	}
	switch ev.Type {
	case "stream_event":
		if ev.Event.Type == "content_block_delta" &&
			ev.Event.Delta.Type == "text_delta" &&
			ev.Event.Delta.Text != "" {
			return []runtime.RuntimeEvent{{
				Type:  runtime.EventTypeDelta,
				Delta: ev.Event.Delta.Text,
			}}, nil
		}
		return nil, nil
	case "result":
		if ev.IsError && ev.Result != "" {
			return []runtime.RuntimeEvent{{
				Type:  runtime.EventTypeError,
				Error: ev.Result,
			}}, nil
		}
		return nil, nil
	}
	return nil, nil
}

// --- codex-json emitter ---

type codexJSONEmitter struct{}

func (codexJSONEmitter) Name() string { return "codex-json" }

func (e codexJSONEmitter) Emit(line string) ([]runtime.RuntimeEvent, error) {
	var ev struct {
		Type string `json:"type"`
		Item struct {
			Type    string `json:"type"`
			Text    string `json:"text"`
			Delta   string `json:"delta"`
			Message struct {
				Content []struct {
					Type string `json:"type"`
					Text string `json:"text"`
				} `json:"content"`
			} `json:"message"`
		} `json:"item"`
		Delta   string `json:"delta"`
		Text    string `json:"text"`
		Message string `json:"message"`
		Error   struct {
			Message string `json:"message"`
		} `json:"error"`
		Content []struct {
			Type string `json:"type"`
			Text string `json:"text"`
		} `json:"content"`
	}
	if err := json.Unmarshal([]byte(line), &ev); err != nil {
		return nil, nil
	}
	switch ev.Type {
	case "item.delta", "item.updated", "response.output_text.delta", "output_text.delta":
		if delta := codexDeltaText(ev); delta != "" {
			return []runtime.RuntimeEvent{{
				Type:  runtime.EventTypeDelta,
				Delta: delta,
			}}, nil
		}
		return nil, nil
	case "item.completed":
		if ev.Item.Type == "agent_message" && ev.Item.Text != "" {
			return []runtime.RuntimeEvent{{
				Type:  runtime.EventTypeDelta,
				Delta: ev.Item.Text,
			}}, nil
		}
		if delta := codexDeltaText(ev); delta != "" {
			return []runtime.RuntimeEvent{{
				Type:  runtime.EventTypeDelta,
				Delta: delta,
			}}, nil
		}
		return nil, nil
	case "error":
		return []runtime.RuntimeEvent{{
			Type:  runtime.EventTypeError,
			Error: ev.Message,
		}}, nil
	case "turn.failed":
		return []runtime.RuntimeEvent{{
			Type:  runtime.EventTypeError,
			Error: ev.Error.Message,
		}}, nil
	}
	return nil, nil
}

func codexDeltaText(ev struct {
	Type string `json:"type"`
	Item struct {
		Type    string `json:"type"`
		Text    string `json:"text"`
		Delta   string `json:"delta"`
		Message struct {
			Content []struct {
				Type string `json:"type"`
				Text string `json:"text"`
			} `json:"content"`
		} `json:"message"`
	} `json:"item"`
	Delta   string `json:"delta"`
	Text    string `json:"text"`
	Message string `json:"message"`
	Error   struct {
		Message string `json:"message"`
	} `json:"error"`
	Content []struct {
		Type string `json:"type"`
		Text string `json:"text"`
	} `json:"content"`
}) string {
	if ev.Item.Delta != "" {
		return ev.Item.Delta
	}
	if ev.Delta != "" {
		return ev.Delta
	}
	if ev.Text != "" {
		return ev.Text
	}
	for _, part := range ev.Item.Message.Content {
		if part.Type == "output_text" && part.Text != "" {
			return part.Text
		}
	}
	for _, part := range ev.Content {
		if part.Type == "output_text" && part.Text != "" {
			return part.Text
		}
	}
	return ""
}

// --- opencode-json emitter ---

type opencodeJSONEmitter struct{}

func (opencodeJSONEmitter) Name() string { return "opencode-json" }

func (e opencodeJSONEmitter) Emit(line string) ([]runtime.RuntimeEvent, error) {
	var ev struct {
		Type  string `json:"type"`
		Error struct {
			Name string `json:"name"`
			Data struct {
				Message string `json:"message"`
			} `json:"data"`
		} `json:"error"`
		Part struct {
			Type string `json:"type"`
			Text string `json:"text"`
		} `json:"part"`
	}
	if err := json.Unmarshal([]byte(line), &ev); err != nil {
		return nil, nil
	}
	switch ev.Type {
	case "text":
		if ev.Part.Type == "text" && ev.Part.Text != "" {
			return []runtime.RuntimeEvent{{
				Type:  runtime.EventTypeDelta,
				Delta: ev.Part.Text,
			}}, nil
		}
		return nil, nil
	case "error":
		msg := ev.Error.Data.Message
		if msg == "" {
			msg = ev.Error.Name
		}
		if msg != "" {
			return []runtime.RuntimeEvent{{
				Type:  runtime.EventTypeError,
				Error: msg,
			}}, nil
		}
		return nil, nil
	}
	return nil, nil
}

// --- cline-json emitter ---

type clineJSONEmitter struct{}

func (clineJSONEmitter) Name() string { return "cline-json" }

func (e clineJSONEmitter) Emit(line string) ([]runtime.RuntimeEvent, error) {
	var ev struct {
		Type    string `json:"type"`
		Say     string `json:"say"`
		Ask     string `json:"ask"`
		Text    string `json:"text"`
		Message string `json:"message"`
		Status  string `json:"status"`
		Error   string `json:"error"`
	}
	if err := json.Unmarshal([]byte(line), &ev); err != nil {
		return nil, nil
	}
	switch {
	case ev.Type == "say" && ev.Say == "text" && ev.Text != "":
		return []runtime.RuntimeEvent{{
			Type:  runtime.EventTypeDelta,
			Delta: ev.Text,
		}}, nil
	case ev.Type == "say" && ev.Say == "error":
		msg := ev.Text
		if msg == "" {
			msg = ev.Message
		}
		if msg != "" {
			return []runtime.RuntimeEvent{{
				Type:  runtime.EventTypeError,
				Error: msg,
			}}, nil
		}
		return nil, nil
	case ev.Type == "completion" && ev.Status == "error":
		msg := ev.Error
		if msg == "" {
			msg = ev.Message
		}
		if msg == "" {
			msg = "cline task failed"
		}
		return []runtime.RuntimeEvent{{
			Type:  runtime.EventTypeError,
			Error: msg,
		}}, nil
	case ev.Type == "ask" && ev.Ask == "api_req_failed":
		msg := ev.Text
		if msg == "" {
			msg = ev.Message
		}
		if msg != "" {
			return []runtime.RuntimeEvent{{
				Type:  runtime.EventTypeError,
				Error: msg,
			}}, nil
		}
		return nil, nil
	}
	return nil, nil
}
