package papertrading

import (
	"context"
	"errors"
	"testing"
	"time"

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

func serviceTestRequest() StartRequest {
	return StartRequest{
		AccountID: "paper-acct-1",
		Spec: backtest.RunSpec{
			StrategyID: "test-simple-buy",
			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),
		},
		StartingCash: market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "10000000"}},
	}
}

func newTestService() *Service {
	bars := makeBars(nil)
	engine := NewEngine(&testBarSource{bars: bars}, &testStrategyPort{strategy: simpleBuyStrategy{id: "test-simple-buy"}})
	fixed := time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)
	return NewService(engine, func() time.Time { return fixed })
}

func TestServiceStartRecordsState(t *testing.T) {
	svc := newTestService()

	state, err := svc.StartPaperTrading(context.Background(), serviceTestRequest())
	if err != nil {
		t.Fatalf("start failed: %v", err)
	}
	if state.Run.Status != backtest.RunStatusSucceeded {
		t.Errorf("expected succeeded run status, got %q", state.Run.Status)
	}
	if state.Run.ID == "" {
		t.Error("expected a non-empty run id")
	}
	// simpleBuyStrategy buys 1 unit on day 1, fills on day 2 at open 1050.
	if len(state.Fills) != 1 {
		t.Fatalf("expected 1 fill, got %d", len(state.Fills))
	}
	if state.Cash.Amount.Value != "9998950" {
		t.Errorf("expected cash 9998950, got %s", state.Cash.Amount.Value)
	}
	if len(state.Positions) != 1 {
		t.Errorf("expected 1 position, got %d", len(state.Positions))
	}
}

func TestServiceGetStateReturnsRecorded(t *testing.T) {
	svc := newTestService()
	if _, err := svc.StartPaperTrading(context.Background(), serviceTestRequest()); err != nil {
		t.Fatalf("start failed: %v", err)
	}

	state, err := svc.GetPaperTradingState(context.Background(), "paper-acct-1")
	if err != nil {
		t.Fatalf("get state failed: %v", err)
	}
	if state.Run.Status != backtest.RunStatusSucceeded {
		t.Errorf("expected succeeded run status, got %q", state.Run.Status)
	}
}

func TestServiceGetStateNotFound(t *testing.T) {
	svc := newTestService()
	_, err := svc.GetPaperTradingState(context.Background(), "never-started")
	if !errors.Is(err, ErrAccountNotFound) {
		t.Fatalf("expected ErrAccountNotFound, got %v", err)
	}
}

func TestServiceStartRequiresAccountID(t *testing.T) {
	svc := newTestService()
	req := serviceTestRequest()
	req.AccountID = ""
	if _, err := svc.StartPaperTrading(context.Background(), req); err == nil {
		t.Fatal("expected error for empty account id")
	}
}

// --- order lifecycle ---

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

func qty(v string) market.Quantity {
	return market.Quantity{Amount: market.Decimal{Value: v}}
}

// startedService starts the standard test account so order lifecycle tests run
// against a seeded order book (cash 9998950, 1 unit of KRX:005930).
func startedService(t *testing.T) *Service {
	t.Helper()
	svc := newTestService()
	if _, err := svc.StartPaperTrading(context.Background(), serviceTestRequest()); err != nil {
		t.Fatalf("start failed: %v", err)
	}
	return svc
}

func buyMarketIntent(qtyVal string) backtest.OrderIntent {
	return backtest.OrderIntent{
		InstrumentID: "KRX:005930",
		Side:         backtest.OrderSideBuy,
		Quantity:     qty(qtyVal),
		Type:         backtest.OrderTypeMarket,
	}
}

func TestServiceSubmitOrderRecordsPending(t *testing.T) {
	svc := startedService(t)

	order, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{
		AccountID: "paper-acct-1",
		Intent:    buyMarketIntent("1"),
	})
	if err != nil {
		t.Fatalf("submit failed: %v", err)
	}
	if order.Status != PaperOrderStatusPending {
		t.Errorf("expected pending status, got %q", order.Status)
	}
	if order.OrderID != "paper-order-paper-acct-1-1" {
		t.Errorf("unexpected order id %q", order.OrderID)
	}
	if order.Fill != nil {
		t.Errorf("pending order must not carry a fill: %+v", order.Fill)
	}
}

