package backtest

import (
	"context"
	"testing"
	"time"

	"git.toki-labs.com/toki/alt/packages/domain/backtest"
	"git.toki-labs.com/toki/alt/packages/domain/market"
)

type mockBarSource struct {
	bars    []market.Bar
	gotSpec backtest.RunSpec
}

func (m *mockBarSource) GetBarsForRun(ctx context.Context, spec backtest.RunSpec) ([]market.Bar, error) {
	m.gotSpec = spec
	return m.bars, nil
}

type mockStrategy struct {
	decideCalls []backtest.StrategyInput
}

func (m *mockStrategy) ID() backtest.StrategyID {
	return "test-strategy"
}

func (m *mockStrategy) Decide(input backtest.StrategyInput) ([]backtest.OrderIntent, error) {
	m.decideCalls = append(m.decideCalls, input)

	// Buy 2 units of the instrument on the first bar
	if len(m.decideCalls) == 1 {
		return []backtest.OrderIntent{
			{
				InstrumentID: input.Bar.InstrumentID,
				Side:         backtest.OrderSideBuy,
				Quantity:     market.Quantity{Amount: market.Decimal{Value: "2"}},
			},
		}, nil
	}
	return nil, nil
}

type mockStrategyPort struct {
	strategy backtest.Strategy
}

func (m *mockStrategyPort) GetStrategy(ctx context.Context, id backtest.StrategyID) (backtest.Strategy, error) {
	return m.strategy, nil
}

// TestEngineFillsOrdersOnNextMatchingInstrumentBar proves the engine holds
// strategy orders as pending and executes them on the next available bar for
// the same instrument using FillOrderOnBar (bar.Open for market orders).
func TestEngineFillsOrdersOnNextMatchingInstrumentBar(t *testing.T) {
	bar1 := market.Bar{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Timeframe:    market.TimeframeDaily,
		Timestamp:    time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
		Open:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "990"}},
		Close:        market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1000"}},
		Low:          market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "980"}},
		High:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1000"}},
	}
	bar2 := market.Bar{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Timeframe:    market.TimeframeDaily,
		Timestamp:    time.Date(2026, 5, 2, 0, 0, 0, 0, time.UTC),
		Open:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1010"}},
		Close:        market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1100"}},
		Low:          market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1000"}},
		High:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1100"}},
	}

	barSource := &mockBarSource{bars: []market.Bar{bar2, bar1}} // unsorted
	strat := &mockStrategy{}
	engine := NewEngine(barSource, &mockStrategyPort{strategy: strat}, nil)

	run := backtest.Run{
		ID: "run-next-bar",
		Spec: backtest.RunSpec{
			StrategyID: "test-strategy",
			Market:     market.MarketKR,
			Timeframe:  market.TimeframeDaily,
			From:       time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
			To:         time.Date(2026, 5, 2, 0, 0, 0, 0, time.UTC),
		},
	}

	err := engine.Execute(context.Background(), run)
	if err != nil {
		t.Fatalf("Engine execution failed: %v", err)
	}

	if len(strat.decideCalls) != 2 {
		t.Fatalf("expected 2 strategy Decide calls, got %d", len(strat.decideCalls))
	}

	// First call: bar1 (May 1). Strategy returns buy order for 2 units.
	// Cash should be 10000000 (initial), position 0.
	firstCall := strat.decideCalls[0]
	if !firstCall.Bar.Timestamp.Equal(bar1.Timestamp) {
		t.Errorf("expected first bar to be May 1, got %s", firstCall.Bar.Timestamp)
	}
	if firstCall.Portfolio.Cash.Amount.Value != "10000000" {
		t.Errorf("expected starting cash 10000000, got %s", firstCall.Portfolio.Cash.Amount.Value)
	}

	// Second call: bar2 (May 2). Strategy returns nil (no orders).
	// The buy from bar1 should have been filled on bar2 at bar2.Open = 1010.
	secondCall := strat.decideCalls[1]
	if !secondCall.Bar.Timestamp.Equal(bar2.Timestamp) {
		t.Errorf("expected second bar to be May 2, got %s", secondCall.Bar.Timestamp)
	}

	// After buy: cash = 10000000 - 2*1010 = 9997980, position = 2
	if secondCall.Portfolio.Cash.Amount.Value != "9997980" {
		t.Errorf("expected cash after buy at 1010 to be 9997980, got %s", secondCall.Portfolio.Cash.Amount.Value)
	}
	pos, ok := secondCall.Portfolio.Position(market.InstrumentID("KRX:005930"))
	if !ok {
		t.Fatal("expected position in second call portfolio")
	}
	if pos.Quantity.Amount.Value != "2" {
		t.Errorf("expected position quantity 2, got %s", pos.Quantity.Amount.Value)
	}
}

