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
}

func (m *mockBarSource) GetBars(ctx context.Context, mkt market.Market, timeframe market.Timeframe, from, to time.Time) ([]market.Bar, error) {
	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
}

func TestEngineCallsStrategyForBars(t *testing.T) {
	// Create fixture bars in unsorted order to verify sorting
	bar1 := market.Bar{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Timeframe:    market.TimeframeDaily,
		Timestamp:    time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
		Close:        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),
		Close:        market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "1100"}},
	}

	barSource := &mockBarSource{bars: []market.Bar{bar2, bar1}} // unsorted
	strat := &mockStrategy{}
	strategyPort := &mockStrategyPort{strategy: strat}

	engine := NewEngine(barSource, strategyPort, nil)

	run := backtest.Run{
		ID: "run-1",
		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))
	}

	// Verify chronological order: first call should have bar1 (May 1), second should have bar2 (May 2)
	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)
	}
	// Initial portfolio cash should be 10000000 (KRW default)
	if firstCall.Portfolio.Cash.Amount.Value != "10000000" {
		t.Errorf("expected starting cash 10000000, got %s", firstCall.Portfolio.Cash.Amount.Value)
	}

	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)
	}
	// History in second call should contain bar1
	if len(secondCall.History) != 1 {
		t.Fatalf("expected history size 1 in second call, got %d", len(secondCall.History))
	}
	if !secondCall.History[0].Timestamp.Equal(bar1.Timestamp) {
		t.Errorf("expected history[0] to be May 1 bar, got %s", secondCall.History[0].Timestamp)
	}
	// Portfolio cash in second call should reflect the purchase: 10000000 - 2 * 1000 = 9998000
	if secondCall.Portfolio.Cash.Amount.Value != "9998000" {
		t.Errorf("expected cash after buy to be 9998000, got %s", secondCall.Portfolio.Cash.Amount.Value)
	}
	// Position quantity should be 2
	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)
	}
}