func TestServiceSubmitOrderRequiresStartedAccount(t *testing.T) {
	svc := newTestService()
	_, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{
		AccountID: "never-started",
		Intent:    buyMarketIntent("1"),
	})
	if !errors.Is(err, ErrAccountNotFound) {
		t.Fatalf("expected ErrAccountNotFound, got %v", err)
	}
}

func TestServiceSubmitOrderRejectsInvalidIntent(t *testing.T) {
	svc := startedService(t)
	bad := buyMarketIntent("1")
	bad.Side = "hold"
	if _, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{AccountID: "paper-acct-1", Intent: bad}); err == nil {
		t.Fatal("expected error for unsupported side")
	}
}

func TestServiceFillMarketOrderUpdatesPortfolio(t *testing.T) {
	svc := startedService(t)

	order, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{AccountID: "paper-acct-1", Intent: buyMarketIntent("1")})
	if err != nil {
		t.Fatalf("submit failed: %v", err)
	}

	filled, state, err := svc.FillPaperOrder(context.Background(), "paper-acct-1", order.OrderID, krwPrice("1000"))
	if err != nil {
		t.Fatalf("fill failed: %v", err)
	}
	if filled.Status != PaperOrderStatusFilled {
		t.Fatalf("expected filled status, got %q (reason %q)", filled.Status, filled.Reason)
	}
	if filled.Fill == nil || filled.Fill.Price.Amount.Value != "1000" {
		t.Fatalf("expected fill at 1000, got %+v", filled.Fill)
	}
	// Buy 1 @ 1000 against seed cash 9998950 -> 9997950, position 1 + 1 = 2.
	if state.Cash.Amount.Value != "9997950" {
		t.Errorf("expected cash 9997950, got %s", state.Cash.Amount.Value)
	}
	if len(state.Positions) != 1 || state.Positions[0].Quantity.Amount.Value != "2" {
		t.Errorf("expected position qty 2, got %+v", state.Positions)
	}
	// Seed run fill (1) plus the manual fill (1).
	if len(state.Fills) != 2 {
		t.Errorf("expected 2 cumulative fills, got %d", len(state.Fills))
	}
}

func TestServiceFillLimitOrderUsesLimitPrice(t *testing.T) {
	svc := startedService(t)

	intent := backtest.OrderIntent{
		InstrumentID: "KRX:005930",
		Side:         backtest.OrderSideBuy,
		Quantity:     qty("1"),
		Type:         backtest.OrderTypeLimit,
		LimitPrice:   krwPrice("900"),
	}
	order, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{AccountID: "paper-acct-1", Intent: intent})
	if err != nil {
		t.Fatalf("submit failed: %v", err)
	}

	// A limit order executes at its own limit price; fill_price is irrelevant.
	filled, state, err := svc.FillPaperOrder(context.Background(), "paper-acct-1", order.OrderID, market.Price{})
	if err != nil {
		t.Fatalf("fill failed: %v", err)
	}
	if filled.Status != PaperOrderStatusFilled || filled.Fill.Price.Amount.Value != "900" {
		t.Fatalf("expected fill at limit 900, got status %q fill %+v", filled.Status, filled.Fill)
	}
	if state.Cash.Amount.Value != "9998050" {
		t.Errorf("expected cash 9998050, got %s", state.Cash.Amount.Value)
	}
}

func TestServiceFillMarketOrderRequiresFillPrice(t *testing.T) {
	svc := startedService(t)
	order, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{AccountID: "paper-acct-1", Intent: buyMarketIntent("1")})
	if err != nil {
		t.Fatalf("submit failed: %v", err)
	}
	_, _, err = svc.FillPaperOrder(context.Background(), "paper-acct-1", order.OrderID, market.Price{})
	if !errors.Is(err, ErrFillPriceRequired) {
		t.Fatalf("expected ErrFillPriceRequired, got %v", err)
	}
}