// TestEnginePassesRunSpecToBarSource proves the engine forwards the whole RunSpec
// (including the input selector) to the bar source instead of dropping selection
// at the engine boundary.
func TestEnginePassesRunSpecToBarSource(t *testing.T) {
	barSource := &mockBarSource{}
	strategyPort := &mockStrategyPort{strategy: &mockStrategy{}}
	engine := NewEngine(barSource, strategyPort, nil)

	run := backtest.Run{
		ID: "run-selector",
		Spec: backtest.RunSpec{
			StrategyID: "test-strategy",
			Market:     market.MarketKR,
			Timeframe:  market.TimeframeDaily,
			From:       time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
			To:         time.Date(2026, 5, 2, 0, 0, 0, 0, time.UTC),
			Selector: backtest.InputSelector{
				InstrumentIDs: []market.InstrumentID{"KRX:005930"},
				Symbols:       []string{"005930"},
			},
		},
	}

	if err := engine.Execute(context.Background(), run); err != nil {
		t.Fatalf("Engine execution failed: %v", err)
	}

	if len(barSource.gotSpec.Selector.InstrumentIDs) != 1 ||
		barSource.gotSpec.Selector.InstrumentIDs[0] != "KRX:005930" {
		t.Errorf("selector instrument ids not forwarded: %+v", barSource.gotSpec.Selector)
	}
	if len(barSource.gotSpec.Selector.Symbols) != 1 || barSource.gotSpec.Selector.Symbols[0] != "005930" {
		t.Errorf("selector symbols not forwarded: %+v", barSource.gotSpec.Selector)
	}
}

// TestEngineOrdersEqualTimestampBarsByInstrumentID verifies that when multiple
// bars share the same timestamp the engine passes them to strategy.Decide in
// ascending instrument id order, matching the StorageBarSource sort contract.
func TestEngineOrdersEqualTimestampBarsByInstrumentID(t *testing.T) {
	sameTime := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
	barA := market.Bar{
		InstrumentID: market.InstrumentID("KRX:000660"),
		Timeframe:    market.TimeframeDaily,
		Timestamp:    sameTime,
		Open:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "80000"}},
		Close:        market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "80000"}},
		Low:          market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "79000"}},
		High:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "81000"}},
	}
	barB := market.Bar{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Timeframe:    market.TimeframeDaily,
		Timestamp:    sameTime,
		Open:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1000"}},
		Close:        market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1000"}},
		Low:          market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "990"}},
		High:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1010"}},
	}

	// Provide bars in reverse instrument-id order to prove the engine sorts them.
	barSource := &mockBarSource{bars: []market.Bar{barB, barA}}
	strat := &multiInstrumentBuyStrategy{}
	engine := NewEngine(barSource, &mockStrategyPort{strategy: strat}, nil)

	run := backtest.Run{
		ID: "run-tie",
		Spec: backtest.RunSpec{
			StrategyID: "test-strategy",
			Market:     market.MarketKR,
			Timeframe:  market.TimeframeDaily,
			From:       sameTime,
			To:         sameTime,
		},
	}
	if err := engine.Execute(context.Background(), run); err != nil {
		t.Fatalf("Engine execution failed: %v", err)
	}
	if len(strat.decideCalls) != 2 {
		t.Fatalf("expected 2 Decide calls, got %d", len(strat.decideCalls))
	}
	if strat.decideCalls[0].Bar.InstrumentID != barA.InstrumentID {
		t.Errorf("first Decide call: got instrument %q, want %q", strat.decideCalls[0].Bar.InstrumentID, barA.InstrumentID)
	}
	if strat.decideCalls[1].Bar.InstrumentID != barB.InstrumentID {
		t.Errorf("second Decide call: got instrument %q, want %q", strat.decideCalls[1].Bar.InstrumentID, barB.InstrumentID)
	}
}

