package trading

import (
	"math/big"
	"strings"

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

// RiskPolicy defines the live order submission limits applied before any broker
// call. All fields are adjustable at runtime via the worker livetrading config.
// A zero-value policy with no currency limits and zero max-counts means no
// numeric limits are applied, but the kill switch still applies separately.
type RiskPolicy struct {
	// MaxOrderNotionalByCurrency is the maximum allowed notional value per
	// order, keyed by currency. An absent currency key means no limit for that
	// currency. Notional is evaluated only when a limit_price is present in the
	// order intent (i.e. for limit orders).
	MaxOrderNotionalByCurrency map[market.Currency]market.Decimal
	// MaxDailyOrders is the maximum number of orders allowed per calendar day.
	// Zero means no limit is applied.
	MaxDailyOrders int
	// MaxOpenOrders is the maximum number of orders that may be in a non-
	// terminal status at the same time. Zero means no limit is applied.
	MaxOpenOrders int
	// AllowShortSelling, when false, rejects sell-side orders without checking
	// the current position (strict mode). Set to true to allow short selling.
	AllowShortSelling bool
}

// KillSwitchState captures whether live order submission is globally halted.
// The safe default is Halted=true; operators must explicitly disable the kill
// switch before any live order can reach the broker.
type KillSwitchState struct {
	// Halted, when true, blocks all live order submissions regardless of risk
	// policy. Safe default is true.
	Halted bool
	// Reason is a human-readable explanation for the current kill switch state.
	Reason string
}

// DefaultKillSwitch returns the safe initial kill switch state (halted=true).
func DefaultKillSwitch() KillSwitchState {
	return KillSwitchState{Halted: true, Reason: "kill switch active by default"}
}

// RiskDecision is the outcome of a risk policy evaluation.
type RiskDecision struct {
	Allowed bool
	Reason  string
}

// RiskUsage carries the account-level counters used to enforce count-based risk
// limits. The service computes these from its in-memory order registry before
// calling EvaluateRisk.
type RiskUsage struct {
	// DailyOrderCount is the number of orders already submitted for the account
	// on the current UTC calendar day (including all statuses).
	DailyOrderCount int
	// OpenOrderCount is the number of orders in a non-terminal status for the
	// account. Terminal statuses are filled, canceled, and rejected.
	OpenOrderCount int
}

// EvaluateRisk evaluates the kill switch and risk policy for a given order
// intent. It returns a RiskDecision with Allowed=false and a human-readable
// Reason when any guard is triggered.
//
// Evaluation order:
//  1. Kill switch halted → block immediately.
//  2. Short selling disallowed → block sell orders (strict, no position check).
//  3. Order notional exceeds limit for the limit_price currency → block.
//  4. Daily order count at or above MaxDailyOrders (when > 0) → block.
//  5. Open order count at or above MaxOpenOrders (when > 0) → block.
func EvaluateRisk(policy RiskPolicy, ks KillSwitchState, intent OrderIntent, usage RiskUsage) RiskDecision {
	if ks.Halted {
		reason := "kill switch is active"
		if ks.Reason != "" {
			reason = ks.Reason
		}
		return RiskDecision{Allowed: false, Reason: reason}
	}

	if !policy.AllowShortSelling && intent.Side == OrderSideSell {
		return RiskDecision{Allowed: false, Reason: "short selling is not allowed"}
	}

	if len(policy.MaxOrderNotionalByCurrency) > 0 && intent.LimitPrice.Amount.Value != "" {
		cur := intent.LimitPrice.Currency
		if limit, ok := policy.MaxOrderNotionalByCurrency[cur]; ok && limit.Value != "" {
			notional := computeNotional(intent.Quantity.Amount.Value, intent.LimitPrice.Amount.Value)
			if notional != nil {
				limitRat := new(big.Rat)
				if _, ok := limitRat.SetString(strings.TrimSpace(limit.Value)); ok {
					if notional.Cmp(limitRat) > 0 {
						return RiskDecision{Allowed: false, Reason: "order notional exceeds limit"}
					}
				}
			}
		}
	}

	if policy.MaxDailyOrders > 0 && usage.DailyOrderCount >= policy.MaxDailyOrders {
		return RiskDecision{Allowed: false, Reason: "daily order limit reached"}
	}

	if policy.MaxOpenOrders > 0 && usage.OpenOrderCount >= policy.MaxOpenOrders {
		return RiskDecision{Allowed: false, Reason: "open order limit reached"}
	}

	return RiskDecision{Allowed: true, Reason: "risk check passed"}
}

// computeNotional returns qty * price as a *big.Rat, or nil when either value
// is not a valid decimal string.
func computeNotional(qty, price string) *big.Rat {
	qtyRat := new(big.Rat)
	if _, ok := qtyRat.SetString(strings.TrimSpace(qty)); !ok {
		return nil
	}
	priceRat := new(big.Rat)
	if _, ok := priceRat.SetString(strings.TrimSpace(price)); !ok {
		return nil
	}
	return new(big.Rat).Mul(qtyRat, priceRat)
}
