package operator

import (
	"encoding/json"
	"fmt"
	"io"
)

// OutputFormat selects how runner events are rendered to stdout.
type OutputFormat string

const (
	// OutputText emits grep-friendly key=value lines (the default).
	OutputText OutputFormat = "text"
	// OutputJSONL emits one JSON object per line.
	OutputJSONL OutputFormat = "jsonl"
)

// noCount marks a step event that has no market result count (for example a
// hello handshake or a transport failure).
const noCount = -1

// StepEvent is one machine-readable record describing a single executed step.
type StepEvent struct {
	Scenario     string
	Step         string
	Action       string
	Status       string
	Count        int
	ErrorCode    string
	ErrorMessage string

	// Backtest-specific output fields
	RunID        string
	RunStatus    string
	StartingCash string
	EndingEquity string
	TotalReturn  string
	TradeCount   int

	// Import-specific output fields
	Provider        string
	InstrumentCount int
	BarCount        int

	// Paper-trading-specific output fields. They are only rendered when
	// AccountID is set, so non-paper steps never emit them.
	AccountID     string
	Cash          string
	PositionCount int
	FillCount     int
	Risk          string

	// Paper risk summary. Risk is "clear" when no order was rejected and
	// "blocked" otherwise. RiskRejectedCount and RiskReason are only rendered for
	// a blocked run so a clear run keeps stable, minimal output.
	RiskRejectedCount int
	RiskReason        string

	// Paper loop equity summary. EquityPointCount is the length of the paper
	// equity curve and LatestEquity is the equity amount of its last point.
	// They are omitted for an empty curve so loop smoke evidence stays stable.
	EquityPointCount int
	LatestEquity     string

	// Paper order lifecycle output fields. They render for submit/cancel/fill
	// steps, keyed on OrderID, independently of the paper-state account block.
	// OrderFillPrice/OrderFillCount carry the fill summary of a filled order.
	OrderID        string
	OrderStatus    string
	OrderReason    string
	OrderFillPrice string
	OrderFillCount int
}

// RunSummary is the final record describing a whole scenario run.
type RunSummary struct {
	Scenario string
	Status   string
	Steps    int
	Passed   int
	ExitCode int
}

// Writer renders runner events to an io.Writer in the selected format. stdout
// carries these machine-readable lines; scripts grep them for status keys.
type Writer struct {
	w      io.Writer
	format OutputFormat
}

// NewWriter returns a Writer for the given format, defaulting to text.
func NewWriter(w io.Writer, format OutputFormat) *Writer {
	if format == "" {
		format = OutputText
	}
	return &Writer{w: w, format: format}
}

