package backtest

import (
	"testing"
	"time"

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

type thresholdStrategy struct{}

func (thresholdStrategy) ID() StrategyID {
	return StrategyID("threshold")
}

func (thresholdStrategy) Decide(input StrategyInput) ([]OrderIntent, error) {
	if input.Bar.Close.Amount.Value != "100" {
		return nil, nil
	}
	return []OrderIntent{{
		InstrumentID: input.Bar.InstrumentID,
		Side:         OrderSideBuy,
		Quantity:     market.Quantity{Amount: market.Decimal{Value: "2"}},
	}}, nil
}

func TestStrategyInterfaceAllowsInjection(t *testing.T) {
	var strategy Strategy = thresholdStrategy{}
	bar := market.Bar{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Close: market.Price{
			Currency: market.CurrencyKRW,
			Amount:   market.Decimal{Value: "100"},
		},
	}

	orders, err := strategy.Decide(StrategyInput{
		Bar:       bar,
		Portfolio: NewPortfolioState(price("1000")),
	})
	if err != nil {
		t.Fatalf("Decide returned error: %v", err)
	}
	if strategy.ID() != StrategyID("threshold") {
		t.Fatalf("strategy ID mismatch: got %q", strategy.ID())
	}
	if len(orders) != 1 {
		t.Fatalf("orders length: got %d, want 1", len(orders))
	}
	if orders[0].Side != OrderSideBuy {
		t.Fatalf("order side: got %q, want %q", orders[0].Side, OrderSideBuy)
	}
}

func TestPortfolioStateAppliesFillsAndCalculatesEquity(t *testing.T) {
	state := NewPortfolioState(price("1000"))

	state, err := state.ApplyFill(Fill{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Side:         OrderSideBuy,
		Quantity:     quantity("2"),
		Price:        price("100"),
	})
	if err != nil {
		t.Fatalf("ApplyFill buy returned error: %v", err)
	}
	if state.Cash.Amount.Value != "800" {
		t.Fatalf("cash after buy: got %q, want %q", state.Cash.Amount.Value, "800")
	}
	position, ok := state.Position(market.InstrumentID("KRX:005930"))
	if !ok {
		t.Fatal("expected position after buy")
	}
	if position.Quantity.Amount.Value != "2" {
		t.Fatalf("position quantity after buy: got %q, want %q", position.Quantity.Amount.Value, "2")
	}

	equity, err := state.Equity()
	if err != nil {
		t.Fatalf("Equity after buy returned error: %v", err)
	}
	if equity.Amount.Value != "1000" {
		t.Fatalf("equity after buy: got %q, want %q", equity.Amount.Value, "1000")
	}

	state, err = state.MarkPrice(market.InstrumentID("KRX:005930"), price("125"))
	if err != nil {
		t.Fatalf("MarkPrice returned error: %v", err)
	}
	equity, err = state.Equity()
	if err != nil {
		t.Fatalf("Equity after mark returned error: %v", err)
	}
	if equity.Amount.Value != "1050" {
		t.Fatalf("equity after mark: got %q, want %q", equity.Amount.Value, "1050")
	}

	state, err = state.ApplyFill(Fill{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Side:         OrderSideSell,
		Quantity:     quantity("1"),
		Price:        price("150"),
	})
	if err != nil {
		t.Fatalf("ApplyFill sell returned error: %v", err)
	}
	if state.Cash.Amount.Value != "950" {
		t.Fatalf("cash after sell: got %q, want %q", state.Cash.Amount.Value, "950")
	}
	position, ok = state.Position(market.InstrumentID("KRX:005930"))
	if !ok {
		t.Fatal("expected remaining position after partial sell")
	}
	if position.Quantity.Amount.Value != "1" {
		t.Fatalf("position quantity after sell: got %q, want %q", position.Quantity.Amount.Value, "1")
	}
}

func TestPortfolioStateRejectsInvalidFills(t *testing.T) {
	state := NewPortfolioState(price("100"))

	_, err := state.ApplyFill(Fill{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Side:         OrderSideBuy,
		Quantity:     quantity("2"),
		Price: market.Price{
			Currency: market.CurrencyUSD,
			Amount:   market.Decimal{Value: "10"},
		},
	})
	if err == nil {
		t.Fatal("expected currency mismatch error")
	}

	_, err = state.ApplyFill(Fill{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Side:         OrderSideSell,
		Quantity:     quantity("1"),
		Price:        price("10"),
	})
	if err == nil {
		t.Fatal("expected oversell error")
	}
}

func price(amount string) market.Price {
	return market.Price{
		Currency: market.CurrencyKRW,
		Amount:   market.Decimal{Value: amount},
	}
}

func quantity(amount string) market.Quantity {
	return market.Quantity{Amount: market.Decimal{Value: amount}}
}

func TestResultCarriesTradeAndPositionSummary(t *testing.T) {
	now := time.Now().UTC()
	res := Result{
		RunID:        RunID("run-123"),
		StartingCash: price("1000"),
		EndingEquity: price("1500"),
		Trades: []TradeSummary{
			{
				InstrumentID: market.InstrumentID("KRX:005930"),
				Side:         OrderSideBuy,
				Quantity:     quantity("10"),
				Price:        price("500"),
				Timestamp:    now,
			},
		},
		Positions: []PositionSummary{
			{
				InstrumentID: market.InstrumentID("KRX:005930"),
				Quantity:     quantity("10"),
				LastPrice:    price("520"),
			},
		},
	}

	if res.RunID != RunID("run-123") {
		t.Errorf("RunID mismatch: got %v, want run-123", res.RunID)
	}
	if res.StartingCash.Amount.Value != "1000" {
		t.Errorf("StartingCash mismatch: got %v, want 1000", res.StartingCash.Amount.Value)
	}
	if res.EndingEquity.Amount.Value != "1500" {
		t.Errorf("EndingEquity mismatch: got %v, want 1500", res.EndingEquity.Amount.Value)
	}
	if len(res.Trades) != 1 {
		t.Fatalf("Trades length mismatch: got %d, want 1", len(res.Trades))
	}
	trade := res.Trades[0]
	if trade.InstrumentID != market.InstrumentID("KRX:005930") {
		t.Errorf("Trade InstrumentID mismatch: got %v", trade.InstrumentID)
	}
	if trade.Side != OrderSideBuy {
		t.Errorf("Trade Side mismatch: got %v", trade.Side)
	}
	if trade.Quantity.Amount.Value != "10" {
		t.Errorf("Trade Quantity mismatch: got %v", trade.Quantity.Amount.Value)
	}
	if trade.Price.Amount.Value != "500" {
		t.Errorf("Trade Price mismatch: got %v", trade.Price.Amount.Value)
	}
	if !trade.Timestamp.Equal(now) {
		t.Errorf("Trade Timestamp mismatch: got %v, want %v", trade.Timestamp, now)
	}

	if len(res.Positions) != 1 {
		t.Fatalf("Positions length mismatch: got %d, want 1", len(res.Positions))
	}
	pos := res.Positions[0]
	if pos.InstrumentID != market.InstrumentID("KRX:005930") {
		t.Errorf("Position InstrumentID mismatch: got %v", pos.InstrumentID)
	}
	if pos.Quantity.Amount.Value != "10" {
		t.Errorf("Position Quantity mismatch: got %v", pos.Quantity.Amount.Value)
	}
	if pos.LastPrice.Amount.Value != "520" {
		t.Errorf("Position LastPrice mismatch: got %v", pos.LastPrice.Amount.Value)
	}
}

func TestResultCarriesAnalysisSummaryAndEquityCurve(t *testing.T) {
	t1 := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
	t2 := time.Date(2026, 5, 2, 0, 0, 0, 0, time.UTC)
	res := Result{
		RunID:        RunID("run-summary"),
		StartingCash: price("1000"),
		EndingEquity: price("1500"),
		Summary: SummaryMetrics{
			StartingCash: price("1000"),
			EndingEquity: price("1500"),
			TotalReturn:  TotalReturn(price("1000"), price("1500")),
			TradeCount:   3,
		},
		EquityCurve: []EquityPoint{
			{Timestamp: t1, Equity: price("1200")},
			{Timestamp: t2, Equity: price("1500")},
		},
	}

	if res.Summary.TotalReturn.Value != "0.5" {
		t.Errorf("Summary TotalReturn mismatch: got %q, want 0.5", res.Summary.TotalReturn.Value)
	}
	if res.Summary.TradeCount != 3 {
		t.Errorf("Summary TradeCount mismatch: got %d, want 3", res.Summary.TradeCount)
	}
	if res.Summary.StartingCash.Amount.Value != "1000" || res.Summary.EndingEquity.Amount.Value != "1500" {
		t.Errorf("Summary cash/equity mismatch: %+v", res.Summary)
	}

	if len(res.EquityCurve) != 2 {
		t.Fatalf("EquityCurve length mismatch: got %d, want 2", len(res.EquityCurve))
	}
	if !res.EquityCurve[0].Timestamp.Equal(t1) || res.EquityCurve[0].Equity.Amount.Value != "1200" {
		t.Errorf("EquityCurve point 0 mismatch: %+v", res.EquityCurve[0])
	}
	if !res.EquityCurve[1].Timestamp.Equal(t2) || res.EquityCurve[1].Equity.Amount.Value != "1500" {
		t.Errorf("EquityCurve point 1 mismatch: %+v", res.EquityCurve[1])
	}
}

func TestTotalReturnHandlesZeroAndInvalidStartingCash(t *testing.T) {
	if got := TotalReturn(price("0"), price("1500")); got.Value != "0" {
		t.Errorf("zero starting cash: got %q, want 0", got.Value)
	}
	if got := TotalReturn(market.Price{Amount: market.Decimal{Value: "bad"}}, price("1500")); got.Value != "0" {
		t.Errorf("invalid starting cash: got %q, want 0", got.Value)
	}
	if got := TotalReturn(price("10000000"), price("10000300")); got.Value != "0.00003" {
		t.Errorf("fixture total return: got %q, want 0.00003", got.Value)
	}
}

func TestCheckRiskRejectsShortSellByDefault(t *testing.T) {
	account := PaperAccount{
		Portfolio: NewPortfolioState(price("10000")),
	}
	order := OrderIntent{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Side:         OrderSideSell,
		Quantity:     quantity("10"),
	}

	decision := CheckRisk(account, order)
	if decision.Allowed {
		t.Fatal("expected short sell to be rejected when no position exists")
	}
	if decision.Reason == "" {
		t.Fatal("expected a reason for risk rejection")
	}
}

func TestCheckRiskAllowsSellWithSufficientPosition(t *testing.T) {
	state := NewPortfolioState(price("10000"))
	state.Positions[market.InstrumentID("KRX:005930")] = Position{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Quantity:     quantity("100"),
		LastPrice:    price("500"),
	}
	order := OrderIntent{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Side:         OrderSideSell,
		Quantity:     quantity("10"),
	}

	decision := CheckRisk(PaperAccount{Portfolio: state}, order)
	if !decision.Allowed {
		t.Fatalf("expected sell to be allowed with sufficient position: %s", decision.Reason)
	}
}

func TestCheckRiskAllowsBuyWithoutShortCheck(t *testing.T) {
	account := PaperAccount{
		Portfolio: NewPortfolioState(price("10000")),
	}
	order := OrderIntent{
		Side:     OrderSideBuy,
		Quantity: quantity("10"),
	}

	decision := CheckRisk(account, order)
	if !decision.Allowed {
		t.Fatal("expected buy to be allowed without position check")
	}
}

func TestPaperAccountCarriesPortfolioState(t *testing.T) {
	port := NewPortfolioState(price("5000"))
	port.Positions[market.InstrumentID("KRX:005930")] = Position{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Quantity:     quantity("50"),
		LastPrice:    price("480"),
	}
	account := PaperAccount{
		ID:        PaperAccountID("paper-1"),
		Portfolio: port,
		FillPolicy: FillPolicyDailyNextBarOHLC,
	}
	if account.Portfolio.Cash.Amount.Value != "5000" {
		t.Fatalf("portfolio cash mismatch: got %q, want 5000", account.Portfolio.Cash.Amount.Value)
	}
	if _, ok := account.Portfolio.Positions[market.InstrumentID("KRX:005930")]; !ok {
		t.Fatal("expected position to carry over from portfolio state")
	}
}

func TestCheckRiskAllowsShortSellWhenEnabled(t *testing.T) {
	account := PaperAccount{
		Portfolio:    NewPortfolioState(price("10000")),
		RiskSettings: RiskSettings{AllowShortSelling: true},
	}
	order := OrderIntent{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Side:         OrderSideSell,
		Quantity:     quantity("10"),
	}

	decision := CheckRisk(account, order)
	if !decision.Allowed {
		t.Fatalf("expected short sell to be allowed when enabled: %s", decision.Reason)
	}
}

func TestCheckRiskRejectsExceedingPositionSell(t *testing.T) {
	state := NewPortfolioState(price("10000"))
	state.Positions[market.InstrumentID("KRX:005930")] = Position{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Quantity:     quantity("5"),
		LastPrice:    price("500"),
	}
	order := OrderIntent{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Side:         OrderSideSell,
		Quantity:     quantity("10"),
	}

	decision := CheckRisk(PaperAccount{Portfolio: state}, order)
	if decision.Allowed {
		t.Fatal("expected sell to be rejected when order quantity exceeds position")
	}
	if decision.Reason == "" {
		t.Fatal("expected a reason for rejection")
	}
}

func TestFillOrderOnDailyBarLimitSellCrossing(t *testing.T) {
	order := OrderIntent{
		Type:       OrderTypeLimit,
		Side:       OrderSideSell,
		Quantity:   quantity("10"),
		LimitPrice: price("505"),
	}
	bar := market.Bar{
		Open:  price("500"),
		High:  price("520"),
		Low:   price("490"),
		Close: price("510"),
	}

	fill, ok, err := FillOrderOnDailyBar(order, bar)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !ok {
		t.Fatal("expected sell order to be fillable when limit crosses bar range")
	}
	if fill.Price.Amount.Value != "505" {
		t.Fatalf("fill price: got %q, want 505", fill.Price.Amount.Value)
	}
	if fill.Price.Currency != market.CurrencyKRW {
		t.Fatalf("fill price currency: got %q, want KRW", fill.Price.Currency)
	}
}

func TestFillOrderOnDailyBarLimitSellNoCrossing(t *testing.T) {
	order := OrderIntent{
		Type:       OrderTypeLimit,
		Side:       OrderSideSell,
		Quantity:   quantity("10"),
		LimitPrice: price("530"),
	}
	bar := market.Bar{
		Open:  price("500"),
		High:  price("520"),
		Low:   price("490"),
		Close: price("510"),
	}

	_, ok, err := FillOrderOnDailyBar(order, bar)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if ok {
		t.Fatal("expected sell order not to be fillable when limit price is above bar high")
	}
}

func TestFillOrderOnDailyBarMarketUsesOpen(t *testing.T) {
	wantTime := time.Date(2026, 6, 1, 15, 0, 0, 0, time.UTC)
	order := OrderIntent{
		Side:     OrderSideBuy,
		Quantity: quantity("10"),
	}
	bar := market.Bar{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Open:         price("500"),
		High:         price("520"),
		Low:          price("490"),
		Close:        price("510"),
		Timestamp:    wantTime,
	}

	fill, ok, err := FillOrderOnDailyBar(order, bar)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !ok {
		t.Fatal("expected order to be fillable")
	}
	if fill.Price.Amount.Value != "500" {
		t.Fatalf("fill price: got %q, want 500", fill.Price.Amount.Value)
	}
	if !fill.Timestamp.Equal(wantTime) {
		t.Fatalf("fill timestamp: got %v, want %v", fill.Timestamp, wantTime)
	}
}

func TestFillOrderOnDailyBarLimitCrossing(t *testing.T) {
	wantTime := time.Date(2026, 6, 1, 15, 0, 0, 0, time.UTC)
	order := OrderIntent{
		Type:       OrderTypeLimit,
		Side:       OrderSideBuy,
		Quantity:   quantity("10"),
		LimitPrice: price("505"),
	}
	bar := market.Bar{
		InstrumentID: market.InstrumentID("KRX:005930"),
		Open:         price("500"),
		High:         price("520"),
		Low:          price("490"),
		Close:        price("510"),
		Timestamp:    wantTime,
	}

	fill, ok, err := FillOrderOnDailyBar(order, bar)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !ok {
		t.Fatal("expected order to be fillable when limit crosses bar range")
	}
	if fill.Price.Amount.Value != "505" {
		t.Fatalf("fill price: got %q, want 505", fill.Price.Amount.Value)
	}
	if !fill.Timestamp.Equal(wantTime) {
		t.Fatalf("fill timestamp: got %v, want %v", fill.Timestamp, wantTime)
	}
}

func TestFillOrderOnDailyBarRejectsMissingLimit(t *testing.T) {
	order := OrderIntent{
		Type:       OrderTypeLimit,
		Side:       OrderSideBuy,
		Quantity:   quantity("10"),
		LimitPrice: market.Price{},
	}
	bar := market.Bar{
		Open:  price("500"),
		High:  price("520"),
		Low:   price("490"),
		Close: price("510"),
	}

	_, ok, err := FillOrderOnDailyBar(order, bar)
	if err == nil {
		t.Fatal("expected error for limit order without limit price")
	}
	if ok {
		t.Fatal("expected order not to be fillable without limit price")
	}
}

func TestFillOrderOnDailyBarRejectsInvalidBarRange(t *testing.T) {
	order := OrderIntent{
		Type:       OrderTypeLimit,
		Side:       OrderSideBuy,
		Quantity:   quantity("10"),
		LimitPrice: price("500"),
	}

	t.Run("low high currency mismatch", func(t *testing.T) {
		bar := market.Bar{
			Open:  price("500"),
			High:  price("520"),
			Low:   market.Price{Currency: market.CurrencyUSD, Amount: market.Decimal{Value: "490"}},
			Close: price("510"),
		}
		_, ok, err := FillOrderOnDailyBar(order, bar)
		if err == nil {
			t.Fatal("expected error for currency mismatch between limit and low")
		}
		if ok {
			t.Fatal("expected order not to be fillable with currency mismatch")
		}
	})

	t.Run("invalid high decimal", func(t *testing.T) {
		bar := market.Bar{
			Open:  price("500"),
			High:  market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "bad"}},
			Low:   price("490"),
			Close: price("510"),
		}
		_, ok, err := FillOrderOnDailyBar(order, bar)
		if err == nil {
			t.Fatal("expected error for invalid high decimal")
		}
		if ok {
			t.Fatal("expected order not to be fillable with invalid high decimal")
		}
	})

	t.Run("invalid low decimal", func(t *testing.T) {
		bar := market.Bar{
			Open:  price("500"),
			High:  price("520"),
			Low:   market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "bad"}},
			Close: price("510"),
		}
		_, ok, err := FillOrderOnDailyBar(order, bar)
		if err == nil {
			t.Fatal("expected error for invalid low decimal")
		}
		if ok {
			t.Fatal("expected order not to be fillable with invalid low decimal")
		}
	})

	t.Run("low greater than high", func(t *testing.T) {
		bar := market.Bar{
			Open:  price("500"),
			High:  price("480"),
			Low:   price("520"),
			Close: price("510"),
		}
		_, ok, err := FillOrderOnDailyBar(order, bar)
		if err == nil {
			t.Fatal("expected error when low > high")
		}
		if ok {
			t.Fatal("expected order not to be fillable with low > high")
		}
	})
}
