package operator

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

// 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

	// Batch/matrix-specific output fields.
	MatrixRunID string
	StrategyID  string
	Timeframe   string
	PeriodID    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

	// Monthly aggregation output fields. They render for aggregate_monthly_bars
	// steps and prove SDD S04: deterministic monthly OHLCV with provenance. The
	// counts default to noCount so an error step omits them; AggregationRuleID is
	// the rule id reported in the response provenance.
	SourceDailyBarCount int
	MonthlyBarCount     int
	ProvenanceCount     int
	AggregationRuleID   string

	// Freshness-specific output fields
	Universe        string
	FreshnessStatus string
	LatestYYYYMMDD  string
	LatestBucket    string
	MissingCount    int
	MissingSymbols  []string

	// Gap-specific output fields
	GapStatus         string
	GapCount          int
	GapYYYYMMDD       []string
	GapBuckets        []string
	DuplicateCount    int
	DuplicateYYYYMMDD []string
	DuplicateBuckets  []string
	ProviderDelayDays 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

	// Live order lifecycle output fields. They render for submit/cancel/get live
	// order steps, keyed on LiveOrderID. BrokerOrderID and BrokerStatus are the
	// raw IDs and status strings the broker returned.
	LiveOrderID       string
	LiveOrderStatus   string
	BrokerOrderID     string
	BrokerStatus      string
	OperatorConfirmed bool

	// Live kill switch output fields. They render for get/set_live_kill_switch steps.
	KillSwitchHalted bool
	KillSwitchReason string

	// Live risk policy output fields. They render for get_live_risk_policy steps.
	MaxDailyOrders    int
	MaxOpenOrders     int
	AllowShortSelling bool

	// Live risk decision output fields. They render for submit_live_order steps
	// when a LiveRiskDecision is present in the response (i.e. risk-blocked submit).
	HasRiskDecision    bool
	RiskDecisionAllow  bool
	RiskDecisionReason string

	// Live account snapshot output fields. They render for sync_live_account and
	// get_live_account_snapshot steps.
	LiveAccountID     string
	LiveBroker        string
	CashCount         int
	LivePositionCount int
	SyncedAtUnixMs    int64
	LiveStale         bool

	// Audit query output fields. They render for list_live_audit_events steps.
	AuditEventCount int

	// Scheduler refresh status output fields. They render for
	// scheduler_refresh_status steps and provide S04 evidence.
	RefreshCount          int
	RefreshSchedule       string
	SchedulerStatus       string
	LastSuccessUnixMs     int64
	LastError             string
	NextRunUnixMs         int64
	RefreshImportedBars   int
	RefreshMissingCount   int
	RefreshGapCount       int
	RefreshDuplicateCount int
	RefreshProviderDelay  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.MatrixRunID != "" {
			fields["matrix_run_id"] = ev.MatrixRunID
			fields["strategy_id"] = ev.StrategyID
			fields["timeframe"] = ev.Timeframe
			fields["period_id"] = ev.PeriodID
		}
		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.StartingCash != "" || 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.Action == string(ActionAggregateMonthlyBars) {
			if ev.SourceDailyBarCount >= 0 {
				fields["source_daily_bar_count"] = ev.SourceDailyBarCount
			}
			if ev.MonthlyBarCount >= 0 {
				fields["monthly_bar_count"] = ev.MonthlyBarCount
			}
			if ev.ProvenanceCount >= 0 {
				fields["provenance_count"] = ev.ProvenanceCount
			}
			if ev.AggregationRuleID != "" {
				fields["aggregation_rule_id"] = ev.AggregationRuleID
			}
		}
		if ev.Universe != "" {
			fields["universe"] = ev.Universe
			fields["timeframe"] = ev.Timeframe
		}
		if ev.FreshnessStatus != "" {
			fields["freshness_status"] = ev.FreshnessStatus
		}
		if ev.LatestYYYYMMDD != "" {
			fields["latest_yyyymmdd"] = ev.LatestYYYYMMDD
		}
		if ev.LatestBucket != "" {
			fields["latest_bucket"] = ev.LatestBucket
		}
		if ev.MissingCount >= 0 {
			fields["missing_count"] = ev.MissingCount
		}
		if len(ev.MissingSymbols) > 0 {
			fields["missing_symbols"] = ev.MissingSymbols
		}
		if ev.GapStatus != "" {
			fields["gap_status"] = ev.GapStatus
			if ev.GapCount >= 0 {
				fields["gap_count"] = ev.GapCount
			}
			if len(ev.GapYYYYMMDD) > 0 {
				fields["gap_yyyymmdd"] = ev.GapYYYYMMDD
			}
			if len(ev.GapBuckets) > 0 {
				fields["gap_buckets"] = ev.GapBuckets
			}
			if ev.DuplicateCount >= 0 {
				fields["duplicate_count"] = ev.DuplicateCount
			}
			if len(ev.DuplicateYYYYMMDD) > 0 {
				fields["duplicate_yyyymmdd"] = ev.DuplicateYYYYMMDD
			}
			if len(ev.DuplicateBuckets) > 0 {
				fields["duplicate_buckets"] = ev.DuplicateBuckets
			}
			if ev.ProviderDelayDays >= 0 {
				fields["provider_delay_days"] = ev.ProviderDelayDays
			}
		}
		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
			}
		}
		if ev.LiveOrderID != "" {
			fields["live_order_id"] = ev.LiveOrderID
			if ev.LiveOrderStatus != "" {
				fields["live_order_status"] = ev.LiveOrderStatus
			}
			if ev.BrokerOrderID != "" {
				fields["broker_order_id"] = ev.BrokerOrderID
			}
			if ev.BrokerStatus != "" {
				fields["broker_status"] = ev.BrokerStatus
			}
			if ev.OperatorConfirmed {
				fields["operator_confirmed"] = ev.OperatorConfirmed
			}
		}
		if ev.Action == string(ActionGetLiveKillSwitch) || ev.Action == string(ActionSetLiveKillSwitch) {
			fields["kill_switch_halted"] = ev.KillSwitchHalted
			if ev.KillSwitchReason != "" {
				fields["kill_switch_reason"] = ev.KillSwitchReason
			}
		}
		if ev.Action == string(ActionGetLiveRiskPolicy) {
			fields["max_daily_orders"] = ev.MaxDailyOrders
			fields["max_open_orders"] = ev.MaxOpenOrders
			fields["allow_short_selling"] = ev.AllowShortSelling
		}
		if ev.HasRiskDecision {
			fields["risk_decision_allowed"] = ev.RiskDecisionAllow
			if ev.RiskDecisionReason != "" {
				fields["risk_decision_reason"] = ev.RiskDecisionReason
			}
		}
		if ev.LiveAccountID != "" {
			fields["live_account_id"] = ev.LiveAccountID
			fields["live_broker"] = ev.LiveBroker
			fields["cash_count"] = ev.CashCount
			fields["live_position_count"] = ev.LivePositionCount
			fields["synced_at_unix_ms"] = ev.SyncedAtUnixMs
			fields["live_stale"] = ev.LiveStale
		}
		if ev.Action == string(ActionListLiveAuditEvents) {
			fields["audit_event_count"] = ev.AuditEventCount
		}
		if ev.Action == string(ActionSchedulerRefreshStatus) {
			fields["refresh_count"] = ev.RefreshCount
			if ev.RefreshSchedule != "" {
				fields["schedule"] = ev.RefreshSchedule
			}
			if ev.SchedulerStatus != "" {
				fields["scheduler_status"] = ev.SchedulerStatus
			}
			fields["last_success_unix_ms"] = ev.LastSuccessUnixMs
			// Always include last_error even if empty for S04 completeness.
			fields["last_error"] = ev.LastError
			// Always include next_run_unix_ms for S04 completeness.
			fields["next_run_unix_ms"] = ev.NextRunUnixMs
			fields["imported_bar_count"] = ev.RefreshImportedBars
			fields["missing_count"] = ev.RefreshMissingCount
			fields["gap_count"] = ev.RefreshGapCount
			fields["duplicate_count"] = ev.RefreshDuplicateCount
			fields["provider_delay_days"] = ev.RefreshProviderDelay
		}
		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.MatrixRunID != "" {
		line += fmt.Sprintf(" matrix_run_id=%s strategy_id=%s timeframe=%s period_id=%s",
			ev.MatrixRunID, ev.StrategyID, ev.Timeframe, ev.PeriodID)
	}
	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.StartingCash != "" || 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.Action == string(ActionAggregateMonthlyBars) {
		if ev.SourceDailyBarCount >= 0 {
			line += fmt.Sprintf(" source_daily_bar_count=%d", ev.SourceDailyBarCount)
		}
		if ev.MonthlyBarCount >= 0 {
			line += fmt.Sprintf(" monthly_bar_count=%d", ev.MonthlyBarCount)
		}
		if ev.ProvenanceCount >= 0 {
			line += fmt.Sprintf(" provenance_count=%d", ev.ProvenanceCount)
		}
		if ev.AggregationRuleID != "" {
			line += fmt.Sprintf(" aggregation_rule_id=%s", ev.AggregationRuleID)
		}
	}
	if ev.Universe != "" {
		line += fmt.Sprintf(" universe=%s timeframe=%s", ev.Universe, ev.Timeframe)
	}
	if ev.FreshnessStatus != "" {
		line += fmt.Sprintf(" freshness_status=%s", ev.FreshnessStatus)
	}
	if ev.LatestYYYYMMDD != "" {
		line += fmt.Sprintf(" latest_yyyymmdd=%s", ev.LatestYYYYMMDD)
	}
	if ev.LatestBucket != "" {
		line += fmt.Sprintf(" latest_bucket=%s", ev.LatestBucket)
	}
	if ev.MissingCount >= 0 {
		line += fmt.Sprintf(" missing_count=%d", ev.MissingCount)
	}
	if len(ev.MissingSymbols) > 0 {
		line += fmt.Sprintf(" missing_symbols=%s", strings.Join(ev.MissingSymbols, ","))
	}
	if ev.GapStatus != "" {
		line += fmt.Sprintf(" gap_status=%s", ev.GapStatus)
		if ev.GapCount >= 0 {
			line += fmt.Sprintf(" gap_count=%d", ev.GapCount)
		}
		if len(ev.GapYYYYMMDD) > 0 {
			line += fmt.Sprintf(" gap_yyyymmdd=%s", strings.Join(ev.GapYYYYMMDD, ","))
		}
		if len(ev.GapBuckets) > 0 {
			line += fmt.Sprintf(" gap_buckets=%s", strings.Join(ev.GapBuckets, ","))
		}
		if ev.DuplicateCount >= 0 {
			line += fmt.Sprintf(" duplicate_count=%d", ev.DuplicateCount)
		}
		if len(ev.DuplicateYYYYMMDD) > 0 {
			line += fmt.Sprintf(" duplicate_yyyymmdd=%s", strings.Join(ev.DuplicateYYYYMMDD, ","))
		}
		if len(ev.DuplicateBuckets) > 0 {
			line += fmt.Sprintf(" duplicate_buckets=%s", strings.Join(ev.DuplicateBuckets, ","))
		}
		if ev.ProviderDelayDays >= 0 {
			line += fmt.Sprintf(" provider_delay_days=%d", ev.ProviderDelayDays)
		}
	}
	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)
		}
	}
	if ev.LiveOrderID != "" {
		line += fmt.Sprintf(" live_order_id=%s", ev.LiveOrderID)
		if ev.LiveOrderStatus != "" {
			line += fmt.Sprintf(" live_order_status=%s", ev.LiveOrderStatus)
		}
		if ev.BrokerOrderID != "" {
			line += fmt.Sprintf(" broker_order_id=%s", ev.BrokerOrderID)
		}
		if ev.BrokerStatus != "" {
			line += fmt.Sprintf(" broker_status=%s", ev.BrokerStatus)
		}
		if ev.OperatorConfirmed {
			line += " operator_confirmed=true"
		}
	}
	if ev.Action == string(ActionGetLiveKillSwitch) || ev.Action == string(ActionSetLiveKillSwitch) {
		line += fmt.Sprintf(" kill_switch_halted=%v", ev.KillSwitchHalted)
		if ev.KillSwitchReason != "" {
			line += fmt.Sprintf(" kill_switch_reason=%q", ev.KillSwitchReason)
		}
	}
	if ev.Action == string(ActionGetLiveRiskPolicy) {
		line += fmt.Sprintf(" max_daily_orders=%d max_open_orders=%d allow_short_selling=%v", ev.MaxDailyOrders, ev.MaxOpenOrders, ev.AllowShortSelling)
	}
	if ev.HasRiskDecision {
		line += fmt.Sprintf(" risk_decision_allowed=%v", ev.RiskDecisionAllow)
		if ev.RiskDecisionReason != "" {
			line += fmt.Sprintf(" risk_decision_reason=%q", ev.RiskDecisionReason)
		}
	}
	if ev.LiveAccountID != "" {
		line += fmt.Sprintf(" live_account_id=%s live_broker=%s cash_count=%d live_position_count=%d synced_at_unix_ms=%d live_stale=%v",
			ev.LiveAccountID, ev.LiveBroker, ev.CashCount, ev.LivePositionCount, ev.SyncedAtUnixMs, ev.LiveStale)
	}
	if ev.Action == string(ActionListLiveAuditEvents) {
		line += fmt.Sprintf(" audit_event_count=%d", ev.AuditEventCount)
	}
	if ev.Action == string(ActionSchedulerRefreshStatus) {
		line += fmt.Sprintf(" refresh_count=%d", ev.RefreshCount)
		if ev.RefreshSchedule != "" {
			line += fmt.Sprintf(" schedule=%s", ev.RefreshSchedule)
		}
		if ev.SchedulerStatus != "" {
			line += fmt.Sprintf(" scheduler_status=%s", ev.SchedulerStatus)
		}
		// Always include last_error and next_run_unix_ms for S04 completeness.
		line += fmt.Sprintf(" last_success_unix_ms=%d last_error=%q next_run_unix_ms=%d imported_bar_count=%d missing_count=%d gap_count=%d duplicate_count=%d provider_delay_days=%d",
			ev.LastSuccessUnixMs, ev.LastError, ev.NextRunUnixMs, ev.RefreshImportedBars, ev.RefreshMissingCount, ev.RefreshGapCount, ev.RefreshDuplicateCount, ev.RefreshProviderDelay)
	}
	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))
}