func TestServiceFillRejectedByRisk(t *testing.T) {
	svc := startedService(t)
	// Sell 5 units while the seeded position holds only 1; short selling disabled.
	intent := backtest.OrderIntent{
		InstrumentID: "KRX:005930",
		Side:         backtest.OrderSideSell,
		Quantity:     qty("5"),
		Type:         backtest.OrderTypeMarket,
	}
	order, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{AccountID: "paper-acct-1", Intent: intent})
	if err != nil {
		t.Fatalf("submit failed: %v", err)
	}
	rejected, state, err := svc.FillPaperOrder(context.Background(), "paper-acct-1", order.OrderID, krwPrice("1200"))
	if err != nil {
		t.Fatalf("fill should not error on a risk denial: %v", err)
	}
	if rejected.Status != PaperOrderStatusRejected {
		t.Fatalf("expected rejected status, got %q", rejected.Status)
	}
	if rejected.Reason == "" {
		t.Error("expected a non-empty rejection reason")
	}
	// Risk denial leaves the portfolio untouched.
	if state.Cash.Amount.Value != "9998950" {
		t.Errorf("expected cash unchanged at 9998950, got %s", state.Cash.Amount.Value)
	}
}

func TestServiceFillRejectedByInsufficientCash(t *testing.T) {
	svc := startedService(t)
	order, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{AccountID: "paper-acct-1", Intent: buyMarketIntent("100000")})
	if err != nil {
		t.Fatalf("submit failed: %v", err)
	}
	rejected, state, err := svc.FillPaperOrder(context.Background(), "paper-acct-1", order.OrderID, krwPrice("1000"))
	if err != nil {
		t.Fatalf("fill should not error on a portfolio denial: %v", err)
	}
	if rejected.Status != PaperOrderStatusRejected {
		t.Fatalf("expected rejected status, got %q", rejected.Status)
	}
	if state.Cash.Amount.Value != "9998950" {
		t.Errorf("expected cash unchanged at 9998950, got %s", state.Cash.Amount.Value)
	}
}

func TestServiceCancelTransitionsPendingOrder(t *testing.T) {
	svc := startedService(t)
	order, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{AccountID: "paper-acct-1", Intent: buyMarketIntent("1")})
	if err != nil {
		t.Fatalf("submit failed: %v", err)
	}
	canceled, err := svc.CancelPaperOrder(context.Background(), "paper-acct-1", order.OrderID)
	if err != nil {
		t.Fatalf("cancel failed: %v", err)
	}
	if canceled.Status != PaperOrderStatusCanceled {
		t.Errorf("expected canceled status, got %q", canceled.Status)
	}

	// Filling a canceled order is an invalid transition.
	if _, _, err := svc.FillPaperOrder(context.Background(), "paper-acct-1", order.OrderID, krwPrice("1000")); !errors.Is(err, ErrOrderNotPending) {
		t.Fatalf("expected ErrOrderNotPending, got %v", err)
	}
}

func TestServiceCancelMissingOrder(t *testing.T) {
	svc := startedService(t)
	// Started account but no such order id.
	if _, err := svc.CancelPaperOrder(context.Background(), "paper-acct-1", "paper-order-paper-acct-1-99"); !errors.Is(err, ErrOrderNotFound) {
		t.Fatalf("expected ErrOrderNotFound, got %v", err)
	}
	// Never-started account.
	if _, err := svc.CancelPaperOrder(context.Background(), "ghost", "any"); !errors.Is(err, ErrAccountNotFound) {
		t.Fatalf("expected ErrAccountNotFound, got %v", err)
	}
}

func TestServiceGetStateReflectsOrderBook(t *testing.T) {
	svc := startedService(t)
	order, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{AccountID: "paper-acct-1", Intent: buyMarketIntent("1")})
	if err != nil {
		t.Fatalf("submit failed: %v", err)
	}
	if _, _, err := svc.FillPaperOrder(context.Background(), "paper-acct-1", order.OrderID, krwPrice("1000")); err != nil {
		t.Fatalf("fill failed: %v", err)
	}

	// After a manual fill, the account view reflects the live order book, not the
	// stale terminal run state.
	state, err := svc.GetPaperTradingState(context.Background(), "paper-acct-1")
	if err != nil {
		t.Fatalf("get state failed: %v", err)
	}
	if state.Cash.Amount.Value != "9997950" {
		t.Errorf("expected live cash 9997950, got %s", state.Cash.Amount.Value)
	}
}