// WriteStep renders one executed step.
func (o *Writer) WriteStep(ev StepEvent) {
	if o.format == OutputJSONL {
		fields := map[string]any{
			"type":     "step",
			"scenario": ev.Scenario,
			"step":     ev.Step,
			"action":   ev.Action,
			"status":   ev.Status,
		}
		if ev.Count >= 0 {
			fields["count"] = ev.Count
		}
		if ev.ErrorCode != "" {
			fields["error_code"] = ev.ErrorCode
		}
		if ev.ErrorMessage != "" {
			fields["error_message"] = ev.ErrorMessage
		}
		if ev.RunID != "" {
			fields["run_id"] = ev.RunID
		}
		if ev.RunStatus != "" {
			fields["run_status"] = ev.RunStatus
		}
		if ev.StartingCash != "" {
			fields["starting_cash"] = ev.StartingCash
		}
		if ev.EndingEquity != "" {
			fields["ending_equity"] = ev.EndingEquity
		}
		if ev.TotalReturn != "" {
			fields["total_return"] = ev.TotalReturn
		}
		if ev.TradeCount > 0 {
			fields["trade_count"] = ev.TradeCount
		}
		if ev.Provider != "" {
			fields["provider"] = ev.Provider
		}
		if ev.InstrumentCount >= 0 {
			fields["instrument_count"] = ev.InstrumentCount
		}
		if ev.BarCount >= 0 {
			fields["bar_count"] = ev.BarCount
		}
		if ev.AccountID != "" {
			fields["account_id"] = ev.AccountID
			if ev.Cash != "" {
				fields["cash"] = ev.Cash
			}
			fields["position_count"] = ev.PositionCount
			fields["fill_count"] = ev.FillCount
			if ev.Risk != "" {
				fields["risk"] = ev.Risk
			}
			if ev.RiskRejectedCount > 0 {
				fields["risk_rejected_count"] = ev.RiskRejectedCount
			}
			if ev.RiskReason != "" {
				fields["risk_reason"] = ev.RiskReason
			}
			if ev.EquityPointCount > 0 {
				fields["equity_point_count"] = ev.EquityPointCount
			}
			if ev.LatestEquity != "" {
				fields["latest_equity"] = ev.LatestEquity
			}
		}
		if ev.OrderID != "" {
			fields["order_id"] = ev.OrderID
			if ev.OrderStatus != "" {
				fields["order_status"] = ev.OrderStatus
			}
			if ev.OrderReason != "" {
				fields["order_reason"] = ev.OrderReason
			}
			if ev.Action == string(ActionFillPaperOrder) {
				fields["fill_count"] = ev.OrderFillCount
			}
			if ev.OrderFillPrice != "" {
				fields["fill_price"] = ev.OrderFillPrice
			}
		}
		o.writeJSON(fields)
		return
	}

	line := fmt.Sprintf("scenario=%s step=%s action=%s status=%s", ev.Scenario, ev.Step, ev.Action, ev.Status)
	if ev.Count >= 0 {
		line += fmt.Sprintf(" count=%d", ev.Count)
	}
	if ev.ErrorCode != "" {
		line += fmt.Sprintf(" error.code=%s", ev.ErrorCode)
	}
	if ev.ErrorMessage != "" {
		line += fmt.Sprintf(" error.message=%q", ev.ErrorMessage)
	}
	if ev.RunID != "" {
		line += fmt.Sprintf(" run.id=%s", ev.RunID)
	}
	if ev.RunStatus != "" {
		line += fmt.Sprintf(" run.status=%s", ev.RunStatus)
	}
	if ev.StartingCash != "" {
		line += fmt.Sprintf(" starting_cash=%s", ev.StartingCash)
	}
	if ev.EndingEquity != "" {
		line += fmt.Sprintf(" ending_equity=%s", ev.EndingEquity)
	}
	if ev.TotalReturn != "" {
		line += fmt.Sprintf(" total_return=%s", ev.TotalReturn)
	}
	if ev.TradeCount > 0 {
		line += fmt.Sprintf(" trade_count=%d", ev.TradeCount)
	}
	if ev.Provider != "" {
		line += fmt.Sprintf(" provider=%s", ev.Provider)
	}
	if ev.InstrumentCount >= 0 {
		line += fmt.Sprintf(" instrument_count=%d", ev.InstrumentCount)
	}
	if ev.BarCount >= 0 {
		line += fmt.Sprintf(" bar_count=%d", ev.BarCount)
	}
	if ev.AccountID != "" {
		line += fmt.Sprintf(" account_id=%s", ev.AccountID)
		if ev.Cash != "" {
			line += fmt.Sprintf(" cash=%s", ev.Cash)
		}
		line += fmt.Sprintf(" position_count=%d fill_count=%d", ev.PositionCount, ev.FillCount)
		if ev.Risk != "" {
			line += fmt.Sprintf(" risk=%s", ev.Risk)
		}
		if ev.RiskRejectedCount > 0 {
			line += fmt.Sprintf(" risk_rejected_count=%d", ev.RiskRejectedCount)
		}
		if ev.RiskReason != "" {
			line += fmt.Sprintf(" risk_reason=%q", ev.RiskReason)
		}
		if ev.EquityPointCount > 0 {
			line += fmt.Sprintf(" equity_point_count=%d", ev.EquityPointCount)
		}
		if ev.LatestEquity != "" {
			line += fmt.Sprintf(" latest_equity=%s", ev.LatestEquity)
		}
	}
	if ev.OrderID != "" {
		line += fmt.Sprintf(" order_id=%s", ev.OrderID)
		if ev.OrderStatus != "" {
			line += fmt.Sprintf(" order_status=%s", ev.OrderStatus)
		}
		if ev.OrderReason != "" {
			line += fmt.Sprintf(" order_reason=%q", ev.OrderReason)
		}
		if ev.Action == string(ActionFillPaperOrder) {
			line += fmt.Sprintf(" fill_count=%d", ev.OrderFillCount)
		}
		if ev.OrderFillPrice != "" {
			line += fmt.Sprintf(" fill_price=%s", ev.OrderFillPrice)
		}
	}
	fmt.Fprintln(o.w, line)
}

// WriteSummary renders the final run summary.
func (o *Writer) WriteSummary(s RunSummary) {
	if o.format == OutputJSONL {
		o.writeJSON(map[string]any{
			"type":      "summary",
			"scenario":  s.Scenario,
			"status":    s.Status,
			"steps":     s.Steps,
			"passed":    s.Passed,
			"exit_code": s.ExitCode,
		})
		return
	}
	fmt.Fprintf(o.w, "scenario=%s status=%s steps=%d passed=%d exit_code=%d\n", s.Scenario, s.Status, s.Steps, s.Passed, s.ExitCode)
}

func (o *Writer) writeJSON(fields map[string]any) {
	data, err := json.Marshal(fields)
	if err != nil {
		// Marshalling a map of strings/ints cannot realistically fail; fall back
		// to a minimal line so a step result is never silently dropped.
		fmt.Fprintf(o.w, "{\"type\":\"error\",\"error_message\":%q}\n", err.Error())
		return
	}
	fmt.Fprintln(o.w, string(data))
}