// multiInstrumentBuyStrategy buys on every bar.
type multiInstrumentBuyStrategy struct {
	decideCalls []backtest.StrategyInput
}

func (s *multiInstrumentBuyStrategy) ID() backtest.StrategyID {
	return "multi-buy"
}

func (s *multiInstrumentBuyStrategy) Decide(input backtest.StrategyInput) ([]backtest.OrderIntent, error) {
	s.decideCalls = append(s.decideCalls, input)
	return []backtest.OrderIntent{{
		InstrumentID: input.Bar.InstrumentID,
		Side:         backtest.OrderSideBuy,
		Quantity:     market.Quantity{Amount: market.Decimal{Value: "1"}},
	}}, nil
}

// TestEngineCarriesUncrossedLimitOrderToLaterMatchingBar proves that a limit order
// whose price was not crossed on the first matching bar is kept pending and fills
// on a later bar where the price condition is met.
func TestEngineCarriesUncrossedLimitOrderToLaterMatchingBar(t *testing.T) {
	bar1 := market.Bar{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Timeframe:    market.TimeframeDaily,
		Timestamp:    time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
		Open:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1000"}},
		Close:        market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1000"}},
		Low:          market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "990"}},
		High:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1010"}},
	}
	// bar2: low=950 > limit=940, so limit buy does NOT cross
	bar2 := market.Bar{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Timeframe:    market.TimeframeDaily,
		Timestamp:    time.Date(2026, 5, 2, 0, 0, 0, 0, time.UTC),
		Open:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1050"}},
		Close:        market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1000"}},
		Low:          market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "950"}},
		High:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1060"}},
	}
	// bar3: low=930 <= limit=940, so limit buy DOES cross
	bar3 := market.Bar{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Timeframe:    market.TimeframeDaily,
		Timestamp:    time.Date(2026, 5, 3, 0, 0, 0, 0, time.UTC),
		Open:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "950"}},
		Close:        market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "900"}},
		Low:          market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "930"}},
		High:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "970"}},
	}

	limitBuyStrategy := &limitBuyStrategy{limitPrice: market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "940"}}}
	store := newInMemoryResultStore()
	engine := NewEngine(&mockBarSource{bars: []market.Bar{bar1, bar2, bar3}}, &mockStrategyPort{strategy: limitBuyStrategy}, store)

	run := backtest.Run{
		ID: "run-limit-carry",
		Spec: backtest.RunSpec{
			StrategyID: "limit-buy-strategy",
			Market:     market.MarketKR,
			Timeframe:  market.TimeframeDaily,
			From:       time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
			To:         time.Date(2026, 5, 3, 0, 0, 0, 0, time.UTC),
		},
	}

	if err := engine.Execute(context.Background(), run); err != nil {
		t.Fatalf("Engine execution failed: %v", err)
	}

	result, err := store.GetResult(context.Background(), run.ID)
	if err != nil {
		t.Fatalf("failed to get result: %v", err)
	}

	// bar1: strategy returns limit buy @ 940 on first bar.
	// bar2: low=950 > limit=940, limit buy @ 940 does NOT fill.
	// bar3: low=930 <= limit=940, limit buy @ 940 DOES fill.
	// Expected: 1 trade, filled at bar3.
	if len(result.Trades) != 1 {
		t.Fatalf("expected 1 trade, got %d", len(result.Trades))
	}
	if len(result.Trades) > 0 {
		trade := result.Trades[0]
		if trade.InstrumentID != market.InstrumentID("KRX:005930") {
			t.Errorf("expected trade instrument KRX:005930, got %s", trade.InstrumentID)
		}
		expectedQty := market.Quantity{Amount: market.Decimal{Value: "2"}}
		if trade.Quantity.Amount.Value != expectedQty.Amount.Value {
			t.Errorf("expected trade quantity %s, got %s", expectedQty.Amount.Value, trade.Quantity.Amount.Value)
		}
	}
}

// limitBuyStrategy returns a limit buy order on the first bar call.
type limitBuyStrategy struct {
	limitPrice market.Price
	callCount  int
}

