package backtest

import (
	"fmt"
	"math/big"
	"strings"
	"time"

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

type RunID string
type StrategyID string

type RunStatus string

const (
	RunStatusPending   RunStatus = "pending"
	RunStatusRunning   RunStatus = "running"
	RunStatusSucceeded RunStatus = "succeeded"
	RunStatusFailed    RunStatus = "failed"
	RunStatusCanceled  RunStatus = "canceled"
)

type RunSpec struct {
	StrategyID StrategyID
	Market     market.Market
	Timeframe  market.Timeframe
	From       time.Time
	To         time.Time
}

type Run struct {
	ID        RunID
	Spec      RunSpec
	Status    RunStatus
	CreatedAt time.Time
	UpdatedAt time.Time
}

type TradeSummary struct {
	InstrumentID market.InstrumentID
	Side         OrderSide
	Quantity     market.Quantity
	Price        market.Price
	Timestamp    time.Time
}

type PositionSummary struct {
	InstrumentID market.InstrumentID
	Quantity     market.Quantity
	LastPrice    market.Price
}

type SummaryMetrics struct {
	StartingCash market.Price
	EndingEquity market.Price
	TotalReturn  market.Decimal
	TradeCount   int
}

type EquityPoint struct {
	Timestamp time.Time
	Equity    market.Price
}

type Result struct {
	RunID        RunID
	StartingCash market.Price
	EndingEquity market.Price
	Trades       []TradeSummary
	Positions    []PositionSummary
	Summary      SummaryMetrics
	EquityCurve  []EquityPoint
}

// TotalReturn computes (endingEquity - startingCash) / startingCash as a decimal
// string. Starting cash that is zero or invalid yields "0" so callers never divide
// by zero or surface float artifacts.
func TotalReturn(startingCash, endingEquity market.Price) market.Decimal {
	start, err := decimalRat(startingCash.Amount)
	if err != nil || start.Sign() == 0 {
		return market.Decimal{Value: "0"}
	}
	end, err := decimalRat(endingEquity.Amount)
	if err != nil {
		return market.Decimal{Value: "0"}
	}
	ratio := new(big.Rat).Quo(new(big.Rat).Sub(end, start), start)
	return decimalValue(ratio)
}

type Strategy interface {
	ID() StrategyID
	Decide(input StrategyInput) ([]OrderIntent, error)
}

type StrategyInput struct {
	Run       Run
	Bar       market.Bar
	Portfolio PortfolioState
	History   []market.Bar
}

type OrderSide string

const (
	OrderSideBuy  OrderSide = "buy"
	OrderSideSell OrderSide = "sell"
)

type OrderType string

const (
	OrderTypeMarket OrderType = "market"
	OrderTypeLimit  OrderType = "limit"
)

type OrderIntent struct {
	InstrumentID market.InstrumentID
	Side         OrderSide
	Quantity     market.Quantity
	Type         OrderType
	LimitPrice   market.Price
}

func (o OrderIntent) OrderType() OrderType {
	if o.Type == "" {
		return OrderTypeMarket
	}
	return o.Type
}

type Fill struct {
	InstrumentID market.InstrumentID
	Side         OrderSide
	Quantity     market.Quantity
	Price        market.Price
	Timestamp    time.Time
}

type Position struct {
	InstrumentID market.InstrumentID
	Quantity     market.Quantity
	LastPrice    market.Price
}

type PortfolioState struct {
	Cash      market.Price
	Positions map[market.InstrumentID]Position
}

func NewPortfolioState(cash market.Price) PortfolioState {
	return PortfolioState{
		Cash:      cash,
		Positions: make(map[market.InstrumentID]Position),
	}
}

func (p PortfolioState) Position(id market.InstrumentID) (Position, bool) {
	position, ok := p.Positions[id]
	return position, ok
}

func (p PortfolioState) ApplyFill(fill Fill) (PortfolioState, error) {
	if fill.InstrumentID == "" {
		return PortfolioState{}, fmt.Errorf("fill instrument id is required")
	}
	if fill.Price.Currency != p.Cash.Currency {
		return PortfolioState{}, fmt.Errorf("fill price currency %q does not match cash currency %q", fill.Price.Currency, p.Cash.Currency)
	}

	quantity, err := decimalRat(fill.Quantity.Amount)
	if err != nil {
		return PortfolioState{}, fmt.Errorf("invalid fill quantity: %w", err)
	}
	if quantity.Sign() <= 0 {
		return PortfolioState{}, fmt.Errorf("fill quantity must be positive")
	}

	price, err := decimalRat(fill.Price.Amount)
	if err != nil {
		return PortfolioState{}, fmt.Errorf("invalid fill price: %w", err)
	}
	if price.Sign() < 0 {
		return PortfolioState{}, fmt.Errorf("fill price must not be negative")
	}

	cash, err := decimalRat(p.Cash.Amount)
	if err != nil {
		return PortfolioState{}, fmt.Errorf("invalid cash amount: %w", err)
	}

	next := p.clone()
	position := next.Positions[fill.InstrumentID]
	position.InstrumentID = fill.InstrumentID
	position.LastPrice = fill.Price

	positionQuantity, err := positionQuantityRat(position)
	if err != nil {
		return PortfolioState{}, fmt.Errorf("invalid position quantity: %w", err)
	}

	notional := new(big.Rat).Mul(quantity, price)
	switch fill.Side {
	case OrderSideBuy:
		if cash.Cmp(notional) < 0 {
			return PortfolioState{}, fmt.Errorf("insufficient cash for fill")
		}
		cash.Sub(cash, notional)
		positionQuantity.Add(positionQuantity, quantity)
	case OrderSideSell:
		if positionQuantity.Cmp(quantity) < 0 {
			return PortfolioState{}, fmt.Errorf("insufficient position quantity for fill")
		}
		cash.Add(cash, notional)
		positionQuantity.Sub(positionQuantity, quantity)
	default:
		return PortfolioState{}, fmt.Errorf("unsupported fill side %q", fill.Side)
	}

	next.Cash.Amount = decimalValue(cash)
	if positionQuantity.Sign() == 0 {
		delete(next.Positions, fill.InstrumentID)
		return next, nil
	}
	position.Quantity.Amount = decimalValue(positionQuantity)
	next.Positions[fill.InstrumentID] = position
	return next, nil
}

func (p PortfolioState) MarkPrice(id market.InstrumentID, price market.Price) (PortfolioState, error) {
	if price.Currency != p.Cash.Currency {
		return PortfolioState{}, fmt.Errorf("mark price currency %q does not match cash currency %q", price.Currency, p.Cash.Currency)
	}
	position, ok := p.Positions[id]
	if !ok {
		return PortfolioState{}, fmt.Errorf("position %q not found", id)
	}
	if _, err := decimalRat(price.Amount); err != nil {
		return PortfolioState{}, fmt.Errorf("invalid mark price: %w", err)
	}

	next := p.clone()
	position.LastPrice = price
	next.Positions[id] = position
	return next, nil
}

func (p PortfolioState) Equity() (market.Price, error) {
	equity, err := decimalRat(p.Cash.Amount)
	if err != nil {
		return market.Price{}, fmt.Errorf("invalid cash amount: %w", err)
	}

	for id, position := range p.Positions {
		if position.LastPrice.Currency != p.Cash.Currency {
			return market.Price{}, fmt.Errorf("position %q price currency %q does not match cash currency %q", id, position.LastPrice.Currency, p.Cash.Currency)
		}
		quantity, err := decimalRat(position.Quantity.Amount)
		if err != nil {
			return market.Price{}, fmt.Errorf("invalid position %q quantity: %w", id, err)
		}
		price, err := decimalRat(position.LastPrice.Amount)
		if err != nil {
			return market.Price{}, fmt.Errorf("invalid position %q price: %w", id, err)
		}
		equity.Add(equity, new(big.Rat).Mul(quantity, price))
	}

	return market.Price{
		Currency: p.Cash.Currency,
		Amount:   decimalValue(equity),
	}, nil
}

func (p PortfolioState) clone() PortfolioState {
	positions := make(map[market.InstrumentID]Position, len(p.Positions))
	for id, position := range p.Positions {
		positions[id] = position
	}
	return PortfolioState{
		Cash:      p.Cash,
		Positions: positions,
	}
}

func decimalRat(decimal market.Decimal) (*big.Rat, error) {
	value := strings.TrimSpace(decimal.Value)
	if value == "" {
		return nil, fmt.Errorf("empty decimal value")
	}
	rat, ok := new(big.Rat).SetString(value)
	if !ok {
		return nil, fmt.Errorf("invalid decimal value %q", decimal.Value)
	}
	return rat, nil
}

func decimalValue(rat *big.Rat) market.Decimal {
	if rat.Sign() == 0 {
		return market.Decimal{Value: "0"}
	}
	if rat.IsInt() {
		return market.Decimal{Value: rat.Num().String()}
	}
	value := strings.TrimRight(rat.FloatString(18), "0")
	value = strings.TrimRight(value, ".")
	if value == "" || value == "-0" {
		value = "0"
	}
	return market.Decimal{Value: value}
}

func positionQuantityRat(position Position) (*big.Rat, error) {
	if position.Quantity.Amount.Value == "" {
		return new(big.Rat), nil
	}
	return decimalRat(position.Quantity.Amount)
}

type PaperAccountID string

type FillPolicy string

const FillPolicyDailyNextBarOHLC FillPolicy = "daily_next_bar_ohlc"

type PaperAccount struct {
	ID           PaperAccountID
	Portfolio    PortfolioState
	UpdatedAt    time.Time
	FillPolicy   FillPolicy
	RiskSettings RiskSettings
}

type RiskSettings struct {
	AllowShortSelling bool
}

type RiskDecision struct {
	Allowed bool
	Reason  string
}

func FillOrderOnDailyBar(order OrderIntent, bar market.Bar) (Fill, bool, error) {
	switch order.OrderType() {
	case OrderTypeMarket:
		return Fill{
			InstrumentID: order.InstrumentID,
			Side:         order.Side,
			Quantity:     order.Quantity,
			Price:        bar.Open,
			Timestamp:    bar.Timestamp,
		}, true, nil
	case OrderTypeLimit:
		if order.LimitPrice.Currency == "" || order.LimitPrice.Amount.Value == "" {
			return Fill{}, false, fmt.Errorf("limit order requires a limit price")
		}
		if order.LimitPrice.Currency != bar.Low.Currency {
			return Fill{}, false, fmt.Errorf("limit price currency %q does not match bar price currency %q", order.LimitPrice.Currency, bar.Low.Currency)
		}
		if order.LimitPrice.Currency != bar.High.Currency {
			return Fill{}, false, fmt.Errorf("limit price currency %q does not match bar high currency %q", order.LimitPrice.Currency, bar.High.Currency)
		}
		limit, err := decimalRat(order.LimitPrice.Amount)
		if err != nil {
			return Fill{}, false, fmt.Errorf("invalid limit price amount: %w", err)
		}
		high, err := decimalRat(bar.High.Amount)
		if err != nil {
			return Fill{}, false, fmt.Errorf("invalid bar high amount: %w", err)
		}
		low, err := decimalRat(bar.Low.Amount)
		if err != nil {
			return Fill{}, false, fmt.Errorf("invalid bar low amount: %w", err)
		}
		if low.Cmp(high) > 0 {
			return Fill{}, false, fmt.Errorf("invalid bar: low %s > high %s", bar.Low.Amount.Value, bar.High.Amount.Value)
		}
		if limit.Cmp(high) > 0 || limit.Cmp(low) < 0 {
			return Fill{}, false, nil
		}
		return Fill{
			InstrumentID: order.InstrumentID,
			Side:         order.Side,
			Quantity:     order.Quantity,
			Price:        order.LimitPrice,
			Timestamp:    bar.Timestamp,
		}, true, nil
	default:
		return Fill{}, false, fmt.Errorf("unsupported order type %q", order.Type)
	}
}

func CheckRisk(account PaperAccount, order OrderIntent) RiskDecision {
	if account.RiskSettings.AllowShortSelling {
		return RiskDecision{Allowed: true, Reason: "short selling allowed"}
	}
	if order.Side != OrderSideSell {
		return RiskDecision{Allowed: true, Reason: "not a sell order"}
	}
	position, ok := account.Portfolio.Positions[order.InstrumentID]
	if !ok {
		return RiskDecision{Allowed: false, Reason: "insufficient position quantity for sell order"}
	}
	posQty, err := decimalRat(position.Quantity.Amount)
	if err != nil {
		return RiskDecision{Allowed: false, Reason: "invalid position quantity"}
	}
	orderQty, err := decimalRat(order.Quantity.Amount)
	if err != nil {
		return RiskDecision{Allowed: false, Reason: "invalid order quantity"}
	}
	if posQty.Cmp(orderQty) < 0 {
		return RiskDecision{Allowed: false, Reason: "insufficient position quantity for sell order"}
	}
	return RiskDecision{Allowed: true, Reason: "risk check passed"}
}