// TestServiceStartClearsOrderBook is the restart regression: a second start of
// the same account must discard the prior order book so state and order ids are
// seeded from the new run, not the stale manual portfolio/sequence.
func TestServiceStartClearsOrderBook(t *testing.T) {
	svc := startedService(t)

	order, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{AccountID: "paper-acct-1", Intent: buyMarketIntent("1")})
	if err != nil {
		t.Fatalf("submit failed: %v", err)
	}
	if order.OrderID != "paper-order-paper-acct-1-1" {
		t.Fatalf("unexpected first order id %q", order.OrderID)
	}
	if _, _, err := svc.FillPaperOrder(context.Background(), "paper-acct-1", order.OrderID, krwPrice("1000")); err != nil {
		t.Fatalf("fill failed: %v", err)
	}

	// Pre-restart the live book is exposed (cash mutated by the manual fill).
	pre, err := svc.GetPaperTradingState(context.Background(), "paper-acct-1")
	if err != nil {
		t.Fatalf("pre-restart state failed: %v", err)
	}
	if pre.Cash.Amount.Value != "9997950" {
		t.Fatalf("expected pre-restart live cash 9997950, got %s", pre.Cash.Amount.Value)
	}

	// Restart the same account.
	if _, err := svc.StartPaperTrading(context.Background(), serviceTestRequest()); err != nil {
		t.Fatalf("restart failed: %v", err)
	}

	// State must now reflect the fresh run seed, not the stale order book.
	post, err := svc.GetPaperTradingState(context.Background(), "paper-acct-1")
	if err != nil {
		t.Fatalf("post-restart state failed: %v", err)
	}
	if post.Cash.Amount.Value != "9998950" {
		t.Errorf("expected post-restart run-seed cash 9998950, got %s", post.Cash.Amount.Value)
	}

	// The next order starts from a fresh book sequence.
	next, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{AccountID: "paper-acct-1", Intent: buyMarketIntent("1")})
	if err != nil {
		t.Fatalf("post-restart submit failed: %v", err)
	}
	if next.OrderID != "paper-order-paper-acct-1-1" {
		t.Errorf("expected fresh sequence paper-order-paper-acct-1-1, got %q", next.OrderID)
	}
}

func TestServiceSubmitRejectsInvalidQuantity(t *testing.T) {
	svc := startedService(t)
	for _, q := range []string{"0", "-1", "abc", "  "} {
		_, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{AccountID: "paper-acct-1", Intent: buyMarketIntent(q)})
		if !errors.Is(err, ErrInvalidOrderInput) {
			t.Errorf("quantity %q: expected ErrInvalidOrderInput, got %v", q, err)
		}
	}
}

func TestServiceSubmitRejectsInvalidLimitPrice(t *testing.T) {
	svc := startedService(t)
	for _, p := range []string{"-1", "abc"} {
		intent := backtest.OrderIntent{
			InstrumentID: "KRX:005930",
			Side:         backtest.OrderSideBuy,
			Quantity:     qty("1"),
			Type:         backtest.OrderTypeLimit,
			LimitPrice:   krwPrice(p),
		}
		_, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{AccountID: "paper-acct-1", Intent: intent})
		if !errors.Is(err, ErrInvalidOrderInput) {
			t.Errorf("limit price %q: expected ErrInvalidOrderInput, got %v", p, err)
		}
	}
}

func TestServiceFillRejectsInvalidFillPrice(t *testing.T) {
	svc := startedService(t)
	order, err := svc.SubmitPaperOrder(context.Background(), SubmitOrderRequest{AccountID: "paper-acct-1", Intent: buyMarketIntent("1")})
	if err != nil {
		t.Fatalf("submit failed: %v", err)
	}
	for _, p := range []string{"-1", "abc"} {
		// A malformed/negative fill price is rejected before any portfolio change,
		// so the order stays pending and can be retried with a valid price.
		_, _, err := svc.FillPaperOrder(context.Background(), "paper-acct-1", order.OrderID, krwPrice(p))
		if !errors.Is(err, ErrInvalidOrderInput) {
			t.Errorf("fill price %q: expected ErrInvalidOrderInput, got %v", p, err)
		}
	}
	// A valid fill still succeeds afterwards (order was left pending).
	filled, _, err := svc.FillPaperOrder(context.Background(), "paper-acct-1", order.OrderID, krwPrice("1000"))
	if err != nil {
		t.Fatalf("valid fill failed: %v", err)
	}
	if filled.Status != PaperOrderStatusFilled {
		t.Errorf("expected filled after valid retry, got %q", filled.Status)
	}
}