func (s *limitBuyStrategy) ID() backtest.StrategyID {
	return "limit-buy-strategy"
}

func (s *limitBuyStrategy) Decide(input backtest.StrategyInput) ([]backtest.OrderIntent, error) {
	s.callCount++
	if s.callCount == 1 {
		return []backtest.OrderIntent{{
			InstrumentID: input.Bar.InstrumentID,
			Side:         backtest.OrderSideBuy,
			Quantity:     market.Quantity{Amount: market.Decimal{Value: "2"}},
			Type:         backtest.OrderTypeLimit,
			LimitPrice:   s.limitPrice,
		}}, nil
	}
	return nil, nil
}

// TestEngineQueuesOrdersByOrderInstrumentID proves that orders are enqueued by
// their own InstrumentID, not the bar's instrument.
//
// Fixture: A1 (May1, KRX:000660), A2 (May2, KRX:000660), B2 (May2, KRX:005930).
// No B1 bar exists, so a B order placed on A1 cannot fill until B2.
// Strategy: on A1 bar returns a B (KRX:005930) order; on all other bars returns nil.
//
// Correct keying (order.InstrumentID): B order sits in pendingOrders["KRX:005930"],
// fills on B2 at B2.Open=1100.
// Old buggy keying (bar.InstrumentID): B order sits in pendingOrders["KRX:000660"],
// fills on A2 at A2.Open=82000.
func TestEngineQueuesOrdersByOrderInstrumentID(t *testing.T) {
	barA1 := market.Bar{
		InstrumentID: market.InstrumentID("KRX:000660"),
		Timeframe:    market.TimeframeDaily,
		Timestamp:    time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
		Open:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "80000"}},
		Close:        market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "80000"}},
		Low:          market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "79000"}},
		High:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "81000"}},
	}
	barA2 := market.Bar{
		InstrumentID: market.InstrumentID("KRX:000660"),
		Timeframe:    market.TimeframeDaily,
		Timestamp:    time.Date(2026, 5, 2, 0, 0, 0, 0, time.UTC),
		Open:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "82000"}},
		Close:        market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "82000"}},
		Low:          market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "81000"}},
		High:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "83000"}},
	}
	// B2 is the only B bar; B1 is intentionally absent so the B order from A1 waits until B2.
	barB2 := market.Bar{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Timeframe:    market.TimeframeDaily,
		Timestamp:    time.Date(2026, 5, 2, 0, 0, 0, 0, time.UTC),
		Open:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1100"}},
		Close:        market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1100"}},
		Low:          market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1090"}},
		High:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1110"}},
	}

	// Sorted order: A1 (May1, 000660), A2 (May2, 000660), B2 (May2, 005930)
	crossInstStrat := &crossInstrumentStrategy{}
	store := newInMemoryResultStore()
	engine := NewEngine(&mockBarSource{bars: []market.Bar{barA1, barA2, barB2}}, &mockStrategyPort{strategy: crossInstStrat}, store)

	run := backtest.Run{
		ID: "run-cross-instrument",
		Spec: backtest.RunSpec{
			StrategyID: "cross-instrument-strategy",
			Market:     market.MarketKR,
			Timeframe:  market.TimeframeDaily,
			From:       time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
			To:         time.Date(2026, 5, 2, 0, 0, 0, 0, time.UTC),
		},
	}

	if err := engine.Execute(context.Background(), run); err != nil {
		t.Fatalf("Engine execution failed: %v", err)
	}

	result, err := store.GetResult(context.Background(), run.ID)
	if err != nil {
		t.Fatalf("failed to get result: %v", err)
	}

	// B order from A1 must fill on B2 at B2.Open=1100, not on A2 at A2.Open=82000.
	// No A trade should exist.
	if len(result.Trades) != 1 {
		t.Fatalf("expected 1 trade, got %d", len(result.Trades))
	}
	trade := result.Trades[0]
	if trade.InstrumentID != market.InstrumentID("KRX:005930") {
		t.Errorf("expected trade instrument KRX:005930, got %s", trade.InstrumentID)
	}
	if trade.Price.Amount.Value != "1100" {
		t.Errorf("expected fill price B2.Open=1100, got %s (82000 means order was keyed by bar instrument)", trade.Price.Amount.Value)
	}
}

// crossInstrumentStrategy returns a KRX:005930 (B) order when processing the
// KRX:000660 (A) bar on its first call, and nil for all subsequent calls.
// This simulates a strategy that places an order for a different instrument than
// the bar being evaluated.
type crossInstrumentStrategy struct {
	decideCalls []backtest.StrategyInput
}

func (s *crossInstrumentStrategy) ID() backtest.StrategyID {
	return "cross-instrument"
}

func (s *crossInstrumentStrategy) Decide(input backtest.StrategyInput) ([]backtest.OrderIntent, error) {
	s.decideCalls = append(s.decideCalls, input)
	if len(s.decideCalls) == 1 {
		return []backtest.OrderIntent{{
			InstrumentID: market.InstrumentID("KRX:005930"),
			Side:         backtest.OrderSideBuy,
			Quantity:     market.Quantity{Amount: market.Decimal{Value: "1"}},
		}}, nil
	}
	return nil, nil
}

// TestEngineDoesNotFillPendingOrderWithoutNextBar proves that if the strategy
// places an order on the last bar in the series it is discarded, and verifies
// via the result store instead of strategy-local state.
func TestEngineDoesNotFillPendingOrderWithoutNextBar(t *testing.T) {
	bar1 := market.Bar{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Timeframe:    market.TimeframeDaily,
		Timestamp:    time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
		Open:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1000"}},
		Close:        market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1000"}},
		Low:          market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "990"}},
		High:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1010"}},
	}
	bar2 := market.Bar{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Timeframe:    market.TimeframeDaily,
		Timestamp:    time.Date(2026, 5, 2, 0, 0, 0, 0, time.UTC),
		Open:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1100"}},
		Close:        market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1100"}},
		Low:          market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1090"}},
		High:         market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1110"}},
	}

	lastBarBuyStrategy := &lastBarBuyStrategy{}
	store := newInMemoryResultStore()
	barSource := &mockBarSource{bars: []market.Bar{bar1, bar2}}
	engine := NewEngine(barSource, &mockStrategyPort{strategy: lastBarBuyStrategy}, store)

	run := backtest.Run{
		ID: "run-last-bar",
		Spec: backtest.RunSpec{
			StrategyID: "last-bar-strategy",
			Market:     market.MarketKR,
			Timeframe:  market.TimeframeDaily,
			From:       time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
			To:         time.Date(2026, 5, 2, 0, 0, 0, 0, time.UTC),
		},
	}

	if err := engine.Execute(context.Background(), run); err != nil {
		t.Fatalf("Engine execution failed: %v", err)
	}

	result, err := store.GetResult(context.Background(), run.ID)
	if err != nil {
		t.Fatalf("failed to get result: %v", err)
	}

	// There should be no trades because the buy was placed on the last bar
	// and no next bar existed to execute it.
	if len(result.Trades) != 0 {
		t.Errorf("expected 0 trades (order discarded), got %d", len(result.Trades))
	}

	// No positions should be open
	if len(result.Positions) != 0 {
		t.Errorf("expected 0 positions, got %d", len(result.Positions))
	}

	// Ending equity should equal starting cash (10000000 KRW) since no trades occurred
	if result.EndingEquity.Amount.Value != startingCashKRW {
		t.Errorf("expected ending equity %s (starting cash), got %s", startingCashKRW, result.EndingEquity.Amount.Value)
	}
}

// lastBarBuyStrategy buys only on the last bar (day == 2).
type lastBarBuyStrategy struct {
	callCount int
}

func (s *lastBarBuyStrategy) ID() backtest.StrategyID {
	return "last-bar-strategy"
}

func (s *lastBarBuyStrategy) Decide(input backtest.StrategyInput) ([]backtest.OrderIntent, error) {
	s.callCount++
	if input.Bar.Timestamp.Day() != 2 {
		return nil, nil
	}
	return []backtest.OrderIntent{{
		InstrumentID: input.Bar.InstrumentID,
		Side:         backtest.OrderSideBuy,
		Quantity:     market.Quantity{Amount: market.Decimal{Value: "2"}},
	}}, nil
}

// NOTE: startingCashKRW constant for test assertions
const startingCashKRW = "10000000"
